--Java入坑--关于线程你想知道的都在这

文章目录

  • 关于Thread你要掌握得知识
    • ■ 进程与线程的关系 (补充协程)
      • ① 什么是线程?
      • ② 什么是进程?
        • ① 进程间的通信方式
        • ② 孤儿进程
        • ③ 僵尸进程
      • ③ 什么是协程?
      • ④ 线程与进程的区别
    • ■ 如何创建一个线程?
      • ① 继承(extends)Thread类
      • ② 实现Runnable接口
      • ③ 实现Callable接口
    • ■ 线程的生命周期
      • ① 让线程进入阻塞状态的方法
      • ② 让线程从阻塞进入就绪状态的方法
      • ③ 线程状态转换图
    • ■ start()和run()方法的区别是什么?
    • ■ 守护线程是啥?
      • ① 守护线程的使用场景
    • ■ 如何关闭停止一个线程?
    • ■ 线程的优先级
    • ■ 控制线程的方法
      • ① join()方法
      • ② sleep()方法
      • ③ yield() 方法
    • ■ 线程之间是如何进行协作的呢?
      • ① 生产者/消费者模式的性能问题是什么?
    • ♦ 总结

关于Thread你要掌握得知识

■ 进程与线程的关系 (补充协程)

① 什么是线程?

线程打个比方来说就是,一个人在一生中,不停的在做呼吸运动,血液循环,细胞修复,激素分泌等运动,这些运动都是同时进行的,并不是得先做完呼吸运动后才开始血液循环,这种同时执行多个操作的思想,在Java中被称为并发,而将并发完成的每一个事件就称为线程。
专业一点的术语就是:

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

② 什么是进程?

直白地讲,进程就是应用程序的启动实例。比如我们运行一个游戏,打开一个软件,就是开启了一个进程 (进程是系统中正在运行的一个程序,程序一旦运行就是进程)。
专业一点的术语就是:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体

① 进程间的通信方式

1,管道 2,消息队列 3,共享内存 4,信号量 5,Socket

② 孤儿进程

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

③ 僵尸进程

一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。

③ 什么是协程?

协程(Coroutines)是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。协程不是被操作系统内核所管理的(线程被操作系统调度),而是完全由程序所控制,也就是在用户态执行。这样带来的好处是性能大幅度的提升,因为不会像线程切换那样消耗资源

--Java入坑--关于线程你想知道的都在这_第1张图片

④ 线程与进程的区别

线程 进程
单位 系统运算调度的最小单位 系统资源分配和调度的基本单位
地址空间 包含在进程的地址空间 有自己的内存地址空间
资源开销 系统开销较小 系统开销较大
资源共享 共享所在进程的地址空间和其它资源 进程之间不能共享资源
通信 线程中通信较为简单 进程中通信比较复杂
稳壮性

■ 如何创建一个线程?

① 继承(extends)Thread类

继承(extends)Thread类,重写run方法,后进行start()进行启动

class Thread1 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
    }
}

public class MyThread {
    public static void main(String[] args) {
        Thread myThread = new Thread1(); // 继承可以这样new出来
        myThread.setName("Thread1");
        myThread.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
    }
}

我的运行结果如下,你们的结果不一定是和我的一样的:

我是main,现在执行第1次
我是Thread1,现在执行第1次
我是main,现在执行第2次
我是main,现在执行第3次
我是main,现在执行第4次
我是Thread1,现在执行第2次
我是main,现在执行第5次
我是Thread1,现在执行第3次
我是Thread1,现在执行第4次
我是Thread1,现在执行第5次
我是main,现在执行第6次
我是main,现在执行第7次
我是main,现在执行第8次
我是main,现在执行第9次
我是Thread1,现在执行第6次
我是Thread1,现在执行第7次
我是Thread1,现在执行第8次
我是Thread1,现在执行第9次
我是Thread1,现在执行第10次
我是main,现在执行第10次

因为这和操作系统的调度有关系,由操作系统决定什么时候调度线程1,什么时候调度Main线程。

② 实现Runnable接口

实现Runnable接口,重写run()方法,然后通过代理Thread来调用start()来启动

class Thread2 implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
    }
}

public class MyThread {
    public static void main(String[] args) {
        Thread myThread2 = new Thread(new Thread2());  //实现接口需要这样new Thread(实现的类)
        myThread2.setName("Thread2");
        myThread2.start();
        
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
    }
}

我的运行结果如下,你们的结果不一定是和我的一样的:

我是main,现在执行第1次
我是main,现在执行第2次
我是Thread2,现在执行第1次
我是main,现在执行第3次
我是Thread2,现在执行第2次
我是main,现在执行第4次
我是Thread2,现在执行第3次
我是main,现在执行第5次
我是Thread2,现在执行第4次
我是main,现在执行第6次
我是Thread2,现在执行第5次
我是main,现在执行第7次
我是Thread2,现在执行第6次
我是main,现在执行第8次
我是Thread2,现在执行第7次
我是main,现在执行第9次
我是Thread2,现在执行第8次
我是main,现在执行第10次
我是Thread2,现在执行第9次
我是Thread2,现在执行第10次

③ 实现Callable接口

实现Callable接口,重写call()方法,通过代理Thread来接收FutureTask对象并调用start()启动

class Thread3 implements Callable<Object> {

    @Override
    public Object call() throws Exception {  //有异常会抛出
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
        return System.currentTimeMillis();
    }
}

public class MyThread {
    public static void main(String[] args) {
        FutureTask<Object> ft = new FutureTask<>(new Thread3()); // 需要用到FutureTask<>
        Thread myThread3 = new Thread(ft);
        myThread3.setName("Callable");
        myThread3.start();
        try {
            Object _return = ft.get();  //获取返回值
            System.out.println("获取到返回值的信息为:" + _return);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是" + Thread.currentThread().getName() + ",现在执行第" + i + "次");
        }
    }
}

我的运行结果如下,你们的结果可能是和我的一样的:

我是Callable,现在执行第1次
我是Callable,现在执行第2次
我是Callable,现在执行第3次
我是Callable,现在执行第4次
我是Callable,现在执行第5次
我是Callable,现在执行第6次
我是Callable,现在执行第7次
我是Callable,现在执行第8次
我是Callable,现在执行第9次
我是Callable,现在执行第10次
获取到返回值的信息为:1586414586808
我是main,现在执行第1次
我是main,现在执行第2次
我是main,现在执行第3次
我是main,现在执行第4次
我是main,现在执行第5次
我是main,现在执行第6次
我是main,现在执行第7次
我是main,现在执行第8次
我是main,现在执行第9次
我是main,现在执行第10次

通过new FutureTask<>(new Thread3()).get()来获取线程结束后的返回值。

■ 三者对比总结

  • 实现Callable接口是可以在线程运行后获取到返回值的,并且会在call()方法执行过程抛出异常。
  • 如果你不需要在线程中返回东西,并且不执行异常操作,推荐使用implement Runnable接口,因为extends在同个类中只能使用一次,存在单继承问题,但implement可以实现多个接口,这样在实现接口的同时还能继承其它类

■ 线程的生命周期

线程的生命周期包含5个阶段,包括:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、销毁(Dead)

① 让线程进入阻塞状态的方法

  • 线程调用sleep( ) 使线程在一定的时间内进入阻塞状态,不能得到cpu时间,但不会释放锁资源。指定的时间一过,线程重新进入可执行状态。
  • 线程调用wait( ) 使线程进入阻塞状态,同时释放自己占有的锁资源,和notify( )搭配使用
  • 线程调用suspend( )(ps:JDK1.2之后已经废除) 使线程进入阻塞状态,并且不会自动恢复,必须其对应的resume( )被调用,才能使线程重新进入可执行状态
  • 线程调用了一个阻塞式I/O方法(Input/Output),在该方法返回之前,该线程被阻塞

② 让线程从阻塞进入就绪状态的方法

  • sleep()规定的时间到
  • 用户输入内容已经返回完毕
  • 上个线程join()方法执行完毕
  • 休眠线程t.wait()被t.notify()或t.notifyAll()唤醒
  • 拿到同步锁

③ 线程状态转换图

--Java入坑--关于线程你想知道的都在这_第2张图片

■ start()和run()方法的区别是什么?

当一个线程通过new关键字创建之后,该线程就处于新建状态,此时仅由虚拟机为其分配了内存,但是该线程的执行体并不会执行。
调用start()方法来启动线程时,该线程就处于就绪状态,此时该线程可以跟其他线程一起运行。
直接调用run()方法的话,会被当作一个普通的函数调用,程序中仍然只有主线程这一个线程就跟普通的方法没什么区别了,会按照代码顺序进行运行,同时也就没有多线程的效果了。

■ 守护线程是啥?

别急着了解守护线程,在了解它之前,先来了解一下JVM什么情况下能正常退出?(JVM的垃圾回收线程其实就是一个守护线程)下面截了一段JDK官方文档的话:

The Java Virtual Machine exits when the only threads running are all daemon threads.

翻译成中文就是:当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出,换句话说就是:如果有一个线程A在线程B里面跑A不是B的守护线程,那么在线程B跑完的时候,并不会退出JVM,因为线程A是非守护线程,所以程序还是会继续执行A线程里的代码块,可以看看下边的代码和输出再听我讲解下:

class ThreadA implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("我是线程A,现在执行第" + i + "次");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("我是线程A,我也结束了我罪恶的一生");

    }
}
class ThreadB implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("我是线程B,现在执行第" + i + "次");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 在线程B开启线程A
        Thread threadA = new Thread(new ThreadA());
        //这里先注释掉 则A线程不是B线程的守护线程
        //        threadA.setDaemon(true);
        threadA.start();
        System.out.println("我是ThreadB,我结束了我罪恶的一生");
    }
}

public class MyThread {
    public static void main(String[] args) {
        Thread threadB = new Thread(new ThreadB());
        threadB.start();
    }
}

打印结果如下:

我是线程B,现在执行第1次
我是线程B,现在执行第2次
我是线程B,现在执行第3次
我是线程B,现在执行第4次
我是线程B,现在执行第5次
我是ThreadB,我结束了我罪恶的一生
我是线程A,现在执行第1次
我是线程A,现在执行第2次
我是线程A,现在执行第3次
我是线程A,现在执行第4次
我是线程A,现在执行第5次
我是线程A,现在执行第6次
我是线程A,现在执行第7次
我是线程A,现在执行第8次
我是线程A,现在执行第9次
我是线程A,现在执行第10次
我是线程A,我也结束了我罪恶的一生

这是没有将线程A设置成B的守护线程的打印,将一个线程设置成守护线程的方法也很简单,Thread.setDaemon(true)就可以将该Thread设置成守护线程,在这时候,我在线程B中将线程A设置成守护线程,threadA.setDaemon(true)--Java入坑--关于线程你想知道的都在这_第3张图片这时再来看看打印结果:

我是线程B,现在执行第1次
我是线程B,现在执行第2次
我是线程B,现在执行第3次
我是线程B,现在执行第4次
我是线程B,现在执行第5次
我是ThreadB,我结束了我罪恶的一生
我是线程A,现在执行第1次

可以看到当线程B结束时,线程A在没有打印完的情况下就终止了,不过关闭线程B的时间还是会让线程A走一段时间,不过相对没设置守护线程,如果线程A中的循环是死循环,它就能很好的退出程序,并且守护线程也会被回收。

① 守护线程的使用场景

守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。

JVM 中的垃圾回收线程就是典型的守护线程,如果说不具备该特性,会发生什么呢?
当 JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬了!!!由此可见,守护线程的重要性了。

通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。

■ 如何关闭停止一个线程?

一般情况下,线程都是执行完毕后自动结束死亡的,但有时我们却想手动关闭停止一个线程,那该怎么操作呢,比如关闭正在执行的定时器,因为一般是通过while(true)来不断判断时间,然后执行指定任务。
这时我们可以通过一个条件判断(eg:isStop)他是否应该要停止:

class ThreadA implements Runnable{

    // 是否结束,默认为false
    private boolean isStop=false;

    @Override
    public void run() {
        int timeMills = 10;
        while (!isStop){
            System.out.println("距离结束还剩下:" + timeMills + "秒");
            try {
                Thread.sleep(1000);  //阻塞线程,休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 这里其实只用timeMills--就可以了,因为它减不到0,在本程序中
            //timeMills--;
            if(timeMills--==0){
                break;
            }
        }
        System.out.println("done");
    }

    public void stopNow(){
        this.isStop = true;
    }
}
public class MyThread {
    public static void main(String[] args) {
        ThreadA thread = new ThreadA();
        new Thread(thread).start();
        try {
            // 让main线程休眠5s,ThreadA线程仍在运行,只是在sleep()这里卡住5s后再运行下边的结束方法
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(5000);
            // 结束线程
            thread.stopNow();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

main
距离结束还剩下:10秒
距离结束还剩下:9秒
距离结束还剩下:8秒
距离结束还剩下:7秒
距离结束还剩下:6秒
done

正常的话是从10秒一直打印到1秒,留意打印main上边的那块注释。

当然早期是可以通过stop()方法来停止线程的,但由于stop强制停止的特性(立刻释放所有监视器monitors),可能会导致一系列难以处理的问题,故最新java版本stop方法已不可用(ps:貌似现在有个interrupt方法和isInterrupted()方法也可以将线程停止,不过只能使用new Thread(){@Override}内部类来定义Thread1,然后再在另一个Thread2调用Thread1.interrupt(),然后Thread1判断isInterrupted(),不过这个interput我也没整明白,试了几次也结束不了…)。

■ 线程的优先级

首先查看源码可看到,这里的优先级默认为5,最小为1,最大为10,分别对应:

  • NORM_PRIORITY = 5
  • MIN_PRIORITY = 1
  • MAX_PRIORITY = 10
    优先级越大,大概率先执行
    --Java入坑--关于线程你想知道的都在这_第4张图片
    接下来测试各个优先级用例的结果(两个最小优先级,两个最大优先级,一个默认优先级),代码如下:
class ThreadB implements Runnable {

    private int index;
    @Override
    public void run() {
        while (index <= 10) {
            System.out.println("我是:"+Thread.currentThread().getName()+",优先级为:"+Thread.currentThread().getPriority());
            index++;
        }
        index=0;
    }
}

public class MyThread {
    public static void main(String[] args) {

        // 声明对象tb
        ThreadB tb = new ThreadB();

        Thread a = new Thread(tb);
        Thread b = new Thread(tb);
        Thread c = new Thread(tb);
        Thread d = new Thread(tb);
        Thread e = new Thread(tb);

        a.setPriority(Thread.MIN_PRIORITY);  //最小优先级
        b.setPriority(Thread.MAX_PRIORITY);  //最大优先级
        c.setPriority(Thread.NORM_PRIORITY); //默认优先级
        d.setPriority(Thread.MAX_PRIORITY);  //最大优先级
        e.setPriority(Thread.MIN_PRIORITY);  //最小优先级

        a.start();
        b.start();
        c.start();
        d.start();
        e.start();

    }
}

程序打印结果如下:

我是:Thread-1,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-0,优先级为:1
我是:Thread-2,优先级为:5
我是:Thread-0,优先级为:1
我是:Thread-3,优先级为:10
我是:Thread-4,优先级为:1
我是:Thread-1,优先级为:10
我是:Thread-4,优先级为:1
我是:Thread-3,优先级为:10
我是:Thread-0,优先级为:1
我是:Thread-2,优先级为:5
我是:Thread-3,优先级为:10
我是:Thread-4,优先级为:1
我是:Thread-1,优先级为:10
我是:Thread-4,优先级为:1
我是:Thread-3,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-3,优先级为:10
我是:Thread-2,优先级为:5
我是:Thread-4,优先级为:1
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-1,优先级为:10
我是:Thread-4,优先级为:1
我是:Thread-2,优先级为:5
我是:Thread-2,优先级为:5
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-2,优先级为:5
我是:Thread-4,优先级为:1
我是:Thread-2,优先级为:5
我是:Thread-2,优先级为:5
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1
我是:Thread-4,优先级为:1

从结果可以看出,并不是优先级最高就一定最先执行,优先级低的也可能先执行,只是概率上是优先级大的先执行。

■ 控制线程的方法

① join()方法

⚪某种意义上说可以让线程有顺序的执行,同时若在当前ThreadA线程上,调用了ThreadB.join()方法,那么会先把ThreadB线程先完成,后再继续执行ThreadA,前提是ThreadB.join()完之后,下边没有其它线程的join()方法,如果还有其它线程的join()方法,则继续执行其它线程。
代码如下:

public class MyThread {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            private int index;
            @Override
            public void run() {
                while (index <= 5) {
                    System.out.println("我是Thread1,index = " + index + " ");
                    index++;
                }
            }
        };
        Thread t2 = new Thread(){
            private int index;
            @Override
            public void run() {
                while (index <= 5) {
                    try {
                        t1.join();  //这里让t1线程插队
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("我是Thread2,index = " + index + " ");
                    index++;
                }
            }
        };
        Thread t3 = new Thread(){
            private int index;
            @Override
            public void run() {
                while (index <= 5) {
                    try {
                        t2.join();  //这里让t1线程插队
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("我是Thread3,index = " + index + " ");
                    index++;
                }
            }
        };
        t1.start();
        t2.start();
        t3.start();
    }
}

因为在线程t2中,让t1线程插队了,然后再线程t3中,让t2插队了,所以不管一开始是t2还是t3线程执行,若是t3先执行,t2插队执行,插队后,t1又插队执行,所以t1线程必定是第一个执行的,然后就是到t2执行,再到t3,打印结果和预期一致,如下:

我是Thread1,index = 0
我是Thread1,index = 1
我是Thread1,index = 2
我是Thread1,index = 3
我是Thread1,index = 4
我是Thread1,index = 5
我是Thread2,index = 0
我是Thread2,index = 1
我是Thread2,index = 2
我是Thread2,index = 3
我是Thread2,index = 4
我是Thread2,index = 5
我是Thread3,index = 0
我是Thread3,index = 1
我是Thread3,index = 2
我是Thread3,index = 3
我是Thread3,index = 4
我是Thread3,index = 5

若将代码改一改,在t3线程让t1插队,而不让t2插队,想一想,结果会是什么样的呢?
没错,t1还是会最先执行,而t2,t3则会按照系统的调度来执行:--Java入坑--关于线程你想知道的都在这_第5张图片

我是Thread1,index = 0
我是Thread1,index = 1
我是Thread1,index = 2
我是Thread1,index = 3
我是Thread1,index = 4
我是Thread1,index = 5
我是Thread3,index = 0
我是Thread3,index = 1
我是Thread2,index = 0
我是Thread3,index = 2
我是Thread2,index = 1
我是Thread3,index = 3
我是Thread2,index = 2
我是Thread3,index = 4
我是Thread2,index = 3
我是Thread3,index = 5
我是Thread2,index = 4
我是Thread2,index = 5

当然,可以把定义在内部的join删除掉,然后这样写也可以的噢:
--Java入坑--关于线程你想知道的都在这_第6张图片

② sleep()方法

⚪故名思意,就是睡觉,你想睡多久,就往括号里边传入你想睡的时间,单位为ms,使用该方法会将当前线程状态变为阻塞状态,使得该线程不会获得执行操作,即使系统中没有其它线程,该线程仍然是处于阻塞暂停状态(咋瓦鲁多~),当然,如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。
--Java入坑--关于线程你想知道的都在这_第7张图片
--Java入坑--关于线程你想知道的都在这_第8张图片
注意
1、线程睡眠到期自动苏醒,并返回到可运行状态(就绪),不是运行状态。
2、sleep()中指定的时间是线程不会运行的最短时间,sleep方法不能作为精确的时间控制。
3、sleep()是静态方法,只能控制当前正在运行的线程。
4、sleep()方法并不会释放锁

③ yield() 方法

⚪译为线程让步,顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程
它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

  举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,
  有可能是其他人先上车了,也有可能是Yield先上车了。
  
  但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,
  最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

 代码如下:
class ThreadA extends Thread {
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("ThreadA|||" + i);
            Thread.yield();  //-------这里用了yield--------
        }
    }
}

class ThreadB extends Thread {
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("ThreadB---" + i);
            Thread.yield();  //-------这里用了yield--------
        }
    }
}

public class TestYield {
    public static void main(String[] args) {
        Thread threadA = new ThreadA();
        Thread threadB = new ThreadB();
        // 设置优先级:MIN_PRIORITY最低优先级1;NORM_PRIORITY普通优先级5;MAX_PRIORITY最高优先级10
        threadA.setPriority(Thread.MAX_PRIORITY);  // 相同大小优先级
        threadB.setPriority(Thread.MAX_PRIORITY);

        threadA.start();
        threadB.start();
    }
}

这里注意的是,两个线程优先级都相同,都用了yield()进行让步,所以说线程的运行其实是看系统的调度,打印如下:

ThreadA|||0
ThreadB—0
ThreadA|||1
ThreadB—1
ThreadA|||2
ThreadB—2
ThreadA|||3
ThreadB—3
ThreadA|||4
ThreadB—4
ThreadA|||5
ThreadB—5
ThreadA|||6
ThreadB—6
ThreadA|||7
ThreadB—7
ThreadA|||8
ThreadB—8
ThreadA|||9
ThreadB—9
ThreadA|||10
ThreadB—10
ThreadA|||11
ThreadB—11
ThreadA|||12
ThreadB—12
ThreadA|||13
ThreadB—13
ThreadA|||14
ThreadB—14
ThreadA|||15
ThreadB—15
ThreadA|||16
ThreadB—16
ThreadA|||17
ThreadB—17
ThreadA|||18
ThreadB—18
ThreadA|||19
ThreadB—19

然后我就在想,既然是让步的话,说明可以提高另一个线程的执行?我就想着让线程B获得最大优先级,线程A获得最小优先级,然后在线程A中,在使用yield()方法进行让步,会不会使得线程B更大概率先执行呢?改了两处地方:--Java入坑--关于线程你想知道的都在这_第9张图片
执行打印结果:

第一次打印 第二次打印
ThreadB ThreadA
ThreadA ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadA ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadB
ThreadB ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA
ThreadA ThreadA

emmm果真有点效果,测试了五六次,大部分都是B执行较A更早,不过也有小概率A执行完,其中有两次都是A先执行完再到B的,不过貌似可以用这种方法再提高线程的优先级。

■ 线程之间是如何进行协作的呢?

最经典的例子是生产者/消费者模式,即若干个生产者线程向队列中系欸如数据,若干个消费者线程从队列中消费数据。这里先不贴代码了,等到时有时间再弄个相关博客^^
--Java入坑--关于线程你想知道的都在这_第10张图片

① 生产者/消费者模式的性能问题是什么?

  1. 涉及到同步锁
  2. 涉及到线程阻塞状态和可运行状态之间的切换
  3. 设置到线程上下文的切换

♦ 总结

篇幅较长的一篇关于线程的知识介绍,当然肯定还有很多要补充的,改天有时间再添加上一些内容,相信看完后的你肯定有了不少的收货,不妨将收货用到实践中,多动手,这样记住得比较牢固。

你可能感兴趣的:(面试,经验总结,Java,java,多线程,面试,thread,经验分享)