JAVA多线程提高二:传统线程的互斥与同步&传统线程通信机制

Posted 屌丝码农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA多线程提高二:传统线程的互斥与同步&传统线程通信机制相关的知识,希望对你有一定的参考价值。

本文主要是回顾线程之间互斥和同步,以及线程之间通信,在最开始没有juc并发包情况下,如何实现的,也就是我们传统的方式如何来实现的,回顾知识是为了后面的提高作准备。

一、线程的互斥

为什么会有线程的互斥?可以想银行取款的问题,如果不做监控,多个人同时针对一个存折取钱的时候就会出现钱不对的问题,
下面我们通过两个例子来分析一下线程的互斥问题以及为什么会产生这个线程?

例子1:一个人生产信息,一个人消费信息

面向对象的思想:类 信息类 生产者 消费者

public class TriditionalThreadSafeLxh {
    public static void main(String[] args) {
        // 多个线程调用同一个对象,会带来数据错乱线程,产生的原因分析:
        // 多个线程调用一个对象的某个方法
        Info info = new Info();
        Production p = new Production(info);
        Consumer c = new Consumer(info);
        new Thread(p).start();
        new Thread(c).start();
    }
}
class Info {
    private String name = "李兴华";
    private String content = "讲师";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}
class Production implements Runnable {
    private Info info;
    
    public Production(Info info) {
        this.info = info;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.info.setName("李兴华");
                try {
                    Thread.sleep(80);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.info.setContent("讲师");
            }else{
                this.info.setName("mldn");
                try {
                    Thread.sleep(80);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.info.setContent("www.mldn.cn");
            }
        }
    }
}
class Consumer implements Runnable{
    private Info info;
    
    public Consumer(Info info) {
        this.info = info;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(80);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.info.getName()+","+this.info.getContent());
        }
    }
    
}

输出:

mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
李兴华,www.mldn.cn
mldn,讲师
mldn,www.mldn.cn

例子2:多个线程同时打印名字

public class TraditionalThreadSafe {
    
    public static void main(String[] args) {
        new TraditionalThreadSafe().init();
    }
    
    public void init(){
        final OutputMessage c = new OutputMessage();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("aaaaa");
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("bbbbb");
            }
        }).start();
        
    }
    
    class OutputMessage{
        public void output(String name){
            while(true){
                for (int i = 0; i < name.length(); i++) {
                    System.out.print(name.charAt(i));//打印一个人的名字
                }
                System.out.println();
            }
        }
    }
}

输出结果:

bbbbb
bbbbb
aaaa
aabbbbb
bbbbb
bbbbb

通过上面两个例子我们分析得出线程互斥问题产生的原因:
(1)多个线程调用同一个对象的某个方法
(2)在线程的run()方法中使用sleep更好的显示出数据错乱线程

 

2.现在已经产生了互斥问题
怎么解决上面的问题?
既然产生的原因是在调用这个对象的方法时候造成互斥,那么我就在这个方法或者代码块实现同步
下面用例子2看一下解决的办法:
代码:
public class TraditionalThreadSafe {
    
    public static void main(String[] args) {
        new TraditionalThreadSafe().init();
    }
    
    public void init(){
        final OutputMessage c = new OutputMessage();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("aaaaa");
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(120);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                c.output("bbbbb");
            }
        }).start();
        
    }
    
    class OutputMessage{
        public synchronized void output(String name){
//            synchronized (this) {
                while(true){
                    for (int i = 0; i < name.length(); i++) {
                        System.out.print(name.charAt(i));//打印一个人的名字
                    }
                    System.out.println();
                }
//            }
            
        }
    }
}

运行结果:

bbbbb
bbbbb
bbbbb
bbbbb
bbbbb
3.分析一下使用Synchronized在不同地方的使用
    (1)同步代码块调用的是当前对象
    (2)在方法上使用同步调用的当前对象
    (3)在静态static方法上使用同步调用的类.class,因为静态方法是类直接调用,锁就是这个类的字节码

 二、线程的通信机制

在讲解线程通信机制之前,我们来看一道面试题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次, 接着再回到主线程又循环100,如此循环50次

分析:

1、主进程子进程存在线程同步问题,对于同步的内容应该封装在一个类中,在类中定义主进程和子进程需要操作的方法,通过获得锁而执行各自的方法。 
2、这里存在子进程和主进程交替运行,应该添加一个信号变量,主进程和子进程判断该状态是否可以执行,主进程或子进程一次循环结束重置该变量值,然后调用notify(notifyallAll)方法来唤醒其他等待共享对象释放的线程。

实现:

定义一个类Business,定义主进程和子进程执行的方法,主进程和子进程对同一个对象business操作,通过使用synchronized作用在该对象相应的方法 从而达到同步的作用。类Business中定义一个交换变量bShouldSub来使得进程交替执行,主进程或子进程执行完都会更改该变量的值,然后调用this.notify 来唤醒其他等待对象business(this)的线程。

public class TraditionalThreadCommucation {
    public static void main(String[] args) {
        final Business b = new Business();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=50; i++) {
                    b.sub(i);
                }
            }
        }).start();
        
        
        for (int i = 1; i <=50; i++) {
            b.main(i);
        }
    }
}
class Business{
    private  boolean isShouldbeSub=true;        //标记为 判断是否该子线程运行
    public synchronized void sub(int i){
        while(!isShouldbeSub){  //不该子线程运行的时候进来了
            try {
                this.wait();  //等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("子线程循环 : "+j+",外层循环: "+i);
        }
        isShouldbeSub=false;
        this.notifyAll();
    }
    
    public synchronized void main(int i){
        while(isShouldbeSub){    //不该主线程运行的时候进来了,检查
            try {
                this.wait(); //等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out.println("主线程循环 : "+j+",外层循环: "+i);
        }
        isShouldbeSub=true;
        this.notifyAll();
    }
}

运行结果:

子线程循环 : 1,外层循环: 1
子线程循环 : 2,外层循环: 1
子线程循环 : 3,外层循环: 1
子线程循环 : 4,外层循环: 1
子线程循环 : 5,外层循环: 1
子线程循环 : 6,外层循环: 1
子线程循环 : 7,外层循环: 1
子线程循环 : 8,外层循环: 1
子线程循环 : 9,外层循环: 1
子线程循环 : 10,外层循环: 1
主线程循环 : 1,外层循环: 1
主线程循环 : 2,外层循环: 1
主线程循环 : 3,外层循环: 1
主线程循环 : 4,外层循环: 1
主线程循环 : 5,外层循环: 1
主线程循环 : 6,外层循环: 1
主线程循环 : 7,外层循环: 1
主线程循环 : 8,外层循环: 1
主线程循环 : 9,外层循环: 1
主线程循环 : 10,外层循环: 1
主线程循环 : 11,外层循环: 1
主线程循环 : 12,外层循环: 1
主线程循环 : 13,外层循环: 1
主线程循环 : 14,外层循环: 1
主线程循环 : 15,外层循环: 1
主线程循环 : 16,外层循环: 1
主线程循环 : 17,外层循环: 1
主线程循环 : 18,外层循环: 1
主线程循环 : 19,外层循环: 1
主线程循环 : 20,外层循环: 1
主线程循环 : 21,外层循环: 1
主线程循环 : 22,外层循环: 1
主线程循环 : 23,外层循环: 1
主线程循环 : 24,外层循环: 1
主线程循环 : 25,外层循环: 1
主线程循环 : 26,外层循环: 1
主线程循环 : 27,外层循环: 1
主线程循环 : 28,外层循环: 1
主线程循环 : 29,外层循环: 1
主线程循环 : 30,外层循环: 1
主线程循环 : 31,外层循环: 1
主线程循环 : 32,外层循环: 1
主线程循环 : 33,外层循环: 1
主线程循环 : 34,外层循环: 1
主线程循环 : 35,外层循环: 1
主线程循环 : 36,外层循环: 1
主线程循环 : 37,外层循环: 1
主线程循环 : 38,外层循环: 1
主线程循环 : 39,外层循环: 1
主线程循环 : 40,外层循环: 1
主线程循环 : 41,外层循环: 1
主线程循环 : 42,外层循环: 1
主线程循环 : 43,外层循环: 1
主线程循环 : 44,外层循环: 1
主线程循环 : 45,外层循环: 1
主线程循环 : 46,外层循环: 1
主线程循环 : 47,外层循环: 1
主线程循环 : 48,外层循环: 1
主线程循环 : 49,外层循环: 1
主线程循环 : 50,外层循环: 1
主线程循环 : 51,外层循环: 1
主线程循环 : 52,外层循环: 1
主线程循环 : 53,外层循环: 1
主线程循环 : 54,外层循环: 1
主线程循环 : 55,外层循环: 1
主线程循环 : 56,外层循环: 1
主线程循环 : 57,外层循环: 1
主线程循环 : 58,外层循环: 1
主线程循环 : 59,外层循环: 1
主线程循环 : 60,外层循环: 1
主线程循环 : 61,外层循环: 1
主线程循环 : 62,外层循环: 1
主线程循环 : 63,外层循环: 1
主线程循环 : 64,外层循环: 1
主线程循环 : 65,外层循环: 1
主线程循环 : 66,外层循环: 1
主线程循环 : 67,外层循环: 1
主线程循环 : 68,外层循环: 1
主线程循环 : 69,外层循环: 1
主线程循环 : 70,外层循环: 1
主线程循环 : 71,外层循环: 1
主线程循环 : 72,外层循环: 1
主线程循环 : 73,外层循环: 1
主线程循环 : 74,外层循环: 1
主线程循环 : 75,外层循环: 1
主线程循环 : 76,外层循环: 1
主线程循环 : 77,外层循环: 1
主线程循环 : 78,外层循环: 1
主线程循环 : 79,外层循环: 1
主线程循环 : 80,外层循环: 1
主线程循环 : 81,外层循环: 1
主线程循环 : 82,外层循环: 1
主线程循环 : 83,外层循环: 1
主线程循环 : 84,外层循环: 1
主线程循环 : 85,外层循环: 1
主线程循环 : 86,外层循环: 1
主线程循环 : 87,外层循环: 1
主线程循环 : 88,外层循环: 1
主线程循环 : 89,外层循环: 1
主线程循环 : 90,外层循环: 1
主线程循环 : 91,外层循环: 1
主线程循环 : 92,外层循环: 1
主线程循环 : 93,外层循环: 1
主线程循环 : 94,外层循环: 1
主线程循环 : 95,外层循环: 1
主线程循环 : 96,外层循环: 1
主线程循环 : 97,外层循环: 1
主线程循环 : 98,外层循环: 1
主线程循环 : 99,外层循环: 1
主线程循环 : 100,外层循环: 1
子线程循环 : 1,外层循环: 2
子线程循环 : 2,外层循环: 2
子线程循环 : 3,外层循环: 2
子线程循环 : 4,外层循环: 2
子线程循环 : 5,外层循环: 2
子线程循环 : 6,外层循环: 2
子线程循环 : 7,外层循环: 2
子线程循环 : 8,外层循环: 2
子线程循环 : 9,外层循环: 2
子线程循环 : 10,外层循环: 2
主线程循环 : 1,外层循环: 2
主线程循环 : 2,外层循环: 2
主线程循环 : 3,外层循环: 2
主线程循环 : 4,外层循环: 2
主线程循环 : 5,外层循环: 2
主线程循环 : 6,外层循环: 2
主线程循环 : 7,外层循环: 2
主线程循环 : 8,外层循环: 2
主线程循环 : 9,外层循环: 2
主线程循环 : 10,外层循环: 2
主线程循环 : 11,外层循环: 2
主线程循环 : 12,外层循环: 2
主线程循环 : 13,外层循环: 2
主线程循环 : 14,外层循环: 2
主线程循环 : 15,外层循环: 2
主线程循环 : 16,外层循环: 2
主线程循环 : 17,外层循环: 2
主线程循环 : 18,外层循环: 2
主线程循环 : 19,外层循环: 2
主线程循环 : 20,外层循环: 2
主线程循环 : 21,外层循环: 2
主线程循环 : 22,外层循环: 2
主线程循环 : 23,外层循环: 2
主线程循环 : 24,外层循环: 2
主线程循环 : 25,外层循环: 2
主线程循环 : 26,外层循环: 2
主线程循环 : 27,外层循环: 2
主线程循环 : 28,外层循环: 2
主线程循环 : 29,外层循环: 2
主线程循环 : 30,外层循环: 2
主线程循环 : 31,外层循环: 2
主线程循环 : 32,外层循环: 2
主线程循环 : 33,外层循环: 2
主线程循环 : 34,外层循环: 2
主线程循环 : 35,外层循环: 2
主线程循环 : 36,外层循环: 2
主线程循环 : 37,外层循环: 2
主线程循环 : 38,外层循环: 2
主线程循环 : 39,外层循环: 2
主线程循环 : 40,外层循环: 2
主线程循环 : 41,外层循环: 2
主线程循环 : 42,外层循环: 2
主线程循环 : 43,外层循环: 2
主线程循环 : 44,外层循环: 2
主线程循环 : 45,外层循环: 2
主线程循环 : 46,外层循环: 2
主线程循环 : 47,外层循环: 2
主线程循环 : 48,外层循环: 2
主线程循环 : 49,外层循环: 2
主线程循环 : 50,外层循环: 2
主线程循环 : 51,外层循环: 2
主线程循环 : 52,外层循环: 2
主线程循环 : 53,外层循环: 2
主线程循环 : 54,外层循环: 2
主线程循环 : 55,外层循环: 2
主线程循环 : 56,外层循环: 2
主线程循环 : 57,外层循环: 2
主线程循环 : 58,外层循环: 2
主线程循环 : 59,外层循环: 2
主线程循环 : 60,外层循环: 2
主线程循环 : 61,外层循环: 2
主线程循环 : 62,外层循环: 2
主线程循环 : 63,外层循环: 2
主线程循环 : 64,外层循环: 2
主线程循环 : 65,外层循环: 2
主线程循环 : 66,外层循环: 2
主线程循环 : 67,外层循环: 2
主线程循环 : 68,外层循环: 2
主线程循环 : 69,外层循环: 2
主线程循环 : 70,外层循环: 2
主线程循环 : 71,外层循环: 2
主线程循环 : 72,外层循环: 2
主线程循环 : 73,外层循环: 2
主线程循环 : 74,外层循环: 2
主线程循环 : 75,外层循环: 2
主线程循环 : 76,外层循环: 2
主线程循环 : 77,外层循环: 2
主线程循环 : 78,外层循环: 2
主线程循环 : 79,外层循环: 2
主线程循环 : 80,外层循环: 2
主线程循环 : 81,外层循环: 2
主线程循环 : 82,外层循环: 2
主线程循环 : 83,外层循环: 2
主线程循环 : 84,外层循环: 2
主线程循环 : 85,外层循环: 2
主线程循环 : 86,外层循环: 2
主线程循环 : 87,外层循环: 2
主线程循环 : 88,外层循环: 2
主线程循环 : 89,外层循环: 2
主线程循环 : 90,外层循环: 2
主线程循环 : 91,外层循环: 2
主线程循环 : 92,外层循环: 2
主线程循环 : 93,外层循环: 2
主线程循环 : 94,外层循环: 2
主线程循环 : 95,外层循环: 2
主线程循环 : 96,外层循环: 2
主线程循环 : 97,外层循环: 2
主线程循环 : 98,外层循环: 2
主线程循环 : 99,外层循环: 2
主线程循环 : 100,外层循环: 2
子线程循环 : 1,外层循环: 3
子线程循环 : 2,外层循环: 3
子线程循环 : 3,外层循环: 3
子线程循环 : 4,外层循环: 3
子线程循环 : 5,外层循环: 3
子线程循环 : 6,外层循环: 3
子线程循环 : 7,外层循环: 3
子线程循环 : 8,外层循环: 3
子线程循环 : 9,外层循环: 3

注意:

<<Effective Java>>中提到,永远不要在循环之外调用wait方法。因为,参考:为什么wait()语句要放在while循环之内

 

参考资料:

《多线程视频》张孝祥

以上是关于JAVA多线程提高二:传统线程的互斥与同步&传统线程通信机制的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程——Lock&Condition

Java 多线程与并发(案例 + 应用)

Java 多线程与并发(案例 + 应用)

Java多线程与并发库高级应用-传统线程互斥技术

LINXU多线程(进程与线程区别,互斥同步)

并发与多线程——同步与互斥