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

多线程并发编程(四):多线程同步互斥Wait/Notify

2016-04-19 17:16 399 查看

前言

前面说了使用Synchronized来进行线程之间的同步,接下来说明wait/notify的使用。

首先wait/notify必须结合synchronized来使用,即在synchronized内部使用

wait表示在获取到该对象锁之后,主动释放该对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作

但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。

Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。Thread.sleep() 并没有释放对象锁,只是暂时休眠,他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

经典面试题

子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再主线程循环100次,如此循环50次,请写出程序

package test01;

public class TraditionalThreadCommunication {

public static void main(String[] args) {
final Business business = new Business();

// 子线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
business.sub(i);
}
}
}).start();

// main方法主线程
for (int i = 0; i < 50; i++) {
business.main(i);
}
}

}
/**
* 线程业务处理类
* @author Administrator
*
*/
class Business{
// 子线程是否可以调用
private boolean subShould = true;

// 子线程业务方法
public synchronized void sub(int i){
while(!subShould){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j < 10; j++) {
System.out.println("sub thread sequence of " + j + " ,loop of " + i);
}
subShould = false;
this.notify();
}

// 主线程业务方法
public synchronized void main(int i){
while(subShould){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j < 100; j++) {
System.out.println("main thread sequence of " + j + " ,loop of " + i);
}
subShould = true;
this.notify();
}
}


分析:

1、实现时首先考虑将线程业务处理封装成一个类,在这个类中实现同步互斥的机制,好处是那么在其他地方调用的时候就不需要考虑线程安全的问题了,另外也好实现同步机制。

2、拆分业务方法,分析题目可以知道,线程业务方法主要是两个,一个主线程100次,一个子线程10次,循环50次那个不属于该线程的业务方法,由调用端去循环。

实现:

1、第一步,封装一个线程业务处理类:Business,分别提供两个业务方法,子线程打印方法和主线程打印方法,主类main方法分别实现两个线程调用这个类的两个方法,这里main方法就是主线程了,不需要另外创建一个新的线程了,所以只要创建一个线程就可以了,当然以此递推,如果需要三个线程,创建两个就可以了。所以一开始代码如下:

package test01;

public class TraditionalThreadCommunication {

public static void main(String[] args) {
final Business business = new Business();

// 子线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
business.sub(i);
}
}
}).start();

// main方法主线程
for (int i = 0; i < 50; i++) {
business.main(i);
}
}

}
/**
* 线程业务处理类
* @author Administrator
*
*/
class Business{

// 子线程业务方法
public void sub(int i){
for (int j = 0; j < 10; j++) {
System.out.println("sub thread sequence of " + j + " ,loop of " + i);
}
}

// 主线程业务方法
public void main(int i){
for (int j = 0; j < 100; j++) {
System.out.println("main thread sequence of " + j + " ,loop of " + i);
}
}
}


这样的代码执行起来,子线程和主线程打印都是根据CPU随机分配的,我们需要将子线程与主线程进行同步,即子线程运行打印的时候,主线程不能运行打印,主线程运行打印的时候,子线程不能运行打印。那么将两个方法进行同步就可以解决这个问题,如下:

class Business{

// 子线程业务方法
public synchronized void sub(int i){
for (int j = 0; j < 10; j++) {
System.out.println("sub thread sequence of " + j + " ,loop of " + i);
}
}

// 主线程业务方法
public synchronized void main(int i){
for (int j = 0; j < 100; j++) {
System.out.println("main thread sequence of " + j + " ,loop of " + i);
}
}
}


两个方法都加上synchronized的,都是用的Business这个类的对象锁,所以互斥,子线程调用sub方法时,主线程不能调用main方法。但是还有一个问题,那就是有可能子线程一直抢占CPU资源,一直运行多次,或者运行完,主线程将拿到Business的对象锁,才开始运行,也有可能主线程一直抢占CPU资源,怎么解决这个问题了,那么就需要使用wait/notify了。

class Business{
// 子线程是否可以调用
private boolean subShould = true;

// 子线程业务方法
public synchronized void sub(int i){
while(!subShould){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j < 10; j++) {
System.out.println("sub thread sequence of " + j + " ,loop of " + i);
}
subShould = false;
this.notify();
}

// 主线程业务方法
public synchronized void main(int i){
while(subShould){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 0; j < 100; j++) {
System.out.println("main thread sequence of " + j + " ,loop of " + i);
}
subShould = true;
this.notify();
}
}


使用一个变量来进行判断,首先,TraditionalThreadCommunication 类中main方法执行的时候,假设子线程先抢占到资源,拿到了Business类的对象锁,那么调用Business类中的sub方法,那么进行10次打印,打印完了之后,变量重新赋值,然后两个线程再次争夺CPU资源,假设还是子线程抢占到了CPU资源,拿到了Business的对象锁,那么进行sub方法的时候,由于subShould的值已经设置为false,那么会调用this.wait();即子线程释放Business类的对象锁,那么变成再次将占资源,如果这时候,主线程拿到了对象锁,那么执行打印,设置变量值为true,并唤醒释放对象锁之后处于等待状态的子线程,然后子线程开始调用,如此循环下去,打印结果就是要求所需要的。

打印一次A,两次B,三次C,如此依次循环打印50次。

package test01;

public class ThreadPrintTest{

public static void main(String[] args) {
final PrintChar print = new PrintChar();

// 线程A
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 50; i++) {
print.printA();
}
}
}).start();

// 线程B
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 50; i++) {
print.printB();
}
}
}).start();

// 线程C
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 50; i++) {
print.printC();
}
}
}).start();
}
}

// 线程打印类
class PrintChar{
private int count = 0;

// 打印一次A
public synchronized void printA(){
while((count = count % 3) != 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
count++;
this.notifyAll();
}

// 打印两次B
public synchronized void printB(){
while((count = count % 3) != 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 2; i++) {
System.out.println("B");
}
count++;
this.notifyAll();
}

// 打印三次C
public synchronized void printC(){
while((count = count % 3) != 2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 3; i++) {
System.out.println("C");
}
count++;
this.notifyAll();
}
}


这个代码就不解释了,自己应该看的懂了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: