美文网首页java高级Java多线程
多线程如何实现同步-多线程之间通讯

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

作者: 弹钢琴的崽崽 | 来源:发表于2021-07-08 20:56 被阅读0次

    一. 什么是线程安全问题

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

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

    全局变量----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就被唤醒了。

    相关文章

      网友评论

        本文标题:多线程如何实现同步-多线程之间通讯

        本文链接:https://www.haomeiwen.com/subject/olotpltx.html