同时启动3个线程从1开始打印递增数字, 每次每个线程打印3次,且按线程顺序打印,打印到36程序结束

效果:

线程1:1
线程1:2
线程1:3
线程2:4
线程2:5
线程2:6
线程3:7
线程3:8
线程3:9
线程1:10
线程1:11
线程1:12

线程3:34
线程3:35
线程3:36

实现 思路:基于 synchronized 的 wait, notifyAll

  1. 同时开启三个线程,分别为这三个线程设置name,1 2 3.。
  2. 定义一个共享的Runnable实现类。
  3. 定义一把共享锁对象,或者使用 this锁对象。(必须是三个线程共享的锁)
  4. white循环,自增数num小于36。
  5. 业务代码执行前,synchronized获取锁。
  6. 获取锁成功的线程,判断num是否大于等于36。(避免线程是从阻塞状态唤醒,获取锁,而num >= 36的情况)。
  7. 判断线程名称是否是可以执行业务的线程。顺序是 线程1执行完再执行线程2再执行线程3,然后又执行线程1,保证顺序输出。
  8. 如果当前线程不能执行业务,wait,释放锁,且暂停当前线程。
  9. 如果是可执行业务线程,循环三次输出num++,将可执行线程name++,列如当前线程name = 1,则下一个可执行线程name = 2。
  10. 业务执行完毕 唤醒阻塞线程。

注意:
阻塞线程被唤醒,必须还要判断当前线程是否是可执行线程。且还要判断num是否大于等于36。


实现代码

public class Test {

    // num 相加后的最大值
    private final int MAX_VALUE = 36;

    // num 自增次数
    private final int INCREMENT_COUNT = 3;

    // 线程相加的变量 num   保证可见性
    private volatile int num;

    // 当前可以进行操作的线程 name  保证可见性
    private volatile int currThread = 1;

    /**
     * i++ 线程的 run方法
     */
    public class MyRun implements Runnable {
        @Override
        public void run() {
            while (num < MAX_VALUE) {  // num < MAX_VALUE 循环
                // 使用this锁
                synchronized (this) {

                    // 此处有可能是阻塞线程被唤醒 可能 num 已经达到 MAX_VALUE 了
                    if (num >= MAX_VALUE) {
                        return;
                    }

                    // 获取线程名称
                    String name = Thread.currentThread().getName();
                    // 死循环
                    for (; ;) {   // 该循环体 控制被唤醒的线程  继续判断当前线程是否是可执行线程
                        if (Integer.parseInt(name) != currThread) {  // 当前线程name 不是可执行操作的线程 进入休眠
                            try {
                                this.wait();

                                // 此处是被唤醒了 才会执行
                                if (num >= MAX_VALUE) {   // 被唤醒后判断 num 是否已经达到 MAX_VALUE了
                                    return;
                                }
                                // num < MAX_VALUE
                                // 再次进入循环体   目的就是判断当前线程是否可执行
                            } catch (InterruptedException e) {
                            }
                        } else {     // 是可执行的线程 跳出循环体
                            break;
                        }
                    }

                    int j = num + INCREMENT_COUNT;  // num + 3
                    while (num < j) {  // 循环三次 输出 i++
                        System.out.println("当前线程" + name + ": " + ++num);
                    }

                    currThread++;     // 将可执行线程的 name加一
                    if (currThread > INCREMENT_COUNT) {
                        currThread = 1;  // 重置可执行线程为 初始
                    }

                    this.notifyAll();   // 唤醒阻塞线程
                }
            }
        }
    }

    /**
     * 启动三个线程
     */
    public void run() {
        // 每个线程共用此 Runnable 实例
        MyRun myRun = new MyRun();

        Thread t1 = new Thread(myRun);
        t1.setName("1");

        Thread t2 = new Thread(myRun);
        t2.setName("2");

        Thread t3 = new Thread(myRun);
        t3.setName("3");

        t1.start();
        t2.start();
        t3.start();
    }

    public static void main(String[] args) {
        new Test().run();
    }
}

优点

  • 代码简单,通俗易懂,不依赖其他工具包。

缺点

  • 唤醒线程使用notifyAll,唤醒所有线程,但是只能有一个线程执行。
  • 被唤醒的线程,在死循环体中,判断是否可执行,如果不能执行,又暂停,从而导致某个线程会(启动 -> 暂停 -> 唤醒 -> 暂停 -> 唤醒),线程的唤醒与暂停对CPU来说开销还是有点大的。


其他解决方案:

从题目出发:

  • 同时开启三个线程。
  • 每个线程输出num++,且是连续的。
  • 线程执行必须是顺序性的:线程1 -> 线程2 -> 线程3 -> 线程1 以此类推。

以上内容看出,线程的执行是串行的,那就可以这样构思,线程1执行完去唤醒线程2,线程2执行完去唤醒线程3,线程3执行完去唤醒线程1,始终都只有一个线程在执行,这样还降低了线程安全问题。

实现 思路。 基于 LockSupport 唤醒/暂停线程

  1. 用数组或集合,维护需要运行的线程。可保证线程的运行顺序。
  2. 首先启动索引0的线程。
  3. 所有业务逻辑代码在 while (num < 36) 循环体中。
  4. 执行完业务逻辑,唤醒/启动下一个索引的线程。
  5. 计算出当前线程是否是最后一次执行,如果是:业务执行完毕。如果不是,线程暂停,等待唤醒。

实现代码

import java.util.concurrent.locks.LockSupport;

public class TestMain {

    /** num 数自增后最大数值 */
    private final int MAX_VALUE = 36;

    /** 每次每个线程 对 num 自增多少次 */
    private final int INCREMENT_COUNT = 3;

    /** 启动多少个线程 */
    private final int THREAD_NUM = 3;

    /** 自增 num 保证线程可见性 */
    private volatile int num;

    /** 启动自增 num 线程的数组 */
    private Thread[] threads = new Thread[THREAD_NUM];

    /* 启动 */
    public static void main(String[] args) {
        new TestMain().run();
    }

    /** 执行   */
    public void run() {
        // 每个线程共用此 Runnable 实例
        MyRunnable myRunnable = new MyRunnable();

        for (int i = 0; i < THREAD_NUM; i++) {
            // 创建线程
            Thread thread = new Thread(myRunnable);
            // 设置线程名称
            thread.setName(String.valueOf(i + 1));
            // 缓存线程
            threads[i] = thread;
        }
        // 先启动第一个线程  线程 name = 1
        threads[0].start();
    }

    /**
     * 运行下一个线程
     */
    public void runNextThread() {
        // 计算下一个执行线程的 数组索引
        int index = num / 3 % 3;

        // 下一个执行的线程
        Thread nextThread = threads[index];
        if (nextThread.getState() == Thread.State.NEW)
            // 线程从未启动过  启动线程
            nextThread.start();
        else
            // 唤醒下一个线程
            LockSupport.unpark(nextThread);
    }

    /**
     * 线程的 Runnable
     */
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            while (num < MAX_VALUE) {
                int j = num + INCREMENT_COUNT;  // num + 3
                while (num < j) {  // 循环三次 输出 ++num
                    System.out.println("当前线程" + Thread.currentThread().getName() + ": " + ++num);
                }

                // 运行下一个线程
                runNextThread();

                // MAX_VALUE - THREAD_NUM * INCREMENT_COUNT
                // 36 - 3 * 3 = 27
                if (num > MAX_VALUE - THREAD_NUM * INCREMENT_COUNT)
                    // 计算 num 是否到了线程的最后一次执行自增  是的话退出循环
                    return;
                else
                    // 暂停线程
                    LockSupport.park(Thread.currentThread());

            }
        }
    }
}

优点

  • 代码简洁,易懂,run方法里不需要对数值进行双重判断。
  • 不需要加锁。
  • 每次只唤醒一个线程执行,避免了唤醒多个线程的开销。

缺点

  • 未知

总结

实现方法有很多种,其实选择什么实现方法,思路都差不多。最主要的是要理解多线程编程的思维。多线程最重要的就是思维和想象力,可以去力扣官网刷题提升自己。

  • 应充分的去想象多线程同时在执行的效果。
  • 应如何把多线程的并行控制成串行
  • 应如何把多线程的并行控制成顺序串行
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Me_Liu_Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值