1.notify()
具体是怎么个意思呢?就是用来唤醒在此对象上等待的单个线程。说的有点太专业。打个比方,现在有十栋大房子,里面有很多被上了锁的房间,奇怪的是锁都是一样的,更不可思议的是,现在只有一把钥匙。而此时,张三用完钥匙后,就会发出归还钥匙的提醒,就相当于发出notify()通知,但是要注意的是,此时钥匙还在张三手中,只不过,当张三发出notify()通知后,JVM从那些整个沉睡的线程,唤醒一个。对应本例子,就是从其余的九栋大房子中唤醒一家,至于提醒谁来拿这把钥匙,就看JVM如何分配资源了。等到张三把钥匙归还后,那个被提醒的哪家,就可以使用该把钥匙来开房间门了。与此相对应的,还有一个notifyAll()方法。这是什么意思呢,还是本例,张三嗓门大,这时吼了一嗓子,即notifyAll(),所有沉睡的线程全都被吵醒了,当张三归还钥匙后,他们就可以竞争了,注意,刚才是JVM自动分配,而此时是线程之间竞争,比如优先级等等条件,是有区别的。
需要注意的是notify()方法执行后,并不是立即释放锁,而是等到加锁的代码块执行完后,才开始释放的,相当于本例中,张三只是发出了归还的通知,但是钥匙还没有归还,需要等到代码块,执行完后,才可以归还。
2.wait()
这个方法又是怎么个意思呢?当执行到这个方法时,就把钥匙归还,开始睡觉了。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
下面来看一个例子,加入我们要实现三个线程之间的同步操作,如何来实现呢?加入有三个线程分别用来输出A/B/C,如何能够三个线程之间顺序执行呢?这时候就需要采取线程之间同步的操作了,详见下面的代码。
[java] view plain copy print?
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name; // A B C
this.prev = prev; // c a b
this.self = self; // a b c
}
@Override
public void run() {
int count = 10;
while (count > 0) {
// 加锁,锁的钥匙是prev
synchronized (prev) {
// 一把锁,锁的钥匙是self变量
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒另一个线程,但是也需要把本线程执行完后,才可以释放锁
self.notify(); //a b c
}
try {
// 释放对象锁,本线程进入休眠状态,等待被唤醒
prev.wait(); //睡觉觉了,等待被叫醒吧 // c a b
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
// 这样才可以保证按照顺序执行
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}</span>
下面来分析一下,首先通过线程之间的同步操作,上面的例子就会按照线程的顺序来分别执行,最终输出的结果就是ABCABCABC..............。
在此还是打一个比方,程序刚开始的时候,创建了三个线程的对象,也就是代表有三座大房子,下面开始执行了,第一个大房子里面有一个叫做张三的人员,打开了一个房子的钥匙c,然后拿着钥匙a,又一次打开了这个房子里面的一个箱子,最后完成后,把箱子的钥匙a给归还了,执行完后,张三开始在这个c钥匙的房子里面漫长的沉睡,也就是prev.wati()方法;当第二个人李四来到房子后,同样执行一边操作,此时需要注意的是,李四扔出的是箱子钥匙b,并在a钥匙的房间随着了;好吧懒货,王五来到了第三个大房子,同样执行了一遍操作,注意的是,王五扔出的是箱子钥匙c,在钥匙b房子睡着了。
重点来了,王五扔出箱子钥匙c后,此时就把在房子钥匙c中的张三给唤醒了,等到王五把钥匙归还后,此时张三又开始周而复始的运作了;于是大家可以按照逻辑接着向下分析一下。
在整个例子中需要注意一下几点
1.为了保证几个线程先能够顺序执行,于是加入了Thread.sleep(10)
2.每个对象执行wait()或者notify()方法时,只能在同一把锁的房子里面,例如
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒另一个线程,但是也需要把本线程执行完后,才可以释放锁
self.notify(); //a b c
}
房子的锁是self,所以执行self.notify()代表把我房子的钥匙给归还了。
有了上面的分析过程,下面我们出一道题,传统线程同步通信技术,子线程循环10次,接着主线程循环100次,又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次。这个例子又如何来实现呢?小编只在这里贴出源码,网上有很多解释的,可以按照小编的逻辑来分析一下。
[java] view plain copy print?
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class TraditionalThreadCommunication {
/**
* @param args
*/
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
business.sub(i);
}
}
}
).start();
for(int i=1;i<=50;i++){
business.main(i);
}
}
}
class Business {
private boolean bShouldSub = true;
public synchronized void sub(int i){
while(!bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
this.notify();
}
public synchronized void main(int i){
while(bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=100;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
this.notify();
}
}
</span>
具体是怎么个意思呢?就是用来唤醒在此对象上等待的单个线程。说的有点太专业。打个比方,现在有十栋大房子,里面有很多被上了锁的房间,奇怪的是锁都是一样的,更不可思议的是,现在只有一把钥匙。而此时,张三用完钥匙后,就会发出归还钥匙的提醒,就相当于发出notify()通知,但是要注意的是,此时钥匙还在张三手中,只不过,当张三发出notify()通知后,JVM从那些整个沉睡的线程,唤醒一个。对应本例子,就是从其余的九栋大房子中唤醒一家,至于提醒谁来拿这把钥匙,就看JVM如何分配资源了。等到张三把钥匙归还后,那个被提醒的哪家,就可以使用该把钥匙来开房间门了。与此相对应的,还有一个notifyAll()方法。这是什么意思呢,还是本例,张三嗓门大,这时吼了一嗓子,即notifyAll(),所有沉睡的线程全都被吵醒了,当张三归还钥匙后,他们就可以竞争了,注意,刚才是JVM自动分配,而此时是线程之间竞争,比如优先级等等条件,是有区别的。
需要注意的是notify()方法执行后,并不是立即释放锁,而是等到加锁的代码块执行完后,才开始释放的,相当于本例中,张三只是发出了归还的通知,但是钥匙还没有归还,需要等到代码块,执行完后,才可以归还。
2.wait()
这个方法又是怎么个意思呢?当执行到这个方法时,就把钥匙归还,开始睡觉了。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
下面来看一个例子,加入我们要实现三个线程之间的同步操作,如何来实现呢?加入有三个线程分别用来输出A/B/C,如何能够三个线程之间顺序执行呢?这时候就需要采取线程之间同步的操作了,详见下面的代码。
[java] view plain copy print?
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class MyThreadPrinter2 implements Runnable {
private String name;
private Object prev;
private Object self;
private MyThreadPrinter2(String name, Object prev, Object self) {
this.name = name; // A B C
this.prev = prev; // c a b
this.self = self; // a b c
}
@Override
public void run() {
int count = 10;
while (count > 0) {
// 加锁,锁的钥匙是prev
synchronized (prev) {
// 一把锁,锁的钥匙是self变量
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒另一个线程,但是也需要把本线程执行完后,才可以释放锁
self.notify(); //a b c
}
try {
// 释放对象锁,本线程进入休眠状态,等待被唤醒
prev.wait(); //睡觉觉了,等待被叫醒吧 // c a b
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
new Thread(pa).start();
// 这样才可以保证按照顺序执行
Thread.sleep(10);
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}</span>
下面来分析一下,首先通过线程之间的同步操作,上面的例子就会按照线程的顺序来分别执行,最终输出的结果就是ABCABCABC..............。
在此还是打一个比方,程序刚开始的时候,创建了三个线程的对象,也就是代表有三座大房子,下面开始执行了,第一个大房子里面有一个叫做张三的人员,打开了一个房子的钥匙c,然后拿着钥匙a,又一次打开了这个房子里面的一个箱子,最后完成后,把箱子的钥匙a给归还了,执行完后,张三开始在这个c钥匙的房子里面漫长的沉睡,也就是prev.wati()方法;当第二个人李四来到房子后,同样执行一边操作,此时需要注意的是,李四扔出的是箱子钥匙b,并在a钥匙的房间随着了;好吧懒货,王五来到了第三个大房子,同样执行了一遍操作,注意的是,王五扔出的是箱子钥匙c,在钥匙b房子睡着了。
重点来了,王五扔出箱子钥匙c后,此时就把在房子钥匙c中的张三给唤醒了,等到王五把钥匙归还后,此时张三又开始周而复始的运作了;于是大家可以按照逻辑接着向下分析一下。
在整个例子中需要注意一下几点
1.为了保证几个线程先能够顺序执行,于是加入了Thread.sleep(10)
2.每个对象执行wait()或者notify()方法时,只能在同一把锁的房子里面,例如
synchronized (self) {
System.out.print(name);
count--;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒另一个线程,但是也需要把本线程执行完后,才可以释放锁
self.notify(); //a b c
}
房子的锁是self,所以执行self.notify()代表把我房子的钥匙给归还了。
有了上面的分析过程,下面我们出一道题,传统线程同步通信技术,子线程循环10次,接着主线程循环100次,又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次。这个例子又如何来实现呢?小编只在这里贴出源码,网上有很多解释的,可以按照小编的逻辑来分析一下。
[java] view plain copy print?
<span style="font-family:Comic Sans MS;font-size:18px;">package com.test;
public class TraditionalThreadCommunication {
/**
* @param args
*/
public static void main(String[] args) {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
business.sub(i);
}
}
}
).start();
for(int i=1;i<=50;i++){
business.main(i);
}
}
}
class Business {
private boolean bShouldSub = true;
public synchronized void sub(int i){
while(!bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
this.notify();
}
public synchronized void main(int i){
while(bShouldSub){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=100;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
this.notify();
}
}
</span>