当前位置:   article > 正文

多线程顺序执行四种方案_多线程保证顺序执行

多线程保证顺序执行

一、方案一(join)

public static void main(String[] args) {
    final Thread t1 = new Thread(() -> System.out.println("线程1执行"));

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

    final Thread t3 = new Thread(() -> {
      try {
        t2.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("线程3执行");
    });

    t3.start();
    t2.start();
    t1.start();
  }
  • 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

首先执行了t3.start(),在t3线程里的run方法里执行了t2.join(),此时有两种情况,可能还没有执行t2.start(),t2处于初始状态,也有可能执行了t2.start(),t2处于运行时状态,所以看到这里就明白了,join源码中while(isAlive()),其实相当于while(this.isAlive())就相当于判断这里的t2是不是已经是不是运行状态(有没有调用start方法)。这么设计是为了防止t2线程没有运行,此时t3线程如果直接执行wait(0)方法,那么将会一直等待下去,造成代码卡死。

为了验证,代码改为:

        t3.start();
        try {
		Thread.sleep(10);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
        t2.start();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

此时的输出:

t3
t2
  • 1
  • 2

分析:将t2.start()和t3.start()之间的时间间隔变长,这样在t3线程中不会执行t2.join()时,保证了t2时处于初始状态还不是运行状态,此时while(isAlive())不成立,不会执行wait(0)方法,所以t3线程不会等待,会先输出t3。

然后再更改代码如下,保证在执行t2.join()时,t2.start()已经执行,t2已经处于运行状态:

public class HighConcurrency {
    public static void main(String[] args) {
        final Thread t2 = new Thread(new Runnable() {
 
            @Override
            public void run() {         
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                	Thread.sleep(10);    //保证此时t2已经是运行时状态了
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();
        t2.start();
    }
}
  • 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

此时的输出:

t2
t3
  • 1
  • 2

分析:在t3线程中执行t2.join()方法前先执行了sleep(10)方法,保证在执行t2.join()时,t2已经是运行时状态了,所以此时t3会执行wait(0)方法等待,直到t2先执行完,t3再继续执行,所以输出t2 t3。

1 synchronized中的对象锁是线程的实例

Object obj = new Object();
synchronized(obj){
   obj.wait();    //线程在这里等待
}
  • 1
  • 2
  • 3
  • 4

此时线程会在obj.wait()处等待,如果想继续执行,此时需要别的线程通过notify、notifyAll唤醒或者中断。但是如果obj是一个线程实例会怎么样呢?

如下面的例子:

  public static void main(String[] args) {
    Thread threadTest = new Thread(() -> {
      System.out.println("执行线程中方法");
      try {
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    });
    threadTest.start();
    synchronized (threadTest) {
      threadTest.wait();    //当线程终止的时候,会调用线程自身的notifyAll()方法
    }
    System.out.println("执行到了这里");
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

输出:

执行线程中方法
执行到了这里
  • 1
  • 2

首先开始线程threadTest,在主线程执行到threadTest.wait()时,主线程会等待,奇怪的是主线程并没有别的线程使用notify或notifyAll方法唤醒,竟然直接执行了后面的语句"执行了这里"。查阅发现如果synchronized获得对象锁是线程的实例时,此时比较特殊,当该线程终止的时候,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。

应用场景:开启一个子线程计算一段数据,在主线程中输出计算结果。

原理:利用了线程实例做对象锁时,在线程执行完后,会调用线程自身的notifyAll()方法,此时主线程会接着执行,用处可以控制线程的执行顺序

2 Join的原理

源码

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
 
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {             //如果时执行的join(0)
            while (isAlive()) {        //调用join方法线程是运行时状态
                wait(0);               //进入等待
            }
        } else {                       //如果是执行的join(time)
            while (isAlive()) {               //如果线程时运行状态
                long delay = millis - now;    
                if (delay <= 0) {
                    break;
                }
                wait(delay);                 //等待delay时间后自动返回继续执行
                now = System.currentTimeMillis() - base;
            }
        }
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

其实wait()方法就是调用了wait(0)方法实现的,wait(0)就是让其一直等待。到这里会发现,其实join方法本质就是利用上面的线程实例作为对象锁的原理,当线程终止时,会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程的特征。

二、方案二(CountDownLatch

CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
举例:在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。CountDownLatch里面N个线程就是学生,学生做完了试卷就可以走了,不用等待其他的学生是否完成

  public static void main(String[] args) {
    System.out.println("全体学生开始考试,一共有两个学生");
    new Thread(() -> {
      System.out.println("第一个学生开始交卷,countDownLatch减1");
      countDownLatch.countDown();
    }).start();

    new Thread(() -> {
      System.out.println("第二个学生开始交卷,countDownLatch减1");
      countDownLatch.countDown();
    }).start();

    countDownLatch.await();
    System.out.println("老师清点试卷,在此之前,只要有一个学生没交即countDownLatch不为0时,不能离开考场");
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述

在上面,我们定义了一个CountDownLatch,并设置其值为2。有两个学生使用两个线程来表示,然后依次执行。最后老师线程(main线程)在学生线程都执行完了才可以执行。CountDownLatch要从2一直减到0,主线程(main线程)才可以执行,否则的话,主线程要一直等待

三、方案三(newSingleThreadExecutor)

该线程池类似于单线程执行,所以先执行完前一个任务后,再顺序执行下一个任务。
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,

  • 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  public static void main(String[] args) {
    Thread t1 = new Thread(() -> System.out.println("线程1"), "线程1");
    Thread t2 = new Thread(() -> System.out.println("线程2"), "线程2");
    Thread t3 = new Thread(() -> System.out.println("线程3"));
    ExecutorService executor = Executors.newSingleThreadExecutor();
    // 将线程依次加入到线程池中
    executor.submit(t1);
    executor.submit(t2);
    executor.submit(t3);
    // 及时将线程池关闭
    executor.shutdown();
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

四、方案四(CompletableFuture)

  public static void main(String[] args) {
    Thread t1 = new Thread(() -> System.out.println("线程1"), "线程1");
    Thread t2 = new Thread(() -> System.out.println("线程2"), "线程2");
    Thread t3 = new Thread(() -> System.out.println("线程3"));
    CompletableFuture.runAsync(() -> t1.start())
        .thenRun(() -> t2.start())
        .thenRun(() -> t3.start());
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/197806
推荐阅读
相关标签
  

闽ICP备14008679号