线程通信
- 虽然无法准确控制线程轮换执行,但我们还是可以通过一些机制来保证线程协调执行。比如让AB交替执行,而不能连续执行A或B。
使用synchronized关键字来保证线程同步
为实现这种功能,可以借助Object类的wait(),notify(),notifyAll(),这三个方法必须由同步监视器来调用(同步代码块是括号里的对象,同步方法是调用者)
使用Lock对象来保证同步
- 如果程序不使用synchronized关键字来保证线程同步,而是直接用Lock对象来保证同步。那么就没有同步监视器了,也就不能用wait,notify
notifyall方法了(三个方法都必须由同步监视器调用),因此,java提供了一个Condition类来保持协调,Condition可以让得到Lock对象的线程释放Lock对象,也可以唤醒其它正处于等待的线程。 - Lock替代了同步代码块或同步方法,Condition替代了同步监视器的功能
- Condition提供了await()让得到Lock对象的线程释放Lock对象,提供了signal(),signalAll()唤醒其它正处于等待此Lock对象的线程。
- 通过调用Lock对象的newConditiom()方法即可获得lock实例的condition实例
案例
启动两个线程(不包括main线程)交替打印出0~10的奇偶数
方案一
输出的时候判断一下当前需要输出的数是不是自己要负责打印的值,如果是就输出,不是就直接释放锁。
避开了线程交替获取锁的需求,而是两个线程随机获取锁,在输出的时候判断如果不是自己的需求则选择不输出而已,明显没有答到面试官想考察的考点上。而且效率较低,如果同一个线程一直抢到锁,而另一个线程一直没有拿到,就会导致线程做很多无谓的空转。
package com.areio.partsixteen;
public class Test1 {
private int count = 0;
private final Object lock = new Object();
public void turning() {
Thread even = new Thread(() -> {
while (count < 10) {
System.out.println("偶数在锁的前面");
synchronized (lock) {
System.out.println("偶数拿到锁");
// 只处理偶数
if ((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
}
System.out.println("偶数放开锁,代码块正常结束");
}
}
}, "偶数");
Thread odd = new Thread(() -> {
while (count < 10) {
System.out.println("ji在锁的前面");
synchronized (lock) {
System.out.println("ji数拿到锁");
// 只处理奇数
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
}
System.out.println("ji数放开锁,代码块正常结束");
}
}
}, "奇数");
even.start();
odd.start();
}
public static void main(String[] args) {
Test1 test1=new Test1();
test1.turning();
}
}
ji在锁的前面
偶数在锁的前面
ji数拿到锁
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 0
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 1
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 2
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 3
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 4
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 5
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 6
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 7
ji数放开锁,代码块正常结束
ji在锁的前面
偶数拿到锁
偶数: 8
偶数放开锁,代码块正常结束
偶数在锁的前面
ji数拿到锁
奇数: 9
ji数放开锁,代码块正常结束
偶数拿到锁
偶数: 10
偶数放开锁,代码块正常结束
方案二(交替获取锁)
package com.areio.partsixteen;
public class Test2 {
private int count = 0;
private final Object lock = new Object();
private boolean flag=true;//flag=true代表此时是偶数要取,flag=false代表此时是奇数要取。0是偶数要取,故初始值为true
public void turning() {
Thread even = new Thread(() -> {//偶数线程
while (count <= 10) {
System.out.println(Thread.currentThread().getName()+"在锁的前面");
synchronized (lock) {
System.out.println(Thread.currentThread().getName()+"取得锁进入代码块");
try {
// 如果当前是奇数要取,则方法堵塞
if (!flag) {
System.out.println(Thread.currentThread().getName()+"发现当前是奇数要取,则wait释放锁");
lock.wait();
System.out.println(Thread.currentThread().getName()+"重新获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("偶数: " + count++);
flag=false;
//唤醒在等待lock实例的其他所有线程(本程序只有2+1个线程)
System.out.println(Thread.currentThread().getName()+"唤醒其他线程");
lock.notifyAll();
}
}
},"偶数线程--");
Thread odd = new Thread(() -> {//奇数
while (count <= 10) {
System.out.println(Thread.currentThread().getName()+"在锁的前面");
synchronized (lock) {
System.out.println(Thread.currentThread().getName()+"取得锁进入代码块,此时count="+count);
try {
if (flag) {
//wait()抛出check异常
System.out.println(Thread.currentThread().getName()+"发现当前是偶数要取,则wait释放锁");
//wait没被唤醒之前不能做任何操作,只能堵塞在这
lock.wait();
System.out.println(Thread.currentThread().getName()+"重新获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//加个判断或者在while处修改为count<=9;因为可能读了该线程进入while时读取count=10之后cpu被强占了,导致该线程重新拿到cpu时count已经变成了11,
if (count<=10){
System.out.println("奇数: " + count++);
flag=true;
System.out.println(Thread.currentThread().getName()+"唤醒其他线程");
lock.notifyAll();
}
}
}
},"奇数线程--");
even.start();
odd.start();
}
public static void main(String[] args) throws InterruptedException {
Test2 test2=new Test2();
test2.turning();
}
}
奇数线程–在锁的前面
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=0
奇数线程–发现当前是偶数要取,则wait释放锁
偶数线程–取得锁进入代码块
偶数: 0
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–重新获得锁
奇数: 1
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 2
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=3
奇数: 3
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 4
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=5
奇数: 5
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 6
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=7
奇数: 7
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 8
偶数线程–唤醒其他线程
偶数线程–在锁的前面
奇数线程–取得锁进入代码块,此时count=9
奇数: 9
奇数线程–唤醒其他线程
奇数线程–在锁的前面
偶数线程–取得锁进入代码块
偶数: 10
偶数线程–唤醒其他线程
奇数线程–取得锁进入代码块,此时count=11
Process finished with exit code 0