Java 线程通信之线程安全问题

什么是线程?

线程其实就是程序的执行单元,负责自己的运行模块,就比如王者荣耀里面的小兵都是一个个的线程。

线程安全案例1——单例模式

懒汉式单例,代码如下:
在这里插入图片描述
这是个最基本的懒汉式单例,现在假设开启两个线程t1t2t1线程执行完了第10行代码 ,恰好给t1线程的时间片用完,没有cpu的执行权了,但是还没有来得及执行第11行代码。

此时同时t2线程也过来执行第10、11行代码,当执行完了第11行代码时,假设刚t2的时间片用完了,释放执行权,t1线程争夺到了执行权,又会开始执行第11代码,又new了一个新对象,分析到这里我们的单例设计模式已经出现问题了。产生了多个对象。

那么怎么解决多线程带来的安全问题?

同步锁

这种由多线程带来的线程安全问题,大家第一时间都会想到用同步锁来解决线程之间对于共享资源的竞争问题,常用的两种锁如下:

synchronized 锁

这个锁可以用于同步代码块,方法上,但是要注意是锁的对象是谁。代码改进如下所示:
在这里插入图片描述
但是在这个方法上面加锁的话,我们没有办法对其进行性能优化,所以我们可以改成同步代码块的方式,对其进行性能优化,代码如下所示:
在这里插入图片描述
改成了同步代码块,我们分析下,t1t2线程过来都要加锁进行判断,t1加锁成功,t2去尝试加锁其实实在做无用功,浪费cpu的资源,那么怎么优化呢?我们可以在外面再加一层判断,代码如下所示:
在这里插入图片描述
这样我们就可以提高懒汉式单例的性能。

lock 锁

使用Lock锁住第15,16行代码,保证两行代码的原子性即可。
在这里插入图片描述

线程安全案例2——卖车票

这里我们模拟3个窗口(其实是3个线程)一起卖10张车票的场景。

10张车票代码如下:
在这里插入图片描述
我们这里每执行一次就减1,代表每卖出一张票就会少一张票。

然后模拟窗口代码如下:
在这里插入图片描述
卖票的任务就交给多线程去跑,所以这里需要实现Runnable接口,这里我们为了看效果,所以使用while(true)做测试。

然后开启三个窗口代码如下:
在这里插入图片描述
然后给三个线程起一个别名方便看效果。

运行结果如下所示:
在这里插入图片描述
从运行结果分析出,非常不符合现实的问题,就是窗口1卖了0车票,窗口3卖了-1车票,很离谱,不符合现实,不可能出现0、-1这种车票。从这里我们就引发出来线程安全问题。这里可以使用Locksynchronized锁。

分析一下问题,我们回头看下车票资源,是线程之间共享的资源,代码如下:
在这里插入图片描述
我们假设没有加锁之前,我们先假设count=1

然后t1线程执行完第11行代码,然后暂停,此时是没有执行count--操作的哦,此时count=1
然后t2线程过来了,也执行完了第11行代码,然后暂停,也没有执行count--,此时count=1
然后t3过来同样的,也执行完了第11行代码,然后暂停,也没有执行count--,此时count=1

现在三个线程都已经执行完了这个第11行的判断条件,都进来了,停在了第13行代码

假设t1醒来,执行完了15行代码,输出结果为1,此时因为执行了count--操作,所以内存中count=0
然后t2醒来,此时主内存中的count等于0,t2输出结果为 0,同时执行count--操作,则count=-1
最后t3醒来,此时主内存中的count等于0,执行完了15行代码,t3输出结果为-1,内存中count=-2

最终分析出问题就是出在了第11行代码和第16行代码没有保证原子性,我们可以将sale()方法加同步锁synchronized,如下所示:
在这里插入图片描述
这样就能保证正常卖票了。
同时我们也可以使用Lock进行加锁,代码如下所示:
在这里插入图片描述
最终运行结果如下所示:在这里插入图片描述

线程安全案例——生产者消费模式

我们假设一个场景:车工厂流水线上面,我们每上游每生产一个另加,下游就可以使用,也就是上游上产零件,下游消费,模拟代码如下所示:
车零件代码如下:
在这里插入图片描述
生产者代码如下:
在这里插入图片描述
消费者代码如下:
在这里插入图片描述
主方法如下:
在这里插入图片描述
输出结果如下所示:
在这里插入图片描述
我们可以从运行结果看出,生产者生产了很多,但是没有被消费,或者一个车零件被多次消费,这是有问题的,就拿这个多次重复消费的例子我们可以看出,消费者没有等待生产者生产一直乱消费,毫无章法,所以我么怎么解决这生产者和消费者之间的协作性呢?这里我们就要提到JDK自带的线程方法,wait(),notify(),notifyAll() 方法,该方法的使用必须放在同步代码块里。原因是怕Lost-Wake-Up,具体可以参考这边文章:wait为什么要在同步块中使用

经过改造之后的代码如下所示:
在这里插入图片描述
在上图中我们会发现,我们加了一个flag标识,其实线程之间的同行就是靠信号量通信,也就是借助一个中间状态来控制。线程之间通过控制这个变量实现线程通信。

运行结果如下所示:非常的和谐。
在这里插入图片描述
上述我们是只有一个生产者,一个消费者的情况,所以notify()方法只会唤醒在wait池中的仅此的一个线程,两个线程(逻辑概念分成一个生产者一个消费者,实际就是两个线程)。你是消费者,那wait池中必定是生产者呗。你是生产者,那wait池中必定是消费者呗。没啥问题,

但是如果出现多消费者多生产者(两个消费者,两个生产者)就会存在问题了,因为notify()这个方法并不知道你唤醒的是消费者还是生产者,只是唤醒其中的一个。加入你是生产者,然后你又把其中的一个在wait池中的生产者唤醒,那生产者肯定多生产了物品,就不和谐了。

现在我们准备搞两个生产者p1,p2,两个消费者c1,c2,四个线程,代码如下所示:
在这里插入图片描述
运行结果如下:
在这里插入图片描述
从上图可以看出,消费者多消费了。分析一下问题在哪里,我们看下面这张图:
在这里插入图片描述
假设现在我们两个消费者线程c1,c2开始执行第20、21、22行代码,因为c1开始执行第20行代码,进行上锁,然后执行到了第21行,flag=false,所以开始执行第22行代码,c1直接释放了锁,c2就开始执行第20、21、22行代码,这个时候wait池中就有c1、c2在等着被notify()方法被唤醒。

假设现在p1线程开始执行,number此时等于1,然后执行完notify()方法,此时唤醒了c1、c2中的其中一个线程,假设唤醒了c1线程,c1线程因为刚刚已经执行到了第22行,所以被唤醒就会往下继续执行,然后输出number=1,然后将信号量flag赋值为false,调用notify()方法唤醒其他线程,假设这个c1刚好就唤醒了c2线程,然后c2就会开始继续往下执行,并没有重新判断信号量的状态,所以输出nunber还是等于1,这就发生了多消费的问题,所以我们可以改造一下,让c2醒来之后重新去判断一下信号量状态,代码改造如下所示:
在这里插入图片描述
从上图我们看到使用了while循环进行信号量的判断,运行结果如下所示:出现了死锁问题。
在这里插入图片描述
为什么会出现死锁的问题呢? 我们可以分析一下以下代码:
在这里插入图片描述
假设p1抢到了cpu执行权,开始执行,flag=truenotify()调用直接丢失通知(假设wait中暂时还没有线程),执行完之后p1释放了锁,但是假设p1又抢夺到cpu执行权,此时flag=true,p1直接进入到wait等待唤醒池中,

接着假设p2抢到了cpu执行权,判断flag=true,p2也进入了wait等待线程池中,此时wait中有p1、p2等待着被唤醒。

假设c1现在抢到了cpu执行权,因为生产者将flag赋值成true,所以不会进入循环,将flag赋值false(flag=false),然后执行完notify()方法,唤醒了wait池中p1、p2的其中一个,假设唤醒的是p1,现在具备抢夺cpu执行的线程有c1,p1,c2。

假设c1又抢到cpu执行权,此时flag=false,c1直接进入wait线程等待池中,此时wait中有c1、p2两个阻塞线程,具备抢夺cpu执行权利的还有p1、c2,假设c2抢到了cpu执行权,发现此时flag=false,直接进入wait线程等待池中,此时wait中有c1、c2、p2,具备抢夺cpu执行权的就只有p1,此时flag=false,p1执行完之后,flag=true,此时wait中有c1、c2、p2,假设p1唤醒的是p2,哈哈,倒大霉了,此时p1、p2存活,然后p1执行时,此时flag=true,直接进入wait等待,p2执行时发现flag=true,也直接进入wait中,现在wait中有p1、p2、c1、c2四个线程,直接死锁。

注意:不管生产者还是消费者,如果其中一个线程没有释放该方法的锁,你是不可能执行该方法的,只能给我排着队等我执行完再说,这就是synchornized的作用, 该锁的监视器和对象进行绑定

wait() 方法:必须在同步代码 synchornized 中执行,当前线程要想执行必须先拿到锁才能执行,执行wait之后它会释放锁,使得其他线程有机会执行notifynotifyAll,来唤醒它在这个同步代码中能够具备抢夺cpu执行的能力。
 
在这里插入图片描述

造成这个死锁最终原因是因为notify这个方法唤醒目标不明确,你不知道是唤醒生产者还是消费者,所以导致死锁,而且java中也没有明确指出唤醒具体目标的方法,但是提供了唤醒全部线程的方法:notifyAll(),既然唤醒一个不能保证目标在不在范围内,那么直接把你所有线程唤醒,肯定就有需要要唤醒的线程。

改造之后的代码如下所示:
在这里插入图片描述
但是使用notifyAll()方法唤醒全部的线程,性能上不太友好,那么怎么优化呢? 我们可以先来把上述代码改造成Lock锁,然后再在Lock锁上进行优化。

使用 Lock 锁演示生产者消费者

代码如下所示:
在这里插入图片描述

运行结果是一模一样的如下所示:
在这里插入图片描述
对于condition.signalAll()还是没有解决掉这个一次性把所有线程唤醒,然后导致又要去while循环,造成性能的开销问题,不过Lock锁提供了一个类Condition,只需要唤醒一个线程即可,就不用唤醒全部的线程。性能上开销还是比较小的。

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
 
Condition 实例实质上被绑定到一个锁上。我们可以将多个状态拆分到Condition上面

join() 和 yield() 方法区别

t1.join() 方法是表示t1线程需要加入进执行,释放正在执行的线程的执行权,当前线程就会处于阻塞状态,知道t1能够抢到执行权,然后执行完结果,当前阻塞的线程才会继续执行。

package com.xxl.job.admin.mytest;

class Task0 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
class Task1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"----->"+i);
        }
    }
}
public class JoinYieldDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread0 = new Thread(new Task0());
        Thread thread1 = new Thread(new Task1());
        thread0.start();

        thread0.join();
        thread1.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"===>"+i);
        }
        System.out.println("main over");

    }
}

thread0.join()这行代码表示:当前执行线程是主线程,当主线程遇到这行代码时,主线程会乖乖的让出执行权,但是不是说直接给thread0线程,只是让出来了,并没有明确指定给谁,所以thread0还需要抢夺执行权,然后等到thread0线程执行完代码,此时主线程就会恢复可就绪状态,拿到了执行权就会向下执行。

运行结果如下所示:

Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
Thread-0:20
Thread-0:21
Thread-0:22
Thread-0:23
Thread-0:24
Thread-0:25
Thread-0:26
Thread-0:27
Thread-0:28
Thread-0:29
Thread-0:30
Thread-0:31
Thread-0:32
Thread-0:33
Thread-0:34
Thread-0:35
Thread-0:36
Thread-0:37
Thread-0:38
Thread-0:39
Thread-0:40
Thread-0:41
Thread-0:42
Thread-0:43
Thread-0:44
Thread-0:45
Thread-0:46
Thread-0:47
Thread-0:48
Thread-0:49
main===>0
main===>1
Thread-1----->0
Thread-1----->1
Thread-1----->2
Thread-1----->3
Thread-1----->4
main===>2
main===>3
main===>4
main===>5
Thread-1----->5
Thread-1----->6
Thread-1----->7
Thread-1----->8
Thread-1----->9
Thread-1----->10
Thread-1----->11
Thread-1----->12
Thread-1----->13
Thread-1----->14
Thread-1----->15
Thread-1----->16
Thread-1----->17
main===>6
main===>7
main===>8
main===>9
main===>10
main===>11
main===>12
main===>13
main===>14
main===>15
main===>16
main===>17
main===>18
main===>19
main===>20
main===>21
main===>22
main===>23
main===>24
main===>25
main===>26
main===>27
Thread-1----->18
main===>28
Thread-1----->19
Thread-1----->20
Thread-1----->21
main===>29
main===>30
main===>31
main===>32
main===>33
main===>34
main===>35
main===>36
main===>37
main===>38
main===>39
main===>40
main===>41
main===>42
main===>43
main===>44
main===>45
main===>46
main===>47
main===>48
main===>49
main over
Thread-1----->22
Thread-1----->23
Thread-1----->24
Thread-1----->25
Thread-1----->26
Thread-1----->27
Thread-1----->28
Thread-1----->29
Thread-1----->30
Thread-1----->31
Thread-1----->32
Thread-1----->33
Thread-1----->34
Thread-1----->35
Thread-1----->36
Thread-1----->37
Thread-1----->38
Thread-1----->39
Thread-1----->40
Thread-1----->41
Thread-1----->42
Thread-1----->43
Thread-1----->44
Thread-1----->45
Thread-1----->46
Thread-1----->47
Thread-1----->48
Thread-1----->49

Process finished with exit code 0

从图上可以看到等thread0线程执行完之后主线程才开始执行。

我们把代码稍微换下顺序,代码如下所示:

public class JoinYieldDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread thread0 = new Thread(new Task0());
        Thread thread1 = new Thread(new Task1());
        thread0.start();
        thread1.start();

        thread0.join();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"===>"+i);
        }
        System.out.println("main over");
    }
}

我们可以看出,主线程已经执行完了两个线程的启动,所以这里thread0thread1线程之间会存在竞争关系,当执行到thread0.join()方法时,主线程让出了执行权,等待thread0线程执行完,才会重新开始执行主线程,但是现在会有thread1线程和主线程之间的竞争输出,运行结果如下所示:

/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=59209:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_333.jdk/Contents/Home/lib/tools.jar:/Users/gongweiming/IdeaProjects/xxl-job/xxl-job-admin/target/classes:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.6.7/spring-boot-starter-web-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter/2.6.7/spring-boot-starter-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot/2.6.7/spring-boot-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.6.7/spring-boot-autoconfigure-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.6.7/spring-boot-starter-logging-2.6.7.jar:/Users/gongweiming/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/Users/gongweiming/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/Users/gongweiming/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/Users/gongweiming/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/Users/gongweiming/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/Users/gongweiming/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/gongweiming/.m2/repository/org/yaml/snakeyaml/1.29/snakeyaml-1.29.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.6.7/spring-boot-starter-json-2.6.7.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.2.1/jackson-databind-2.13.2.1.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.2/jackson-annotations-2.13.2.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.2/jackson-core-2.13.2.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.2/jackson-datatype-jdk8-2.13.2.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.2/jackson-datatype-jsr310-2.13.2.jar:/Users/gongweiming/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.2/jackson-module-parameter-names-2.13.2.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.6.7/spring-boot-starter-tomcat-2.6.7.jar:/Users/gongweiming/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.62/tomcat-embed-core-9.0.62.jar:/Users/gongweiming/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.62/tomcat-embed-el-9.0.62.jar:/Users/gongweiming/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.62/tomcat-embed-websocket-9.0.62.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-web/5.3.19/spring-web-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-beans/5.3.19/spring-beans-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-webmvc/5.3.19/spring-webmvc-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-aop/5.3.19/spring-aop-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-context/5.3.19/spring-context-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-expression/5.3.19/spring-expression-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-core/5.3.19/spring-core-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-jcl/5.3.19/spring-jcl-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-freemarker/2.6.7/spring-boot-starter-freemarker-2.6.7.jar:/Users/gongweiming/.m2/repository/org/freemarker/freemarker/2.3.31/freemarker-2.3.31.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-context-support/5.3.19/spring-context-support-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-mail/2.6.7/spring-boot-starter-mail-2.6.7.jar:/Users/gongweiming/.m2/repository/com/sun/mail/jakarta.mail/1.6.7/jakarta.mail-1.6.7.jar:/Users/gongweiming/.m2/repository/com/sun/activation/jakarta.activation/1.2.2/jakarta.activation-1.2.2.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-actuator/2.6.7/spring-boot-starter-actuator-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-actuator-autoconfigure/2.6.7/spring-boot-actuator-autoconfigure-2.6.7.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-actuator/2.6.7/spring-boot-actuator-2.6.7.jar:/Users/gongweiming/.m2/repository/io/micrometer/micrometer-core/1.8.5/micrometer-core-1.8.5.jar:/Users/gongweiming/.m2/repository/org/hdrhistogram/HdrHistogram/2.1.12/HdrHistogram-2.1.12.jar:/Users/gongweiming/.m2/repository/org/latencyutils/LatencyUtils/2.0.3/LatencyUtils-2.0.3.jar:/Users/gongweiming/.m2/repository/org/mybatis/spring/boot/mybatis-spring-boot-starter/2.2.2/mybatis-spring-boot-starter-2.2.2.jar:/Users/gongweiming/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/2.6.7/spring-boot-starter-jdbc-2.6.7.jar:/Users/gongweiming/.m2/repository/com/zaxxer/HikariCP/4.0.3/HikariCP-4.0.3.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-jdbc/5.3.19/spring-jdbc-5.3.19.jar:/Users/gongweiming/.m2/repository/org/springframework/spring-tx/5.3.19/spring-tx-5.3.19.jar:/Users/gongweiming/.m2/repository/org/mybatis/spring/boot/mybatis-spring-boot-autoconfigure/2.2.2/mybatis-spring-boot-autoconfigure-2.2.2.jar:/Users/gongweiming/.m2/repository/org/mybatis/mybatis/3.5.9/mybatis-3.5.9.jar:/Users/gongweiming/.m2/repository/org/mybatis/mybatis-spring/2.0.7/mybatis-spring-2.0.7.jar:/Users/gongweiming/.m2/repository/mysql/mysql-connector-java/8.0.29/mysql-connector-java-8.0.29.jar:/Users/gongweiming/IdeaProjects/xxl-job/xxl-job-core/target/classes:/Users/gongweiming/.m2/repository/io/netty/netty-all/4.1.76.Final/netty-all-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-buffer/4.1.76.Final/netty-buffer-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec/4.1.76.Final/netty-codec-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-dns/4.1.76.Final/netty-codec-dns-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-haproxy/4.1.76.Final/netty-codec-haproxy-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-http/4.1.76.Final/netty-codec-http-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-http2/4.1.76.Final/netty-codec-http2-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-memcache/4.1.76.Final/netty-codec-memcache-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-mqtt/4.1.76.Final/netty-codec-mqtt-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-redis/4.1.76.Final/netty-codec-redis-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-smtp/4.1.76.Final/netty-codec-smtp-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-socks/4.1.76.Final/netty-codec-socks-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-stomp/4.1.76.Final/netty-codec-stomp-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-codec-xml/4.1.76.Final/netty-codec-xml-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-common/4.1.76.Final/netty-common-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-handler/4.1.76.Final/netty-handler-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-handler-proxy/4.1.76.Final/netty-handler-proxy-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-resolver/4.1.76.Final/netty-resolver-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-resolver-dns/4.1.76.Final/netty-resolver-dns-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport/4.1.76.Final/netty-transport-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-rxtx/4.1.76.Final/netty-transport-rxtx-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-sctp/4.1.76.Final/netty-transport-sctp-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-udt/4.1.76.Final/netty-transport-udt-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-classes-epoll/4.1.76.Final/netty-transport-classes-epoll-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-native-unix-common/4.1.76.Final/netty-transport-native-unix-common-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-classes-kqueue/4.1.76.Final/netty-transport-classes-kqueue-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-resolver-dns-classes-macos/4.1.76.Final/netty-resolver-dns-classes-macos-4.1.76.Final.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-native-epoll/4.1.76.Final/netty-transport-native-epoll-4.1.76.Final-linux-x86_64.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-native-epoll/4.1.76.Final/netty-transport-native-epoll-4.1.76.Final-linux-aarch_64.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-native-kqueue/4.1.76.Final/netty-transport-native-kqueue-4.1.76.Final-osx-x86_64.jar:/Users/gongweiming/.m2/repository/io/netty/netty-transport-native-kqueue/4.1.76.Final/netty-transport-native-kqueue-4.1.76.Final-osx-aarch_64.jar:/Users/gongweiming/.m2/repository/io/netty/netty-resolver-dns-native-macos/4.1.76.Final/netty-resolver-dns-native-macos-4.1.76.Final-osx-x86_64.jar:/Users/gongweiming/.m2/repository/io/netty/netty-resolver-dns-native-macos/4.1.76.Final/netty-resolver-dns-native-macos-4.1.76.Final-osx-aarch_64.jar:/Users/gongweiming/.m2/repository/com/google/code/gson/gson/2.8.9/gson-2.8.9.jar:/Users/gongweiming/.m2/repository/org/codehaus/groovy/groovy/3.0.10/groovy-3.0.10.jar:/Users/gongweiming/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar com.xxl.job.admin.mytest.JoinYieldDemo
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1----->0
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
Thread-1----->1
Thread-1----->2
Thread-1----->3
Thread-1----->4
Thread-1----->5
Thread-1----->6
Thread-1----->7
Thread-1----->8
Thread-1----->9
Thread-1----->10
Thread-1----->11
Thread-1----->12
Thread-1----->13
Thread-1----->14
Thread-0:20
Thread-0:21
Thread-0:22
Thread-0:23
Thread-0:24
Thread-1----->15
Thread-1----->16
Thread-1----->17
Thread-0:25
Thread-1----->18
Thread-1----->19
Thread-1----->20
Thread-0:26
Thread-0:27
Thread-0:28
Thread-0:29
Thread-0:30
Thread-0:31
Thread-0:32
Thread-0:33
Thread-0:34
Thread-0:35
Thread-0:36
Thread-0:37
Thread-0:38
Thread-1----->21
Thread-0:39
Thread-1----->22
Thread-0:40
Thread-1----->23
Thread-1----->24
Thread-1----->25
Thread-1----->26
Thread-1----->27
Thread-1----->28
Thread-1----->29
Thread-1----->30
Thread-1----->31
Thread-1----->32
Thread-1----->33
Thread-1----->34
Thread-1----->35
Thread-1----->36
Thread-0:41
Thread-0:42
Thread-0:43
Thread-0:44
Thread-0:45
Thread-0:46
Thread-0:47
Thread-0:48
Thread-0:49
Thread-1----->37
Thread-1----->38
Thread-1----->39
Thread-1----->40
main===>0
main===>1
main===>2
Thread-1----->41
Thread-1----->42
Thread-1----->43
Thread-1----->44
Thread-1----->45
Thread-1----->46
Thread-1----->47
Thread-1----->48
Thread-1----->49
Thread-1----->50
Thread-1----->51
Thread-1----->52
Thread-1----->53
Thread-1----->54
Thread-1----->55
Thread-1----->56
Thread-1----->57
Thread-1----->58
Thread-1----->59
Thread-1----->60
Thread-1----->61
Thread-1----->62
Thread-1----->63
main===>3
Thread-1----->64
main===>4
main===>5
main===>6
main===>7
main===>8
Thread-1----->65
Thread-1----->66
main===>9
main===>10
main===>11
main===>12
main===>13
Thread-1----->67
Thread-1----->68
Thread-1----->69
Thread-1----->70
Thread-1----->71
Thread-1----->72
Thread-1----->73
Thread-1----->74
Thread-1----->75
Thread-1----->76
Thread-1----->77
Thread-1----->78
Thread-1----->79
Thread-1----->80
Thread-1----->81
Thread-1----->82
Thread-1----->83
Thread-1----->84
Thread-1----->85
Thread-1----->86
Thread-1----->87
Thread-1----->88
Thread-1----->89
Thread-1----->90
Thread-1----->91
Thread-1----->92
Thread-1----->93
Thread-1----->94
Thread-1----->95
Thread-1----->96
Thread-1----->97
Thread-1----->98
Thread-1----->99
main===>14
main===>15
main===>16
main===>17
main===>18
main===>19
main===>20
main===>21
main===>22
main===>23
main===>24
main===>25
main===>26
main===>27
main===>28
main===>29
main===>30
main===>31
main===>32
main===>33
main===>34
main===>35
main===>36
main===>37
main===>38
main===>39
main===>40
main===>41
main===>42
main===>43
main===>44
main===>45
main===>46
main===>47
main===>48
main===>49
main over
Process finished with exit code 0

从结果可以看出,thread0thread1之间存在着竞争输出,但是一定要等到thread0输出完了,主线程就会启动,然后就有了主线程thread1之间的竞争输出了。

Thread.yield() 方法是暂停一会儿当前执行的线程,休息一会儿。并且该方法是一个静态方法。这个方法感觉就是一个让线程和谐的作用,不要出现那种一边倒的现象。

最后举个使用例子如下所示:

package com.xxl.job.admin.mytest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyBlockLinkedQueue {
    private int[] arr = new int[100];
    private int count = 0;
    private int putIndex = 0;
    private int takeIndex = 0;
    private Lock lock = new ReentrantLock();
    Condition notFull = lock.newCondition();
    Condition notEmpty = lock.newCondition();

    public void put(int a){
        lock.lock();
        while (count == arr.length) {
            //别动
            try {
                notFull.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        arr[putIndex] = a;
        System.out.println("存放元素角标="+putIndex+"元素是="+arr[putIndex]);
        if (++putIndex== arr.length) {
            putIndex = 0;//复位
        }
        ++count;
        notEmpty.signal();
        lock.unlock();
    }

    public void take() {
        lock.lock();
        while (count == 0) {
            //别动
            try {
                notEmpty.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        int e = arr[takeIndex];
        System.out.println("take = " + e);
        if (++takeIndex == arr.length) {
            takeIndex = 0;
        }
        --count;
        notFull.signal();
        lock.unlock();
    }

}

class Pro implements Runnable {
    private MyBlockLinkedQueue queue;

    public Pro(MyBlockLinkedQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            queue.put(i);
        }
    }
}

class Consu implements Runnable {
    private MyBlockLinkedQueue queue;

    public Consu(MyBlockLinkedQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            queue.take();
        }
    }
}

public class MyBlockLinkedQueueDemo {

    public static void main(String[] args) {
        MyBlockLinkedQueue queue = new MyBlockLinkedQueue();
        new Thread(new Pro(queue)).start();
        new Thread(new Consu(queue)).start();
    }
}

输出结果如下所示:
在这里插入图片描述
推荐文章:
wait为什么要在同步块中使用?
基于Java 生产者消费者模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值