为了账号安全,请及时绑定邮箱和手机立即绑定

多线程基础

标签:
Java

多线程基础

1、线程简介

任务:进程,线程,多线程

现实中很多的例子,其实本质上大脑CPU统一时间依旧只做了一件事情!

  • 线程就是独立的执行路径;

  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;

main()称之为主线程,为系统的入口,用于执行整个程序;

  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

Process与Thread

  • 程序:静态的一组指令和数据的集合

  • 进程:执行程序的一次执行过程,是动态的;进程是系统资源分配的单位

  • 线程:线程就是独立执行的路径,一个进程中包含1-n个线程;线程是CPU调度和执行的单位

    image-20200627013431393

2、线程实现(重点)

任务:Thread、Runnable、Callable

三种创建方式

1、基础Thread 类

2、实现Runnable 接口

3、实现Callable 接口

Thread

start方法:线程交替执行

run方法:按顺序执行

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 1:53
 * 创建线程方式1
 */
//1、继承Thread类
public class PrimeThread extends Thread {
    long minPrime;

    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    //2、重写run 方法
    @Override
    public void run() {
        for (int i = 1; i <= 2000; i++) {
            System.out.println("线程循环:" + i);
        }
    }

    public static void main(String[] args) {
        //3、start 启动运行
        PrimeThread p = new PrimeThread(143);
        p.start();

        for (int i = 1; i <= 2000; i++) {
            System.out.println("主线程循环:" + i);
        }
    }
}

Runnable

推荐使用:同一个对象可以被多次调用,避免OOP单继承的局限性

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 1:57
 * 创建线程方式2
 */
//1、实现Runnable接口
public class PrimeRunable implements Runnable {
    long minPrime;
    PrimeRunable(long minPrime) {
        this.minPrime = minPrime;
    }
    //2、重写run方法
    @Override
    public void run() {
        System.out.println("启动线程" + minPrime);
    }
    public static void main(String[] args) {
        //3、创建线程对象,调用start()方法启动线程
        PrimeRunable p = new PrimeRunable(143);
        new Thread(p, "线1").start();
        new Thread(p, "线2").start();
        new Thread(p, "线3").start();
    }
}

案例:分析多线程下载图片的顺序

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 12:28
 * 练习Thread实现图片 多线程下载
 */
public class PictureThread extends Thread {

    private String url;//图片地址
    private String name;//保存的图片名

    public PictureThread(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //下载图片的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件名为:" + name);
    }

    public static void main(String[] args) {
        PictureThread p1 = new PictureThread("https://cdn2.jianshu.io/assets/default_avatar/2-9636b13945b9ccf345bc98d0d81074eb.jpg","1.jpg");
        PictureThread p2 = new PictureThread("http://seopic.699pic.com/photo/50163/5313.jpg_wh1200.jpg","2.jpg");
        PictureThread p3 = new PictureThread("https://highpic.originoo.com/highpic/detail/161025/RM/bji81201635.jpg", "3.jpg");

        p1.start();
        p2.start();
        p3.start();
    }
}

//下载器
class WebDownloader {
    //下载方法
    public void downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

结果每次都不一样,并不是我们想的1,2,3的顺序

image-20200627130312851

案例:龟兔赛跑-Race

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 19:57
 * 模拟龟兔赛跑
 */
public class Race implements Runnable {

    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //模拟兔子休眠
            if (Thread.currentThread().getName().equals("兔子") && i % 50 == 0) {
                try {
                    Thread.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了就停止程序
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步!");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps) {
        if (winner != null) {//已经存在胜利者了
            return true;
        }
        if (steps >= 100) {
            winner = Thread.currentThread().getName();
            System.out.println("winner is " + winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race, "乌龟").start();
        new Thread(race, "兔子").start();
    }
}

乌龟取得了胜利,兔子走到50步的时候睡觉去了!

image-20200627211140928

案例:初始并发问题

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 19:46
 * 多线程购票 初始并发问题
 * 发现问题:多个线程操作同一个对象时,线程不安全,数据紊乱。
 */
public class BuyTickets implements Runnable {

    //票数
    private int tickeNums = 10;

    @Override
    public void run() {
        while (true) {
            if (tickeNums <= 0) {
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + tickeNums-- + "票");
        }
    }

    public static void main(String[] args) {
        BuyTickets b = new BuyTickets();
        new Thread(b, "小周").start();
        new Thread(b, "肖鑫").start();
        new Thread(b, "黄牛").start();
        new Thread(b, "小明").start();
        new Thread(b, "肖红").start();
        new Thread(b, "萧兰").start();
    }
}

并发操作同一个对象

image-20200627204616644

Callable了解即可

1. 实现 Callable 接口,需要返回值类型

2. 重写 call 方法,需要抛出异常

3. 创建目标对象

4. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);

5. 提交执行: Future result1 = ser.submit(t1);

6. 获取结果: boolean r1 = result1.get()

7. 关闭服务: ser.shutdownNow();

演示:利用callable改造下载图片案例

有返回值

可以抛出异常

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 12:28
 * 创建线程方式3 改造下载图片案例
 */
//1、实现Callable接口,需要返回值类
public class PrimeCallable implements Callable<Boolean> {

    private String url;//图片地址
    private String name;//保存的图片名

    public PrimeCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //2、重写call方法,需要抛出异常
    @Override
    public Boolean call() throws Exception {
        Boolean bool;
        WebDownloader2 webDownloader = new WebDownloader2();
        bool = webDownloader.downloader(url, name);
        System.out.println("下载了文件名为:" + name);
        return bool;
    }

    public static void main(String[] args) throws Exception {
        //3、创建目标对象
        PrimeCallable p1 = new PrimeCallable("https://cdn2.jianshu.io/assets/default_avatar/2-9636b13945b9ccf345bc98d0d81074eb.jpg", "1.jpg");
        PrimeCallable p2 = new PrimeCallable("http://seopic.699pic.com/photo/50163/5313.jpg_wh1200.jpg", "2.jpg");
        PrimeCallable p3 = new PrimeCallable("https://highpic.originoo.com/highpic/detail/161025/RM/bji81201635.jpg", "3.jpg");
        //4、创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //5、提交执行
        Future<Boolean> result1 = ser.submit(p1);
        Future<Boolean> result2 = ser.submit(p2);
        Future<Boolean> result3 = ser.submit(p3);
        //6、获取结果
        Boolean r1 = result1.get();
        Boolean r2 = result2.get();
        Boolean r3 = result3.get();
        //7、关闭服务
        ser.shutdownNow();
        System.out.println("r1:" + r1 + "\tr2:" + r2 + "\tr3:" + r3);
    }
}

//下载器
class WebDownloader2 {
    //下载方法
    public boolean downloader(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
            return false;
        }
        return true;
    }
}

结果:如何只创建一个池子 Executors.newFixedThreadPool(1); 结果就是1-2-3!

image-20200627214102238

3、线程状态

五大状态:

image-20200627215602765

1、对象一旦创建就进入到了新建状态

2、当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行

3、cpu调度后进入运行状态,线程才真正执行线程体的代码块

4、当调用sleep,wait 或同步锁定时,线程进入阻塞状态,就是代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。

5、线程中断或者结束,一旦进入死亡状态,就不能再次启动

线程方法

方 法 说 明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

终止线程

建议线程正常停止

建议使用标志位

不建议使用过期的JDK方法stop/destory等

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 22:20
 * 停止线程
 */
public class StopThread implements Runnable {

    //1、定义一个标志位
    boolean flag = true;
    int count;

    @Override
    public void run() {
        while (flag) {
            System.out.println("跑步,run跑了-->" + count++ + "步");
        }
    }

    //2、设置公开的停止方法
    public void stop() {
        flag = false;
    }

    public static void main(String[] args) {
        StopThread p = new StopThread();
        new Thread(p).start();
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            int timeno = 0;
            @Override
            public void run() {
                timeno++;
                System.err.println("timeno:"+timeno);
                if(timeno==10){
                    p.stop();
                    timer.cancel();
                    System.out.println("线程停止,关闭定时器");
                }
            }
        },0,100);
    }
}

image-20200627224329388

线程休眠 sleep

sleep不会是否锁,每个对象都有一个锁

sleep后的单位是ms,存在异常InterruptedException

休眠后进入就绪状态,可以用来模拟网络延时,倒计时

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 22:54
 * 使用线程模拟倒计时
 */
public class CountDownSheep {
    public static void main(String[] args) throws InterruptedException {
        tenDown();
    }

    private static void tenDown() throws InterruptedException {
        //获取系统当前时间
        Date now = new Date(System.currentTimeMillis());
        while (true) {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(now));
            now = new Date(System.currentTimeMillis());
        }
    }
}

结果每秒输出一次

image-20200627230359466

线程礼让 yield

让当前执行的线程暂停,但不阻塞;线程由运行状态转为就绪状态,等待CPU调度

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 23:08
 * 线程礼让,礼让不一定成功,看CPU心情
 */
public class YieldThread {
    public static void main(String[] args) {
        Mythread p = new Mythread();
        new Thread(p,"A").start();
        new Thread(p,"B").start();
    }
}

class Mythread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--》线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"--》线程停止执行");
    }
}

礼让成功和礼让不成功的结果

image-20200627231303066image-20200627231325081

线程插队 join

线程插队,其他线程阻塞,让插队线程先执行

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 23:16
 * 线程插队
 */
public class JsonThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程VIP来了-->" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JsonThread p = new JsonThread();
        Thread thread = new Thread(p);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if (i == 200) {
                thread.join();//插队
            }
            System.out.println("main==》" + i);
        }
    }
}

image-20200627232143093

查看JDK帮助文档

image-20200627232445075

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 23:25
 * 观察线程状态
 */
public class StateThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(".....");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //观察启动后
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);//RUNNABLE

        while (state != Thread.State.TERMINATED) {//TERMINATED 已退出的线程处于此状态
            Thread.sleep(100);
            state = thread.getState();//更新线程状态
            System.out.println(state);//输出转态
        }
    }
}

image-20200627233521879

线程优先级 priority

Java提供了一个线程调度器 来监控就绪状态的线程,按照优先级1-10来决定调度那个线程先执行

优先级高的不一定先运行,只是概率高

MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-27 23:42
 * 线程优先级
 */
public class PriorityThread {
    public static void main(String[] args) {
        //默认优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
        MyPriority p = new MyPriority();
        Thread t1 = new Thread(p,"t1");
        Thread t2 = new Thread(p,"t2");
        Thread t3 = new Thread(p,"t3");
        Thread t4 = new Thread(p,"t4");
        Thread t5 = new Thread(p,"t5");
        Thread t6 = new Thread(p,"t6");
        //先设置优先级再启动
        t1.start();
        t2.setPriority(1);
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(Thread.MAX_PRIORITY);//10
        t4.start();
        t5.setPriority(8);
        t5.start();
        t6.setPriority(11);
        t6.start();
    }
}

class MyPriority implements Runnable {
    @Override
    public void run() {
        //打印线程名--》优先级
        System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
    }
}

image-20200627235139013

守护线程 daemon

线程分为用户线程和守护线程

daemon = false; 默认是false 用户线程

虚拟机必须确保用户线程执行完毕 eg:main()

虚拟机不用等待守护线程执行完毕 eg:gc()

/**
 * @author 流浪不是我的初衷
 * @create 2020-06-28 0:00
 * 测试守护线程
 */
public class DaemonThread {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread thread = new Thread(god);
        thread.setDaemon(true);//默认是false 用户线程,这里设置为true 守护线程
        thread.start();//上帝守护线程启动

        new Thread(you).start();//你 用户线程启动
    }
}

//上帝
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

//你 人生不过3w天
class You implements Runnable {
    @Override
    public void run() {
        //100年
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.err.println("============goodbye world!");//Hello World!
    }
}

image-20200628000813861

image-20200628000845286

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消