您的位置:首页 > 编程语言 > Java开发

JAVA线程同步中的notify和wait()函数

2016-03-11 11:42 429 查看
在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static
XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在JAVA中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。

其中Synchronized(Object){}可以理解为一个条件语句

if(Object 被占用){

则等待Object被释放

}

else{

执行{}中代码

}

需要注意的是,一旦开始执行后面的{}中的代码,则Object被占用

当{}中代码被执行完毕,或者调用wait()或者notify()函数后会将Object释放

还有需要注意的几点:

1、wait和notify必须在synchronized方法或者synchronized(Object)代码块内,因为这两个函数是针对有对象锁存在的情况才能调用(所谓的对象锁可以理解为对象占用)

2、notify唤醒函数调用时,JVM在与之对应的Object等待线程中随机唤醒一个线程

3、wait和notify都是释放当前占用的Object,只不过是notify调用后会继续执行到代码块{}的结尾处再释放Object,而wait调用后会阻塞本线程马上释放Object。

4、当有wait函数调用时必须要有对应的notify唤醒函数调用,wait函数只能被notify函数叫醒。

一道比较经典的面试题,题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

代码如下:

public class Draft {

public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
Object c = new Object();

Threadprinter pa = new Threadprinter(c,a,"a");
Threadprinter pb = new Threadprinter(a,b,"b");
Threadprinter pc = new Threadprinter(b,c,"c");

new Thread(pa).start();
Thread.sleep(200);
new Thread(pb).start();
Thread.sleep(200);
new Thread(pc).start();
Thread.sleep(200);
}

}

class Threadprinter implements Runnable{

private Object pre;
private Object self;
private String name;
public Threadprinter(Object pre, Object self,String name) {
// TODO Auto-generated constructor stub
this.name = name;
this.pre = pre;
this.self = self;
}
@Override
public void run() {
// TODO Auto-generated method stub
int count = 10;
while (count > 0) {
synchronized (pre) {
synchronized (self) {
System.out.println(name);
count--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
self.notify();
}
try {
pre.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
代码思路:

1、对于A,B,C三个对象,应该有3把锁,这样当唤醒线程时候才能知道对应唤醒哪个子线程

2、对于一个对象内部,比如A,每一次打印前应该被它之前的C唤醒,同时打印完了后需要唤醒它后面的B,而唤醒B的动作又是通过释放A来完成,所以一个对象内部需要有2把锁,即两个占用资源,它之前的对象和它本身,比如当A执行打印动作的时候需要得到C和它本身A这两个占用资源,所以应该有两个synchronized块嵌套,其中C占用资源在前。

3、用来唤醒自己的锁是在自己之前的对象给的,定义为pre,用来唤醒自己后面的对象的锁是自己给后面的,定义为self。

关于其中的睡眠函数:

当程序执行时,如果没有睡眠函数,在ABC线程被同时启动的时候内部流程如下

A占用C,A锁,B锁空余

->A打印,同时B等待A锁,C得到B锁等待C锁

->A释放A,同时B得到A等待B,C得到B等待C

->A释放C,C得到B,C开始打印,B得到A等待B

->C释放C,A得到C等待A,B得到A等待B

->C释放B,A得到C等待A,B得到A,B开始打印

打印出来结果为ACB,所以要想打印出ABC必须让ABC按照顺序启动,即要保证在第一轮打印的时候C不能在B之前得到第一轮中空余出来的那个B锁,所以解决方法就是保证第一轮打印中BC都等自己之前的线程进入休眠后再开启,所以添加睡眠函数。当第一轮中C抢占不到B资源后,在后面的执行中它都无法抢占,因为即使每一次一个线程执行的时候都会空余出一把锁比如第二轮A执行时候B锁仍然是空余出来的,但是此时C因为Wait函数进入了休眠状态,只有当B正常执行的时候才能通过notify将它唤醒,所以它不会再有机会抢占B锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: