线程交替执行经典问题(生产者和消费者问题)
让两个线程交替打印出1A2B3C4D5E6F?
一、使用LockSupport
如果只有两个线程,使用LockSupport实现比较简单快捷,但是当有多个线程的时候,LockSupport.unpark(指定线程),我们不知道那个指定线程需要唤醒。所以不适用多个线程的情况。
package com.itheima.security.springboot.juc;
import java.util.concurrent.locks.LockSupport;
/**
* @Title: 模拟线程交替执行经典问题
* @Description: TODO
* @Params:
* @return
* @throws
* @Author: Vector
* @DateTime:
*/
public class T04_LockSupport {
static Thread t1, t2;
public static void main(String[] args) {
char[] arrayA = "1234567".toCharArray();
char[] arrayB = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
for (char temp : arrayA) {
System.out.println(temp);
LockSupport.unpark(t2); //唤醒t2线程
LockSupport.park();//t1线程阻塞 todo 即让当前线程进入阻塞状态
}
}, "t1");
t2 = new Thread(() -> {
for (char temp : arrayB) {
LockSupport.park();//t2线程阻塞 todo 即让当前线程进入阻塞状态
System.out.println(temp);
LockSupport.unpark(t1); //唤醒t1线程
}
}, "t2");
t1.start();
t2.start();
}
}
二、使用synchronized的wait 和notify
前提条件:只有已经获取锁的线程,才可以调用锁的wait()、notify()方法,否则会抛出异常IllegalMonitorStateException。
1.wait()的作用是让当前线程进入等待(阻塞)状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
线程执行 Object.wait()方法时,会将当前线程加入到 _waitSet 这个双向链表中,然后再运行ObjectMonitor::exit() 方法来释放锁。
wait()的正确使用姿势 while(条件不满足) { wait(); }
当某个线程获取到锁后,发现当前还不满足执行的条件,就可以调用对象锁的wait方法,进入等待状态。直到某个时刻,外在条件满足了,就可以由其他线程通过调用notify()或者notifyAll()方法,来唤醒此线程。
2.notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
当线程执行 Object.notify():方法时,从 _waitSet 头部拿线程节点,然后根据策略(QMode指定)决定将线程节点放在哪里,包括_cxq 或 _EntryList 的头部或者尾部,然后唤醒队列中的线程。
notify()和notifyAll()分别何时使用
满足以下三个条件时,可以使用notify(),其余情况尽量使用notifyAll():
-
- 所有等待线程拥有相同的等待条件;
- 所有等待线程被唤醒后,执行相同的操作;
- 只需要唤醒一个线程。
要知道notify()是随机唤醒一个,而notifyAll()则是唤醒全部。如果是要唤醒特定的线程,最好用notifyAll() + while(条件不满足)来保证指定线程会被唤醒。
3.wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。在指定时间内,如果没有其他线程唤醒自己,则主动唤醒自己。
package com.itheima.security.springboot.juc;
import java.util.concurrent.CountDownLatch;
/**
* @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
* @Description: TODO
* @Params:
* @return
* @throws
* @Author: Vector
* @DateTime:
*/
public class T05_SyhcWaitNotify {
static Thread t1, t2;
private static Object o = new Object();
private static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
char[] arrayA = "1234567".toCharArray();
char[] arrayB = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
synchronized (o) {
for (char temp : arrayA) {
System.out.println(temp);
latch.countDown();
try {
o.notify(); //唤醒唤醒单个线程
o.wait();//todo 让当前线程处于“等待(阻塞)状态,也会让当前线程释放它所持有的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1");
t2 = new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
for (char temp : arrayB) {
System.out.println(temp);
try {
o.notify(); //唤醒唤醒单个线程
o.wait();//todo 让当前线程处于“等待(阻塞)状态,也会让当前线程释放它所持有的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2");
t1.start();
t2.start();
}
}
三、使用ReentrantLock的wait 和notify
package com.itheima.security.springboot.juc;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
* @Description: TODO
* @Params:
* @return
* @throws
* @Author: Vector
* @DateTime:
*/
public class T05_ReentrantLockWaitSignal {
public static void main(String[] args) {
char[] arrayA = "1234567".toCharArray();
char[] arrayB = "ABCDEFG".toCharArray();
ReentrantLock lock = new ReentrantLock();
//可以把Condition理解为队列
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
lock.lock();
try {
for (char temp : arrayA) {
System.out.println(temp);
//释放门闩
latch.countDown();
condition2.signal();
condition1.await();
}
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
for (char temp : arrayB) {
System.out.println(temp);
condition1.signal();
condition2.await();
}
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
四、巧妙使用TransferQueue
package com.itheima.security.springboot.juc;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
/**
* @Title: 模拟线程交替执行经典问题, 让两个线程交替打印出1A2B3C4D5E6F
* @Description: TODO
* @Params:
* @return
* @throws
* @Author: Vector
* @DateTime:
*/
public class T05_TransferQueue {
public static void main(String[] args) {
char[] arrayA = "1234567".toCharArray();
char[] arrayB = "ABCDEFG".toCharArray();
TransferQueue<Character> queue = new LinkedTransferQueue<>();
new Thread(() -> {
try {
for (char temp : arrayA) {
queue.transfer(temp);
System.out.println(queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
try {
for (char temp : arrayB) {
System.out.println(queue.take());
queue.transfer(temp);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
}