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

前言

前面说了使用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,并唤醒释放对象锁之后处于等待状态的子线程,然后子线程开始调用,如此循环下去,打印结果就是要求所需要的。

扫描二维码关注公众号,回复: 902337 查看本文章

打印一次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();
    }
}

这个代码就不解释了,自己应该看的懂了。

猜你喜欢

转载自blog.csdn.net/saytime/article/details/51192280