基础知识
- o.wait() 方法:让正在 o 对象上活动的线程进入等待,并且释放该线程占有的锁。
- o.notiry() 方法:让正在 o 对象上等待的某个线程唤醒。
- o.notifyAll() 方法:让正在 o 对象上等待的所有线程唤醒。
- 注:线程被唤醒以后会从上次等待的位置继续往下执行,也就是会接着执行 wait() 方法之后的代码。
- Conditon 接口中的 await() 对应 Object 中的 wait();
Condition 接口中的 signal() 对应 Object 中的 notify();
Condition 接口中的 signalAll() 对应 Object 中的 notifyAll()。
Synchronized 版本的生产者消费者问题
public class Test7 {
public static void main(String[] args) {
Data data = new Data();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.produce();
}
}, "A").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.consume();
}
}, "B").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.produce();
}
}, "C").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.consume();
}
}, "D").start();
}
}
class Data{
int num = 0;
// 步骤:判断等待、业务、通知唤醒
public synchronized void produce(){
// 线程被唤醒之后是会从上次等待的位置继续往下执行,也就是会接着执行 wait() 方法之后的代码
// 所以假设 A、C 线程同时被唤醒以后,如果是 if,那么不会再次进行判断,A、C线程下一条要执行的语句都是下面的 num++,所以会出现 2 的情况。而如果是 while,那么会再次执行判断,此时只能有一个线程执行到 num ++ 语句
while(num != 0){ // 不能是 if
try {
this.wait(); // 当前线程进入等待状态,并且释放 data 对象的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + " -> " + num);
this.notifyAll();
}
// 步骤:判断等待、业务、通知唤醒
public synchronized void consume(){
while(num == 0){ // 不能是 if
try {
this.wait(); // 当前线程进入等待状态,并且释放 data 对象的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + " -> " + num);
this.notifyAll();
}
}
Lock 版本的生产者消费者问题
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test8 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.produce();
}
}, "A").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.consume();
}
}, "B").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.produce();
}
}, "C").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.consume();
}
}, "D").start();
}
}
class Data2{
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
int num = 0;
// 步骤:判断等待、业务、通知唤醒
public void produce(){
lock.lock();
try {
while(num != 0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + " -> " + num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 步骤:判断等待、业务、通知唤醒
public void consume(){
lock.lock();
try {
while(num == 0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + " -> " + num);
condition.signalAll();
} catch(Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 上面两种虽然可以实现生产者消费者问题,但是线程执行的顺序是不固定的,如果我们要想某个线程执行完之后指定另一个线程执行,则需要下面的方法。
使用 Condition 来精准地唤醒某个线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// Condition 可以用来唤醒指定的线程
// 现在有三个线程 A、B、C,现在要 A 线程执行完了之后去通知 B 线程执行,B 线程执行完了之后去通知 C 线程执行,C 线程执行完了之后去通知 A 线程执行
public class Test9 {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.printA();
}
}, "A").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.printB();
}
}, "B").start();
new Thread(() ->{
for(int i = 0; i < 10; i++){
data.printC();
}
}, "C").start();
}
}
class Data3{
Lock lock = new ReentrantLock();
// 通过监视器判断我要唤醒的是哪一个线程
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
int num = 1; // num 为 1 时让线程 A 执行,num 为 2 时让线程 B 执行,num 为 3 时让线程 C 执行
// 步骤:判断等待、业务、通知唤醒
public void printA(){
lock.lock();
try {
while(num != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + " 线程执行了");
num = 2;
condition2.signal(); // 唤醒线程 B
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 步骤:判断等待、业务、通知唤醒
public void printB(){
lock.lock();
try {
while(num != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + " 线程执行了");
num = 3;
condition3.signal(); // 唤醒线程 C
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 步骤:判断等待、业务、通知唤醒
public void printC(){
lock.lock();
try {
while(num != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + " 线程执行了");
num = 1;
condition1.signal(); // 唤醒线程 A
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
补充 1:synchronized 的使用方法
- 修饰代码块。指定一个线程共享的对象,给其加锁
synchronized(线程共享对象){
同步代码块
}
- 修饰实例方法,在实例方法上使用 synchronized。表示对当前实例对象 this 加锁,并且同步代码块是整个方法体。
public synchronized void f(){}
- 修饰静态方法,在静态方法上使用 synchronized。表示对当前类的 Class 对象加锁。多个线程执行同一个类中被 synchronized 修饰的静态方法时有且只有一个线程能够获取当前类的 Class 对象锁并执行方法,其它线程等待(注:一个类的类锁永远只有一把)
public synchronized static void f(){}
补充 2:Lock 的使用方法
Lock lock = new RenntrantLock();
lock.lock(); // 加锁
try{
同步代码块
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}