benym的知识笔记 benym的知识笔记
🦮首页
  • Java

    • Java-基础
    • Java-集合
    • Java-多线程与并发
    • Java-JVM
    • Java-IO
  • Python

    • Python-基础
    • Python-机器学习
  • Kafka
  • Redis
  • MySQL
  • 分布式事务
  • Spring

    • SpringIOC
    • SpringAOP
🦌设计模式
  • 剑指Offer
  • LeetCode
  • 排序算法
🐧实践
  • Rpamis

    • Utils
    • Exception
    • Security
  • 归档
  • 标签
  • 目录
🦉里程碑
🐷关于
GitHub (opens new window)

benym

惟其艰难,才更显勇毅🍂惟其笃行,才弥足珍贵
🦮首页
  • Java

    • Java-基础
    • Java-集合
    • Java-多线程与并发
    • Java-JVM
    • Java-IO
  • Python

    • Python-基础
    • Python-机器学习
  • Kafka
  • Redis
  • MySQL
  • 分布式事务
  • Spring

    • SpringIOC
    • SpringAOP
🦌设计模式
  • 剑指Offer
  • LeetCode
  • 排序算法
🐧实践
  • Rpamis

    • Utils
    • Exception
    • Security
  • 归档
  • 标签
  • 目录
🦉里程碑
🐷关于
GitHub (opens new window)
  • Java-基础

    • Java反射获取类对象的三种方式
    • 动态代理
  • Java-集合

    • ArrayList的扩容机制
  • Java-多线程与并发

    • Java多线程实现的几种方式
    • 多线程交替打印数字—多种实现
      • CountDownLatch使用方法
      • CyclicBarrier使用方法
      • Semaphore使用方法
      • CompletableFuture常用用法及踩坑
    • Java-JVM

      • 自定义类加载器
      • JMH-基准测试框架
    • Java-IO

      • 概览
    • Java
    • Java-多线程与并发
    benym
    2022-01-14
    目录

    多线程交替打印数字—多种实现

    # 多线程交替打印数字—多种实现

    # 使用synchronized锁实现

    public class Test {
    
        public static void main(String[] args) {
            mutilThreadPrintNum m1 = new mutilThreadPrintNum();
            Thread thread1 = new Thread(m1);
            Thread thread2 = new Thread(m1);
            thread1.setName("奇数");
            thread2.setName("偶数");
            thread1.start();
            thread2.start();
        }
    }
    
    class mutilThreadPrintNum implements Runnable {
    
        int num = 1;
    
        @Override
        public void run() {
            synchronized (this) {
                while (true) {
                    // 唤醒wait()的一个或所有线程
                    notify();
                    if (num <= 100) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        num++;
                    } else {
                        break;
                    }
                    try {
                        // wait会释放当前的锁,让另一个线程可以进入
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # 打印结果
    奇数:1
    偶数:2
    奇数:3
    偶数:4
    ......
    奇数:99
    偶数:100
    
    1
    2
    3
    4
    5
    6
    7
    1
    2
    3
    4
    5
    6
    7

    通过加锁和notify()、wait()机制可以有效的实现两个线程分别打印奇数和偶数,但互斥锁始终会影响性能,效率不高。

    # 使用valatile标志位实现

    public class Test {
    
        static volatile boolean flag = true;
        static volatile int num = 1;
    
        public static void main(String[] args) {
            mutilThreadPrintNumOdd m1 = new mutilThreadPrintNumOdd();
            mutilThreadPrintNumEven m2 = new mutilThreadPrintNumEven();
            Thread thread1 = new Thread(m1);
            Thread thread2 = new Thread(m2);
            thread1.setName("奇数");
            thread2.setName("偶数");
            thread1.start();
            thread2.start();
        }
    
        public static class mutilThreadPrintNumOdd implements Runnable {
    
            @Override
            public void run() {
                while (num <= 100) {
                    if (flag) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        num++;
                        flag = false;
                    }
                }
            }
        }
    
        public static class mutilThreadPrintNumEven implements Runnable {
    
            @Override
            public void run() {
                while (num <= 100) {
                    if (!flag) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        num++;
                        flag = true;
                    }
                }
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    打印结果和上文相同,使用volatile关键字可以保证变量的可见性,但并不能保证num的原子性,即多个线程操作num时,他是非线程安全的,此处能够正确打印的原因是因为flag标志位的判断。相对于加锁来说,效率更高

    # 使用AtomicInteger+LockSupport实现

    public class Test {
    
        public static AtomicInteger num = new AtomicInteger(1);
    
        public static Thread thread1 = null, thread2 = null;
    
        public static void main(String[] args) {
            mutilThreadPrintNumOdd m1 = new mutilThreadPrintNumOdd();
            mutilThreadPrintNumEven m2 = new mutilThreadPrintNumEven();
            thread1 = new Thread(m1);
            thread2 = new Thread(m2);
            thread1.setName("奇数");
            thread2.setName("偶数");
            thread1.start();
            thread2.start();
    
        }
    
        public static class mutilThreadPrintNumOdd implements Runnable {
    
            @Override
            public void run() {
                while (true) {
                    if (num.get() % 2 != 0 && num.get() <= 100) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        // 原子递增
                        num.incrementAndGet();
                        // 获取许可,阻塞其他线程
                        LockSupport.park();
                    } else {
                        // 释放许可,并将许可传递到偶数线程
                        LockSupport.unpark(thread2);
                    }
                }
            }
        }
    
        public static class mutilThreadPrintNumEven implements Runnable {
    
            @Override
            public void run() {
                while (true) {
                    if (num.get() % 2 == 0 && num.get() <= 100) {
                        System.out.println(Thread.currentThread().getName() + ":" + num);
                        // 原子递增
                        num.incrementAndGet();
                        // 获取许可,阻塞其他线程
                        LockSupport.park();
                    } else {
                        // 释放许可,并将许可传递到奇数线程
                        LockSupport.unpark(thread1);
                    }
                }
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56

    使用JUC包内的AtomicInteger保持多线程并发安全,同时采用LockSupport唤醒或阻塞线程

    # 踩坑日志

    第三种实现方法一开始并不是正确的,如果LockSupport.park()方法放在如下位置

    @Override
    public void run() {
        while (true) {
            // 获取许可,阻塞其他线程
            LockSupport.park();
            if (num.get() % 2 != 0 && num.get() <= 100) {
                System.out.println(Thread.currentThread().getName() + ":" + num);
                // 原子递增
                num.incrementAndGet();
            } else {
                // 释放许可,并将许可传递到偶数线程
                LockSupport.unpark(thread2);
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    那么程序将会发生死锁,因为两个线程都持有当前线程的许可,并没有等待到释放许可的执行,当我们把断点放在奇数和偶数获取许可的代码段上时,会发现奇数线程先获取了许可,还没来得及执行if判断,偶数线程也获得了许可,此时程序没有任何打印。 奇数线程:

    奇数线程

    偶数线程:

    偶数线程

    此时我们采用jps命令找到当前线程的pid

    jps

    之后采用jstack pid命令分析当前线程的堆栈信息,可以发现奇数线程和偶数线程都处于WAITING状态,他们都在等待对方释放锁或传递许可。所以正确的写法应该在if判断内,当打印之后便会阻塞当前线程,由于数字已经打印,再次循环时便会进入到else的判断逻辑,即当前线程发现不是属于自己该打印的数字就会尝试唤醒另一个线程。

    jstack
    编辑 (opens new window)
    #Java#多线程
    上次更新: 2023/05/13, 18:05:21
    Java多线程实现的几种方式
    CountDownLatch使用方法

    ← Java多线程实现的几种方式 CountDownLatch使用方法→

    最近更新
    01
    SpringCache基本配置类
    05-16
    02
    DSTransactional与Transactional事务混用死锁场景分析
    03-04
    03
    Rpamis-security-原理解析
    12-13
    更多文章>
    Theme by Vdoing | Copyright © 2018-2024 benym | MIT License
     |   |   | 
    渝ICP备18012574号 | 渝公网安备50010902502537号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式