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循环之内

参考资料:

《多线程视频》张孝祥

原文地址:https://www.cnblogs.com/pony1223/p/9251641.html

时间: 2024-10-11 22:38:02

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

线程的互斥锁和条件变量通信机制

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #include <pthread.h> 5 6 #define BUFFER_SIZE 2 7 struct prodcons 8 { 9 int buffer[BUFFER_SIZE]; 10 pthread_mutex_t lock; 11 int readpos,writepos; 12 pthread_cond_t no

“全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第二十六章:同步方法生产者与消费者线程 下一章 "全栈2019"Java多线程第二十七章:Lock获取lock/释放unlock锁 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorha

JAVA多线程提高一:传统线程技术&amp;传统定时器Timer

前面我们已经对多线程的基础知识有了一定的了解,那么接下来我们将要对多线程进一步深入的学习:但在学习之前我们还是要对传统的技术进行一次回顾,本章我们回顾的则是:传统线程技术和传统的定时器实现. 一.传统线程技术 1.创建方式 1.继承thread类 Thread t = new Thread(){ @Override public void run() { } }; t.start(); 2.实现Runnable接口 Thread t1 = new Thread(new Runnable() {

JAVA多线程提高三:线程范围内共享变量&amp;ThreadLocal

今天我们学习的是如何在线程自己的范围内达到变量数据的共享,而各个线程之间又是互相独立开来,各自维护的,即我们说的ThreadLocal的作用. 一.概念 可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据,就可以实现线程范围内共享相同的变量. 二.代码 Runnable中的run()方法里面执行Thread.currentThread()都会对应当前Runnable对应的线程,因此A.B中对应的Thread.currentThread

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

Java多线程开发系列之番外篇:事件派发线程---EventDispatchThread

事件派发线程是java Swing开发中重要的知识点,在安卓app开发中,也是非常重要的一点.今天我们在多线程开发中,穿插进来这个线程.分别从线程的来由.原理和使用方法三个方面来学习事件派发线程. 一.事件派发线程的前世今生 事件(Event)派发(Dispatch)线程(Thread)简写为EDT,也就是各个首字母的简写.在一些书或者博客里边也将其译为事件分发线程.事件调度线程.巴拉巴拉,总之,知道这些名字就行.笔者认为这里翻译成派发更准确点. 熟悉Swing和awt编程的小伙伴对事件派发线程

Java并发原语——线程、互斥与同步

本文将介绍: Java线程基本操作(创建.等待等) Java线程同步原语(同步.互斥) 如果你对以上话题已了如指掌,请止步. Java线程基本操作 Java的线程API以java.lang.Thread类提供,线程的基本操作被封装为为Thread类的方法,其中常用的方法是:   方法 说明 void start() 启动线程 void join() 等待线程结束 创建(启动)线程 Java中,创建线程的过程分为两步: 创建可执行(Runnable)的线程对象: 调用它的start()方法: 可执

Java 多线程详解(二)------如何创建进程和线程

Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html 在上一篇博客中,我们已经介绍了并发和并行的区别,以及进程和线程的理解,那么在Java 中如何创建进程和线程呢? 1.在 Windows 操作系统中创建进程 在 windows 操作系统中,我们创建一个进程通常就是打开某个应用软件,这便在电脑中创建了一个进程.更原始一点的,我们在命令提示符中来做(我们以打开记事本这个进程为例): 第一步:windows+R,

Java多线程13:读写锁和两种同步方式的对比

读写锁ReentrantReadWriteLock概述 大型网站中很重要的一块内容就是数据的读写,ReentrantLock虽然具有 完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低.所以在JDK中提供了一种读写锁 ReentrantReadWriteLock,使用它可以加快运行效率. 读写锁表示两个锁,一个是读操作相关的锁,称为共享锁:另一个是写操作相关的锁,称为排他锁.我把这两个操作理解为三句话: 1.读和读之间不互斥,因为读操作不会有线程安全问题 2.