多个线程之间的通信问题
在同步代码块中,锁对象是谁,就用那个对象来调用wait和notify
为什么wait方法和notify方法需要定义在Object?
因为所有的对象都是Object的子类对象,而所欲的对象都可以当做锁对象
jdk1.5版本之前多个线程通信用synchronized和唤醒全部线程notifyAll等逻辑来控制执行顺序问题。
jdk1.5之后就可以用互斥锁。
先展示jdk1.5之前的用法
public class Demo6_Notify {
public static void main(String[] args) {
final Printer p = new Printer(); // jdk1.8不需要加final
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
try {
p.print1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
try {
p.print2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
try {
p.print3();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
// 等待唤醒机制
/*
* 在同步代码块中,锁对象是谁,就用那个对象来调用wait和notify
* 为什么wait方法和notify方法需要定义在Object
* 因为所有的对象都是Object的子类对象,而所欲的对象都可以当做锁对象
*/
/**
*
* @author lcy
* jdk1.5版本之前多个线程通信都是这种办法
* jdk1.5之后就可以用互斥锁
*
*/
class Printer {
private byte[] lock = new byte[0];
private int flag = 1;
public void print1() throws InterruptedException {
synchronized (lock) {
while (flag != 1) {
lock.wait();
}
System.out.print("我");
System.out.print("最");
System.out.print("帅");
System.out.print("\r\n");
flag = 2;
lock.notifyAll();
}
}
public void print2() throws InterruptedException {
synchronized (lock) {
while (flag != 2) {
lock.wait();
}
System.out.print("你");
System.out.print("说");
System.out.print("啥");
System.out.print("\r\n");
flag = 3;
lock.notifyAll();
}
}
public void print3() throws InterruptedException {
synchronized (lock) {
while (flag != 3) {
lock.wait();
}
System.out.print("听");
System.out.print("不");
System.out.print("见");
System.out.print("\r\n");
flag = 1;
lock.notifyAll();
}
}
/*public void print1() throws InterruptedException {
synchronized (lock) {
if (flag != 1) {
lock.wait();
}
Thread.sleep(10);
System.out.print("我");
System.out.print("最");
System.out.print("帅");
System.out.print("\r\n");
flag = 1;
lock.notify(); // 随机唤醒另一条线程
}
}
public void print2() throws InterruptedException {
synchronized (lock) {
if (flag == 1) {
lock.wait();
}
System.out.print("你");
System.out.print("说");
System.out.print("啥");
System.out.print("\r\n");
flag = 2;
lock.notify();
}
}*/
}
运行结果(结果太多,展示一部分):
sleep方法和wait方法的区别
1.sleep方法必须传入参数,参数就是时间,当时间到了,不用唤醒,自动醒来,比如Thread.sleep(1000),就是1s就苏醒。
wait方法不是必须传入参数,如果没参数wait(),遇到wait就等待。
如果传入参数,经过传入参数的ms值后就苏醒,比如wait(1000),1秒后苏醒,然后从对象的等待集中删除该线程,并重新进行线程调度。然后,该线程以常规方式与其他线程竞争。
2.sleep方法在同步代码块中不释放锁,wait方法在同步代码块中释放锁(即当前线程释放对同步监视器的锁定,线程由运行态变为了阻塞态也称等待态,不指定参数需要notify唤醒)。
3.使用wait方法,当前线程必须拥有此对象监视器。即有synchronized同步监视器。
4.sleep是静态方法,wait方法是非静态的。
5.sleep是Thread类里面定义的静态方法,wait方法不是Thread定义的,是在Object定义的方法,最终由native修饰,看不到源码。查找文档时,wait方法需要到Object类去看。
jdk1.5版本之后的用法:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Demo7_ReentrantLock {
/**
*
* @param args
* 1.5版本的新特性互斥锁 1.同步 使用ReentrantLock类的lock()和unlock()方法进行同步 2.通信
* 使用ReentrantLock类的newCondition()方法可以获取Condition对象
* 需要等待的时候使用Condition的await()方法,唤醒的时候用signal()方法
* 不同的线程使用不同的Condition,这样就能区分唤醒的时候找哪个线程了
*/
public static void main(String[] args) {
final Printer2 p = new Printer2();
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
p.print2();
}
}
}.start();
new Thread() {
public void run() {
for (int i = 0; i < 200; ++i) {
p.print3();
}
}
}.start();
}
}
class Printer2 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() {
r.lock();
try {
if (flag != 1) {
c1.await();
}
System.out.print("我");
System.out.print("最");
System.out.print("帅");
System.out.print("\r\n");
flag = 2;
c2.signal();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
r.unlock();
}
}
public void print2() {
r.lock();
try {
if (flag != 2) {
c2.await();
}
System.out.print("你");
System.out.print("说");
System.out.print("啥");
System.out.print("\r\n");
flag = 3;
c3.signal();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
r.unlock();
}
}
public void print3() {
r.lock();
try {
if (flag != 3) {
c3.await();
}
System.out.print("听");
System.out.print("不");
System.out.print("见");
System.out.print("\r\n");
flag = 1;
c1.signal();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
r.unlock();
}
}
}
这里使用signal,是随机解除等待集中某个线程的阻塞状态。这比解除所有线程的阻塞更加有效,但是也存在危险。如果随机选择的线程发现自己仍然不能运行,那么它再次被阻塞。如果没有其他线程再次调用signal,那么系统就死锁了。
这里可以调用signalAll,signalAll不会立即激活一个等待线程。它仅仅解除等待线程的阻塞,以便线程可以在当前线程退出同步方法之后,通过竞争实现对对象的访问。
用newCondition方法获得一个条件对象,当一个线程拥有某个条件的锁时,它仅仅可以在该条件上调用await、signalAll或signal方法。
一个可重入的互斥锁Lock,它具有与使用 synchronized
方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
public Condition newCondition()
返回用来与此 Lock
实例一起使用的 Condition
实例。
在使用内置监视器锁时,返回的 Condition
实例支持与 Object
的监视器方法(wait
、notify
和 notifyAll
)相同的用法。
- 在调用
Condition
、waiting 或 signalling 这些方法中的任意一个方法时,如果没有保持此锁,则将抛出IllegalMonitorStateException
。 - 在调用 waiting 条件方法时,将释放锁,并在这些方法返回之前,重新获取该锁,将锁保持计数恢复为调用方法时所持有的值。
- 如果线程在等待时被中断,则等待将终止,并将抛出
InterruptedException
,清除线程的中断状态。 - 等待线程按 FIFO 顺序收到信号。
- 等待方法返回的线程重新获取锁的顺序与线程最初获取锁的顺序相同,在默认情况下,未指定此顺序,但对于公平 锁,它们更倾向于那些等待时间最长的线程。
void await() throws InterruptedException
造成当前线程在接到信号或被 中断之前一直处于等待状态。
与此 Condition
相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:
- 其他某个线程调用此
Condition
的signal()
方法,并且碰巧将当前线程选为被唤醒的线程;或者 - 其他某个线程调用此
Condition
的signalAll()
方法;或者 - 其他某个线程中断当前线程,且支持中断线程的挂起;或者
- 发生“虚假唤醒”
在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证 它保持此锁。
如果当前线程:
- 在进入此方法时已经设置了该线程的中断状态;或者
- 在支持等待和中断线程挂起时,线程被中断,
则抛出 InterruptedException
,并清除当前线程的中断状态。在第一种情况下,没有指定是否在释放锁之前发生中断测试。
实现注意事项
假定调用此方法时,当前线程保持了与此 Condition
有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException
)并且该实现必须对此进行记录。
与响应某个信号而返回的普通方法相比,实现可能更喜欢响应某个中断。在这种情况下,实现必须确保信号被重定向到另一个等待线程(如果有的话)。
抛出:
InterruptedException
- 如果当前线程被中断(并且支持中断线程挂起)
void signal()
唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await
返回之前,该线程必须重新获取锁。
===========================Talk is cheap, show me the code===========================