java多线程技术四- 传统多线程同步通信技术

面试题分析

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


  • 分析

子线程在循环的时候,主线程不能执行,说明子线程和主线程之间一定要互斥,子线程循环10次的代码要被保护起来,主线程循环100次的代码要被保护起来
分析清楚业务逻辑,业务逻辑是子线程循环10次,接着主线程循环100次,彼此往复调用,然后外部调用业务逻辑这块

代码实现:

public class ThreadCommunication {
    public static void main(String[] args) {
        // 子线程
        new Thread(new Runnable() {
            public void run() {
                for (int i = 1; i <= 50; i++) {
                    for (int j = 1; j < 10; j++) {
                        System.out.println("sub thread sequence " + j + " of loop " + i);
                    }
                }
            }
        }).start();
        //主线程
        for (int i = 1; i <= 50; i++) {
            for (int j = 1; j <= 100; j++) {
                System.out.println("main thread sequence " + j + " of loop " + i);
            }
        }

    }
}

执行结果:
java多线程技术四- 传统多线程同步通信技术_第1张图片

如下修改

public class ThreadCommunication {
    public static void main(String[] args) {
        // 子线程
        new Thread(new Runnable() {
            public void run() {
                for (int i = 1; i <= 50; i++) {
                    synchronized(ThreadCommunication.class) {
                        for (int j = 1; j <=10; j++) {
                            System.out.println("sub thread sequence " + j + " of loop " + i);
                        }
                    }

                }
            }
        }).start();
        //主线程
        for (int i = 1; i <= 50; i++) {
            synchronized (ThreadCommunication.class){
                for (int j = 1; j <= 100; j++) {
                    System.out.println("main thread sequence " + j + " of loop " + i);
                }
            }
        }

    }
}

针对于循环使用synchronized保护起来,但是用了字节码作为锁,这时候,你确信你所有线程访问这个类下面的 某个方法是确实要全部同步就使用字节码对象作为锁,就是不分组,轻易不要使用字节码对象作为锁

优化:使用业务类封装业务逻辑

public class ThreadCommunication {
    public static void main(String[] args) {
        final Bussiness bs = new Bussiness();
        // 子线程
        new Thread(new Runnable() {
            public void run() {
                for (int i = 1; i <= 50; i++) {
                    bs.sub(i);
                }
            }
        }).start();
        //主线程
        for (int i = 1; i <= 50; i++) {
            bs.main(i);
        }

    }
}


class Bussiness {
    public synchronized void sub(int i){
        for (int j = 1; j <=10; j++) {
            System.out.println("sub thread sequence " + j + " of loop " + i);
        }
    }

    public synchronized void main(int i){
        for (int j = 1; j <=100; j++) {
            System.out.println("main thread sequence " + j + " of loop " + i);
        }
    }
}

执行结果:

java多线程技术四- 传统多线程同步通信技术_第2张图片

进行到这一步,说明已经达到了线程同步效果,即线程执行是互斥的,下面我们再看看如何线程通信,当子线程执行完去执行主线程

线程通信

java 线程之间通信是使用notify(),wait()方法
- Bussiness类做如下修改:

class Bussiness {
    private boolean beShouldSub = true;

    public synchronized void sub(int i){

        if(!beShouldSub){
            try {
                //当不改论到自己执行的时候就等待,使用同一个同步监视器对象
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <=10; j++) {
            System.out.println("sub thread sequence " + j + " of loop " + i);
        }
        beShouldSub = false;
    }

    public synchronized void main(int i){
        //当不改自己执行的时候就等待,使用同一个同步监视器对象
        if(beShouldSub){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <=100; j++) {
            System.out.println("main thread sequence " + j + " of loop " + i);
        }
        beShouldSub = true;
    }
}

java多线程技术四- 传统多线程同步通信技术_第3张图片

假设CPU 一下子就给了主线程,主线程一检查,不该自己执行,然后就等待,然后CPU就去找sub 线程,sub线程得到cpu资源就开始执行,执行完了,更新状态,这时候该主线程执行了,但是这时候主线程已经在等待了,不会去反推变量状态,所以主线程不会执行,这时候要主线程执行的话,需要将其唤醒,使用notify()

class Bussiness {
    private boolean beShouldSub = true;

    public synchronized void sub(int i){

        if(!beShouldSub){
            try {
                //当不改论到自己执行的时候就等待,使用同一个同步监视器对象
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <=10; j++) {
            System.out.println("sub thread sequence " + j + " of loop " + i);
        }
        beShouldSub = false;
        this.notify();
    }

    public synchronized void main(int i){
        //当不改自己执行的时候就等待,使用同一个同步监视器对象
        if(beShouldSub){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <=100; j++) {
            System.out.println("main thread sequence " + j + " of loop " + i);
        }
        beShouldSub = true;
        this.notify();
    }
}
  • 执行结果:
    java多线程技术四- 传统多线程同步通信技术_第4张图片

  • 再次优化,将Bussiness 类的两个 if条件换成while条件,比如 if(beShouldSub) 改成while (beShouldSub)

    会判断两次,第一次发现当不是自己执行的时候就等待,然后别人执行完把自己唤醒,唤醒之后又判断一下,是不是该自己执行了,是的话就自己执行了,while方式比if 好,更健壮,牢靠
    为什么用while而不是if
    有一些情况下,线程会被假唤醒, 就是没有被通知的时候会被唤醒

经验

要用到共同数据的若干方法应该归到一个类上,这种设计正好体现了高类聚和程序的健壮性
锁本身是一个对象,两个线程指定的代码要实现同步互斥的效果,那必须使用同一个lock对象,锁是在要操作的资源类(业务类) 的内部方法中,而不是在线程中,如上,线程操作的是Business 对象, synchronized是在Business类的两个内部方法上,而不是在线程上,因此将线程的同步,通信都是在资源类上,线程只是将资源类拿过去运行

写在最后的话

感谢张孝祥老师讲的java基础增强课程,一路走好,感谢!

你可能感兴趣的:(java多线程)