在同步代码块中,锁对象是谁,就用那个对象来调用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
方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
返回用来与此 Lock
实例一起使用的 Condition
实例。
在使用内置监视器锁时,返回的 Condition
实例支持与 Object
的监视器方法(wait
、notify
和 notifyAll
)相同的用法。
Condition
、waiting 或 signalling 这些方法中的任意一个方法时,如果没有保持此锁,则将抛出 IllegalMonitorStateException
。InterruptedException
,清除线程的中断状态。
造成当前线程在接到信号或被 中断之前一直处于等待状态。
与此 Condition
相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:
Condition
的 signal()
方法,并且碰巧将当前线程选为被唤醒的线程;或者Condition
的 signalAll()
方法;或者在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证 它保持此锁。
如果当前线程:
则抛出 InterruptedException
,并清除当前线程的中断状态。在第一种情况下,没有指定是否在释放锁之前发生中断测试。
实现注意事项
假定调用此方法时,当前线程保持了与此 Condition
有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException
)并且该实现必须对此进行记录。
与响应某个信号而返回的普通方法相比,实现可能更喜欢响应某个中断。在这种情况下,实现必须确保信号被重定向到另一个等待线程(如果有的话)。
抛出:
InterruptedException
- 如果当前线程被中断(并且支持中断线程挂起)
唤醒一个等待线程。
如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await
返回之前,该线程必须重新获取锁。
===========================Talk is cheap, show me the code===========================