多线程如何实现同步-多线程之间通讯

一. 什么是线程安全问题

多线程同时对同一个全局变量做写的操作,可能会受到其他

线程的干扰,就会发生线程安全性问题。

全局变量----java内存结构

什么是写操作------修改

当多个线程共享同一个全局变量,做写的操作时,可能会受到其他的线程干扰,发生线程

安全问题。

public class ThreadCount implements Runnable {
    private static Integer count = 100;

    @Override
    public void run() {
        while (count > 1) {
            cal();
        }
    }

    private  void cal() {
        try {
            Thread.sleep(20);
        } catch (Exception e) {

        }
        count--;
        System.out.println(Thread.currentThread().getName() + "," + count);
    }


    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

同时执行概念

1.. 多线程如何解决线程安全问题/ 多线程如何实现同步呢

核心思想:上锁 分布式锁

在同一个jvm中,多个线程需要竞争锁的资源,最终只能够有一个线程

能够获取到锁,多个线程同时抢同一把锁,谁(线程)能够获取到锁,

谁就可以执行到该代码,如果没有获取锁成功 中间需要经历锁的升级过程

如果一致没有获取到锁则会一直阻塞等待。

如果线程A获取锁成功 但是线程A一直不释放锁

线程B一直获取不到锁,则会一直阻塞等待。

代码从那一块需要上锁?-----可能会发生线程安全性问题的代码需要上锁。

Juc并发编程 锁 重入锁 悲观锁 乐观锁 公平锁 非公平锁

线程0 线程1 同时获取 this锁,假设线程0 获取到this ,意味着线程1没有获取到锁

则会阻塞等待。等我们线程0 执行完count-- 释放锁之后 就会唤醒 线程1从新进入

到获取锁的资源。

获取锁与释放锁 全部都是有底层虚拟机实现好了。

对一块代码加锁缺点:

可能会影响到程序的执行效率。

如果是同一把锁 在多线程的情况下 最终只能够给一个线程使用。

如果有线程持有了该锁 意味着其他的线程 不能够在继续获取锁

核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码

上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。

  1. 使用synchronized锁,JDK1.6开始 锁的升级过程 juc 18-25
  2. 使用Lock锁 ,需要自己实现锁的升级过程。底层是基于aqs实现
  3. 使用Threadlocal,需要注意内存泄漏的问题。
  4. 原子类 CAS 非阻塞式

2. synchronized锁的基本用法

在多线程的情况下 需要是同一个对象锁

Synchronized(对象锁){
 需要保证线程安全的代码
}
  1. 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象 的锁。
  2. 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁
  3. 修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得 当前类对象 的锁

2.1 修饰代码块

修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得 给定对象 的锁。

public class ThreadCount implements Runnable {
    private static Integer count = 100;
    private String lock = "lock";

    @Override
    public void run() {
        while (count > 1) {
            cal();
        }
    }
    private void cal() {
        synchronized (this) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }

    }
    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

2.2 修饰实例方法

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例的锁

在实例方法上默认加上synchronized 默认使用this锁。

public class ThreadCount implements Runnable {
    private static Integer count = 100;
    private String lock = "lock";

    @Override
    public void run() {
        while (count > 1) {
            cal();
        }
    }

    private synchronized void cal() {
        try {
            Thread.sleep(10);
        } catch (Exception e) {

        }
        count--;
        System.out.println(Thread.currentThread().getName() + "," + count);
    }
    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        Thread thread1 = new Thread(threadCount);
        Thread thread2 = new Thread(threadCount);
        thread1.start();
        thread2.start();
    }
}

2.3 修饰静态方法

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得 当前类对象的锁

默认使用当前类的类名.class 锁

public class ThreadCount implements Runnable {
    private static Integer count = 100;
    private static String lock = "lock";

    @Override
    public void run() {
        while (count > 1) {
            cal();
        }
    }

    private static void cal() {
        synchronized (ThreadCount.class) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            count--;
            System.out.println(Thread.currentThread().getName() + "," + count);
        }

    }


    public static void main(String[] args) {
        ThreadCount threadCount1 = new ThreadCount();
        ThreadCount threadCount2 = new ThreadCount();
        Thread thread1 = new Thread(threadCount1);
        Thread thread2 = new Thread(threadCount2);
        thread1.start();
        thread2.start();
    }
}

2.4 synchronized死锁问题

我们如果在使用synchronized 需要注意 synchronized锁嵌套的问题 避免死锁的问题发生。

案例:

public class DeadlockThread implements Runnable {
    private int count = 1;
    private String lock = "lock";

    @Override
    public void run() {
        while (true) {
            count++;
            if (count % 2 == 0) {
                // 线程1需要获取 lock 在获取 a方法this锁
                // 线程2需要获取this 锁在 获取B方法lock锁
                synchronized (lock) {
                    a();
                }
            } else {
                synchronized (this) {
                    b();
                }
            }
        }
    }

    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + ",a方法...");
    }

    public void b() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ",b方法...");
        }
    }

    public static void main(String[] args) {
        DeadlockThread deadlockThread = new DeadlockThread();
        Thread thread1 = new Thread(deadlockThread);
        Thread thread2 = new Thread(deadlockThread);
        thread1.start();
        thread2.start();
    }
}

synchronized 死锁诊断工具

D:\path\jdk\jdk8\bin\jconsole.exe

线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁

线程2 先获取this锁, 进入到b方法需要自定义对象的lock锁

线程1 线程2 是在同时执行

线程1 线程2
先获取到自定义对象的lock锁 先获取this锁
需要线程2已经持有的this锁 线程1已经持有自定义对象的lock锁

2.5 springmvc 接口中使用

需要注意:

Spring MVC Controller默认是单例的 需要注意线程安全问题

单例的原因有二:

1、为了性能。

2、不需要多例。

@Scope(value = "prototype") 设置为多例子。

@RestController
@Slf4j
//@Scope(value = "prototype")
public class CountService {

    private int count = 0;

    @RequestMapping("/count")
    public synchronized String count() {
        try {
            log.info(">count<" + count++);
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
        } catch (Exception e) {

        }
        return "count";
    }
}

2.6 临界区

当多个线程读共享资源 读的过程中,没有任何问题,

在多个线程对共享资源读写操作时发生指令交错,就会发生线程安全问题

在多线程中如果存在对共享资源读写操作,该代码称作为临界区。

public class Thread08 extends Thread {
    int count = 0;

    @Override
    public void run() {
        // 该代码就是为临界区
        count++ ;
    }
}

2.7 竞争条件

多个线程在临界区内执行,由于代码的执行序列不同(指令)而导致结果无法预测,称之为发生了竞态条件

解决办法:

synchronized,Lock、原子类

3. 字节码角度分析线程安全问题

线程安全问题:

  1. 字节码
  2. 上下文切换
  3. Jmm java内存模型

Java源代码 →编译成class文件

3.1 线程安全代码

public class Thread02 extends Thread {
    private static int sum = 0;

    @Override
    public void run() {
        sum();
    }

    public void sum() {
        for (int i = 0; i < 10000; i++) {
            sum++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread02 t1 = new Thread02();
        Thread02 t2 = new Thread02();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(sum);
    }
}

3.2 字节码文件分析

javap -p -v Thread01.class

sum++

Getstatic ### 获取静态变量值sum

iconst_1 ## 准备一个常量1

Iadd ### 自增

Putstatic ### 将修改后的值存入静态变量sum

3.3 Cpu上下文角度分析线程安全问题

分析:

共享变量值 sum=0

假设现在cpu执行到t1线程,t1线程执行到13行 就切换到另外t2线程执行,

t2线程将静态变量sum=0改成=sum=1 有切换回来执行t1线程 t1线程 使用之前获取

Sum=0 +1 赋值给共享变量sum ,则最终结果:sum=1.

但是现在sum++ 执行 最终结果是算了一次。

二. 多线程线程之间通讯

1. 等待/通知机制

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:

  1. notify() :通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁
  2. notifyAll():通知所有等待在该对象的线程
  3. wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.mayikt.thread.days02.Thread03.run(Thread03.java:16)

注意:wait,notify和notifyAll要与synchronized一起使用

2. wait/notify/notifyAll在Object类中

因为我们在使用synchronized锁 对象锁可以是任意对象,所以wait/notify/notifyAll需要放在Object类中。

3. wait/notify/简单的用法

public class Thread03 extends Thread {
    @Override
    public void run() {
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + ">>当前线程阻塞,同时释放锁!<<");
                this.wait();
            }
            System.out.println(">>run()<<");
        } catch (InterruptedException e) {

        }
    }

    public static void main(String[] args) {
        Thread03 thread = new Thread03();
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (Exception e) {

        }
        synchronized (thread) {
            // 唤醒正在阻塞的线程
            thread.notify();
        }

    }
}

4. 多线程通讯实现生产者与消费者

public class Thread04 {
    class Res {
        /**
         * 姓名
         */
        private String userName;
        /**
         * 性别
         */
        private char sex;
        /**
         * 标记
         */
        private boolean flag = false;
    }

    class InputThread extends Thread {
        private Res res;

        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    //flag = false  写入输入 flag = true 则不能写入数据 只能读取数据
                    try {
                        // 如果flag = true 则不能写入数据 只能读取数据 同时释放锁!
                        if (res.flag) {
                            res.wait();
                        }
                    } catch (Exception e) {

                    }
                    if (count == 0) {
                        this.res.userName = "余胜军";
                        this.res.sex = '男';
                    } else {
                        this.res.userName = "小薇";
                        this.res.sex = '女';
                    }
                    res.flag = true;
                    res.notify();
                }

                count = (count + 1) % 2;
            }
        }
    }

    class OutThread extends Thread {
        private Res res;

        public OutThread(Res res) {
            this.res = res;
        }


        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    try {
                        if (!res.flag) {
                            res.wait();
                        }
                    } catch (Exception e) {

                    }
                    System.out.println(res.userName + "," + res.sex);
                    res.flag = false;
                    res.notify();
                }
            }
        }
    }


    public static void main(String[] args) {
        new Thread04().print();
    }

    public void print() {
        Res res = new Res();
        InputThread inputThread = new InputThread(res);
        OutThread outThread = new OutThread(res);
        inputThread.start();
        outThread.start();
    }
}
/**
 * flag 默认值==false
 * flag false 输入线程 输入值    输出线程 先拿到锁 释放锁 
 * flag true 输出线程 输出值
 */
public boolean flag = false;

5. Join/Wait与sleep之间的区别

sleep(long)方法在睡眠时不释放对象锁

join(long)方法先执行另外的一个线程,在等待的过程中释放对象锁 底层是基于wait封装的,

Wait(long)方法在等待的过程中释放对象锁

6. 三个线程 T1,T2,T3,怎么确保它们按顺序执行?

Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ",线程执行"), "t1");
Thread t2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ",线程执行"), "t2");
Thread t3 = new Thread(() -> System.out.println(Thread.currentThread().getName() + ",线程执行"), "t3");
t1.start();
t2.start();
t3.start();
public class Thread05 {


    public    static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");
        }, "t1");
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");
        }, "t2");
        Thread t3 = new Thread(() -> {
            try {
                t2.join();
            } catch (InterruptedException e) {

            }
            System.out.println(Thread.currentThread().getName() + ",线程执行");
        }, "t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

7. Join的底层原理如何实现

public class Thread06 {
    private Object object = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread06 thread06 = new Thread06();
        Thread thread = thread06.print();
        thread.start();
        try {
            Thread.sleep(3000);
            thread.interrupt();
        } catch (Exception e) {

        }

    }

    public Thread print() {
        Thread thread = new Thread(() -> {
            synchronized (object) {
                System.out.println("1");
                try {
                    object.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("2");
            }
        });
        return thread;
    }
}

Join底层原理是基于wait封装的,唤醒的代码在jvm Hotspot 源码中 当

jvm在关闭线程之前会检测线阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就被唤醒了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容