JUC第三讲:Java 并发-线程基础

news2025/4/13 5:36:24

JUC第三讲:Java 并发-线程基础

本文是JUC第三讲,主要概要性的介绍线程的基础,为后面的章节深入介绍Java并发的知识提供基础。

文章目录

  • JUC第三讲:Java 并发-线程基础
    • 1、带着BAT大厂的面试问题去理解
    • 2、线程状态转换
      • 2.1、新建(New)
      • 2.2、可运行(Runnable)
      • 2.3、阻塞(Blocking)
      • 2.4、无限期等待(Waiting)
      • 2.5、限期等待(Timed Waiting)
      • 2.6、死亡(Terminated)
    • 3、线程使用方式
      • 3.1、实现 Runnable 接口
      • 3.2、实现 Callable 接口
      • 3.3、继承 Thread 类
      • 3.4、使用线程池
      • 3.5、实现接口 VS 继承 Thread
    • 4、基础线程机制
      • 4.1、Executor
      • 4.2、Daemon
      • 4.3、sleep()
      • 4.4、yield()
    • 5、线程中断
      • 5.1、InterruptedException
      • 5.2、interrupted()
      • 5.3、Executor 的中断操作
    • 6、线程互斥同步
      • 6.1、synchronized
      • 6.2、ReentrantLock
      • 6.3、比较
      • 6.4、使用选择
    • 7、线程之间的协作 面试必备
      • 7.1、join()
      • 7.2、Object的wait()/notify()/notifyAll()
      • 7.3、Condition的await()/signal()/signalAll()
      • 7.4、LockSupport 的park()/unpark()
    • Action1:有ABC 3 个线程,线程C需要等待线程AB执行完成才能执行?

1、带着BAT大厂的面试问题去理解

请带着这些问题继续后文,会很大程度上帮助你更好的理解线程基础。

  • 线程有哪几种状态? 分别说明从一种状态到另一种状态转变有哪些方式?
  • 通常线程有哪几种使用方式?
  • 基础线程机制有哪些?
  • 线程的中断方式有哪些?
  • 线程的互斥同步方式有哪些? 如何比较和选择?
  • 线程之间有哪些协作方式?

2、线程状态转换

image

2.1、新建(New)

创建后尚未启动。

2.2、可运行(Runnable)

可能正在运行,也可能正在等待 CPU 时间片。

包含了操作系统线程状态中的 Running 和 Ready

2.3、阻塞(Blocking)

等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

2.4、无限期等待(Waiting)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入方法退出方法
没有设置 Timeout 参数的 Object.wait() 方法Object.notify() / Object.notifyAll()
没有设置 Timeout 参数的 Thread.join() 方法被调用的线程执行完毕
LockSupport.park() 方法-

2.5、限期等待(Timed Waiting)

无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。

调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。

睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入

进入方法退出方法
Thread.sleep() 方法时间结束
设置了 Timeout 参数的 Object.wait() 方法时间结束 / Object.notify() / Object.notifyAll()
设置了 Timeout 参数的 Thread.join() 方法时间结束 / 被调用的线程执行完毕
LockSupport.parkNanos() 方法-
LockSupport.parkUntil() 方法-

2.6、死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束

3、线程使用方式

常见的Java线程的4种创建方式分别为:

  • 实现 Runnable 接口;
  • 通过ExecutorService和Callable实现有返回值的线程
  • 继承 Thread 类;
  • 基于线程池。

如下图所示:
在这里插入图片描述

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的

3.1、实现 Runnable 接口

实现 Runnable 接口创建线程类,实现该类的 run()方法,基于Java编程语言的规范,如果子类已经继承(extends)了一个类,就无法再直接继承Thread类,此时可以通过实现Runnable接口创建线程。具体的实现过程为:通过实现Runnable接口创建ChildrenClassThread 线程,实例化名称为childrenThread的线程实例,创建Thread类的实例并传入childrenThread线程实例,调用线程的start方法启动线程。

  • 通过 Thread 调用 start() 方法来启动线程。
public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}
  • 优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想;
  • 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法

3.2、实现 Callable 接口

  • 通过 Callable 和 Future 接口创建线程。
    • Callable接口,是一种让线程执行完成后,能够返回结果的

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

3.3、继承 Thread 类

通过继承 Thread 类创建线程类,该子类重写Thread类的 run方法,Thread类实现了Runnable接口并定义了操作线程的一些方法,我们可以通过继承Thread类的方式创建一个线程

  • 当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}
  • 优点:编写简单,如果需要访问当前线程,无需使用 Thread.currentThread()方法,直接使用this,即可获得当前线程;
  • 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

3.4、使用线程池

  • 使用线程池

  • 具体见 JUC第十六讲:深入理解 Java 中的线程池

3.5、实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

4、基础线程机制

4.1、Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executor:

  • CachedThreadPool: 一个任务创建一个线程;
  • FixedThreadPool: 所有任务只能使用固定大小的线程;
  • SingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool。
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 5; i++) {
        executorService.execute(new MyRunnable());
    }
    executorService.shutdown();
}

4.2、Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

使用 setDaemon() 方法将一个线程设置为守护线程

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true);
}

4.3、sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

public void run() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

4.4、yield()

对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

public void run() {
    Thread.yield();
}

5、线程中断

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。

5.1、InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞

对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

public class InterruptExample {
    private static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("Thread run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new MyThread1();
    thread1.start();
    thread1.interrupt();
  	// 下面这行代码不会被执行
    System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at InterruptExample.lambda$main$0(InterruptExample.java:5)
    at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

5.2、interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的interrupt() 方法就无法使线程提前结束。

但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

public class InterruptExample {
    private static class MyThread2 extends Thread {
        @Override
        public void run() {
            while (!interrupted()) {
                // ..
            }
            System.out.println("Thread end");
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread thread2 = new MyThread2();
    thread2.start();
    thread2.interrupt();
}
Thread end

5.3、Executor 的中断操作

调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。

以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程

public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> {
        try {
            Thread.sleep(2000);
            System.out.println("Thread run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    executorService.shutdownNow();
    System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
    at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

Future<?> future = executorService.submit(() -> {
    // ..
});
future.cancel(true);

6、线程互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

6.1、synchronized

1. 同步一个代码块

public void func() {
    synchronized (this) {
        // ...
    }
}

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步

对于以下代码,使用 ExecutorService 执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须等待。

public class SynchronizedExample {

    public void func1() {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
    }
}

public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func1());
    executorService.execute(() -> e1.func1());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

对于以下代码,两个线程调用了不同对象的同步代码块,因此这两个线程就不需要同步。从输出结果可以看出,两个线程交叉执行。

public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    SynchronizedExample e2 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func1());
    executorService.execute(() -> e2.func1());
}
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

2. 同步一个方法

public synchronized void func () {
    // ...
}

它和同步代码块一样,作用于同一个对象。

3. 同步一个类

public void func() {
    synchronized (SynchronizedExample.class) {
        // ...
    }
}

作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步

public class SynchronizedExample {

    public void func2() {
        synchronized (SynchronizedExample.class) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
    }
}

public static void main(String[] args) {
    SynchronizedExample e1 = new SynchronizedExample();
    SynchronizedExample e2 = new SynchronizedExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> e1.func2());
    executorService.execute(() -> e2.func2());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

4. 同步一个静态方法

public synchronized static void fun() {
    // ...
}

作用于整个类。

6.2、ReentrantLock

ReentrantLock 是 java.util.concurrent(JUC)包中的锁。

public class LockExample {
    private Lock lock = new ReentrantLock();
    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
          	// 确保释放锁,从而避免发生死锁。
            lock.unlock();
        }
    }
}

public static void main(String[] args) {
    LockExample lockExample = new LockExample();
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> lockExample.func());
    executorService.execute(() -> lockExample.func());
}
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

6.3、比较

1. 锁的实现

synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

3. 等待可中断

当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情

ReentrantLock 可中断,而 synchronized 不行。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 可以同时绑定多个 Condition 对象。

6.4、使用选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放

7、线程之间的协作 面试必备

当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调

7.1、join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出

public class JoinExample {

    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }

    private class B extends Thread {

        private A a;

        B(A a) {
            this.a = a;
        }

        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }

    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
}
public static void main(String[] args) {
    JoinExample example = new JoinExample();
    example.test();
}
A
B

7.2、Object的wait()/notify()/notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

public class WaitNotifyExample {
    public synchronized void before() {
        System.out.println("before");
        notifyAll();
    }

    public synchronized void after() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    WaitNotifyExample example = new WaitNotifyExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

7.3、Condition的await()/signal()/signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

使用 Lock 来获取一个 Condition 对象。

public class AwaitSignalExample {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void before() {
        lock.lock();
        try {
            System.out.println("before");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    AwaitSignalExample example = new AwaitSignalExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}
before
after

7.4、LockSupport 的park()/unpark()

使用park/unpark实现线程同步

class MyThread extends Thread {
    private Object object;

    public MyThread(Object object) {
        this.object = object;
    }

    public void run() {
        System.out.println("before unpark");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));
        // 释放许可
        LockSupport.unpark((Thread) object);
        // 休眠500ms,保证先执行park中的setBlocker(t, null);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再次获取blocker
        System.out.println("Blocker info " + LockSupport.getBlocker((Thread) object));

        System.out.println("after unpark");
    }
}

public class test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(Thread.currentThread());
        myThread.start();
        System.out.println("before park");
        // 获取许可
        LockSupport.park("ParkAndUnparkDemo");
        System.out.println("after park");
    }
}

运行结果:

before park
before unpark
Blocker info ParkAndUnparkDemo
after park
Blocker info null
after unpark

具体可以参考这篇文章:JUC第八讲:JUC锁: LockSupport详解

Action1:有ABC 3 个线程,线程C需要等待线程AB执行完成才能执行?

方法一:LockSupport + AtomicInteger

  • 先执行线程C,用 park() 挂起线程C,线程A、B各自执行完成时,flag 减1并判断是否为0,若为0则用unpark( c )给线程C 颁发许可
public static void main(String[] args) {
        AtomicInteger flag = new AtomicInteger(2);
        Thread c =  new Thread(()->{
            System.out.println("线程C开启,等待线程A、B执行完成才继续执行");
            LockSupport.park();
            System.out.println("线程C开始执行");
        });
        c.start();

        new Thread(()->{
            System.out.println("线程A开始执行");
            try {
                TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程A执行完成");
            if (flag.decrementAndGet() == 0){
            	//唤醒指定线程
                LockSupport.unpark(c);
            }
        }).start();
        
        new Thread(()->{
            System.out.println("线程B开始执行");
            try {
                TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B执行完成");
            if (flag.decrementAndGet() == 0){
                LockSupport.unpark(c);
            }
        }).start();
}

方法二:CountDownLatch

  • CountDownLatch 有一个计数器,countDown方法 对计数器做减操作,await 方法等待计数器达到0。所有await的线程都会阻塞直到计数器为0或者等待线程中断或者超时
public static void main(String[] args) {   
		CountDownLatch latch = new CountDownLatch(2);
    new Thread(() -> {
        System.out.println("线程A开始执行");
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程A执行完成");
    }).start();

    new Thread(() -> {
        System.out.println("线程B开始执行");
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程B执行完成");
    }).start();

    new Thread(() -> {
        System.out.println("线程C开启,等待线程A、B执行完成才继续执行");
        try {
            latch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程C执行完成");
    }).start();
}

方法三:CyclicBarrier

CyclicBarrier 与 CountDownLatch 类似 ,它能阻塞一组线程全部到某个状态再同时执行。 CyclicBarrier 与 CountDownLatch 的关键区别在于,所有的线程必须全部到达位置,才能继续执行。CountDownLatch 用于等待事件,而 CyclicBarrier 用于等待其他线程,在任意一个线程没有完成之前,所有线程都不能继续执行。

public static void main(String[] args) {
    CyclicBarrier barrier = new CyclicBarrier(3);
    //只有所有线程执行到了 await(),所有线程才会继续往下执行

    new Thread(() -> {
        System.out.println("线程A开始执行");
        try {
            //执行业务
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            System.out.println("线程A执行完成,等待其它线程一起冲破栅栏");
            barrier.await();
        } catch (Exception e) {
          	e.printStackTrace();
        }
        System.out.println("线程A执行完成");
    }).start();

    new Thread(() -> {
        System.out.println("线程B开始执行");
        try {
            //执行业务
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            System.out.println("线程B执行完成,等待其它线程一起冲破栅栏");
            barrier.await();
        } catch (Exception e) {
          	e.printStackTrace();
        }
        System.out.println("线程B执行完成");
    }).start();

    new Thread(() -> {
        try {
            System.out.println("线程C开启,等待线程AB执行完成一起冲破栅栏");
            barrier.await();
            //执行业务
        } catch (Exception e) {
          	e.printStackTrace();
        }
        System.out.println("线程C执行完成");
    }).start();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1021703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 开发人员转CGE(castle Game engine)城堡游戏引擎指导手册

Unity 开发人员的城堡游戏引擎概述 一、简介2. Unity相当于什么GameObject&#xff1f;3. 如何设计一个由多种资产、生物等组成的关卡&#xff1f;4. 在哪里放置特定角色的代码&#xff08;例如生物、物品&#xff09;&#xff1f;Unity 中“向 GameObject 添加 MonoBehaviour”…

权限提升数据库(基于MySQL的UDF,MOF,启动项提权)

获取数据库权限 如何获取数据库的最高权限用户的密码&#xff0c;常用方法有这些 网站存在高权限SQL注入点 数据库的存储文件或备份文件 网站应用源码中的数据库配置文件 采用工具或脚本爆破 网站存在高权限SQL注入点 可以通过sqlmap拿到user表的账号密码&#xff0c;密码可能…

短视频矩阵系统源码开发分享

①账号的建立与发布频率 要根据品牌的定位和特点&#xff0c;结合平台的特点和用户需求&#xff0c;制作符合品牌及个人形象的账号名称和内容发布主旨&#xff0c;以在短视频平台建立起自身标签&#xff0c;从而提升品牌知名度和美誉度。 发文频率也很关键&#xff0c;发文频…

新增MariaDB数据库管理、支持多版本MySQL数据库共存,1Panel开源面板v1.6.0发布

2023年9月18日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.6.0版本。 在这个版本中&#xff0c;1Panel新增MariaDB数据库管理&#xff1b;支持多版本MySQL数据库共存&#xff1b;支持定时备份系统快照和应用商店中已安装应用&#xff1b;支持为防火墙…

零代码编程:用ChatGPT批量下载网站中的特定网页内容

http://blog.umd.edu/davidkass这个网站上有伯克希尔股东大会的一些文字稿&#xff0c;其标题如下&#xff1a; Notes From the Berkshire Hathaway 2020 Annual Meeting – May 2, 2020 Notes From the Berkshire Hathaway 2021 Annual Meeting – May 1, 2021 在右边的搜索…

MySQL 篇

目录 1、数据库三范式 2、数据库事务的特性 3、MySQL数据库引擎 4、说说 InnoDB 与 MyISAM 的区别 5、索引是什么&#xff1f; 6、索引数据结构 7、MySQL 索引类型有哪些&#xff1f; 8、索引有什么优缺点&#xff1f; 9、使用索引应该注意些什么&#xff1f; …

(图论) 827. 最大人工岛 ——【Leetcode每日一题】

❓ 827. 最大人工岛 难度&#xff1a;困难 给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。 返回执行此操作后&#xff0c;grid 中最大的岛屿面积是多少&#xff1f; 岛屿 由一组上、下、左、右四个方向相连的 1 形成。 示例 1: 输入: grid [[1, 0]…

redisplusplus笔记

redis与连接 Redis处理命令 connection主要方法及与reply关系 connection只支持移动语义&#xff0c;不支持拷贝和赋值 recv使用ReplyUPtr&#xff0c;即unique_ptr<redisReply, ReplyDeleter>,其中ReplyDeleter定义如下 struct ReplyDeleter {void operator()(redis…

从0搭建夜莺v6基础监控告警系统(二):采集数据、打通夜莺显示

文章目录 1. 写在前面1.1. categraf 采集数据1.2. 官方文档传送门 2. 配置过程2.1. 打通夜莺和 VictoriaMetrics2.2. 配置 Categraf2.3. 验证结果2.4. 配置仪表盘 3. 部署总结3.1. 操作总结3.2. 仪表盘展示 上一操作我们已经安装好了所需的基础服务&#xff0c;接下来需要打通各…

AI项目八:yolo5+Deepsort实现目标检测与跟踪(CPU版)

若该文为原创文章&#xff0c;转载请注明原文出处。 一、DeepSORT简介 DeepSORT 是一种计算机视觉跟踪算法&#xff0c;用于在为每个对象分配 ID 的同时跟踪对象。DeepSORT 是 SORT&#xff08;简单在线实时跟踪&#xff09;算法的扩展。DeepSORT 将深度学习引入到 SORT 算法中…

Android.bp常用语法和预定义属性

介绍 Android.bp是Android构建系统中用于定义模块和构建规则的配置文件&#xff0c;它使用一种简单的声明式语法。以下是Android.bp的一些常见语法规则和约定&#xff1a; 注释&#xff1a; 单行注释使用//符号。 多行注释使用/和/包围。 和go语言相同 // 这是单行注释 /* 这是…

爆破shadow文件密码脚本(完成版)

在之前的博客Python爆破shadow文件密码脚本&#xff08;简化版&#xff09;中我们做了简化版的爆破shadow文件密码的python脚本&#xff0c;接下来在之前代码的基础上改进&#xff1a; import crypt shadow_line"root:$y$j9T$uEgezfJhn7Ov5naU8bzZt.$9qIqkWYObaXajS5iLDA…

charles报错Not allowed GET http://xx.xx.com/xx - connection dropped

现象&#xff1a;手机抓包时&#xff0c;charles提示Not allowed GET http://xx.xx.com/xx - connection&#xff0c;请求status显示block 排查原因&#xff1a; 1、换手机连接抓包工具&#xff0c;现象也是同上&#xff0c;可以排除手机的原因 2、检索网络上关于报错的解决方…

【HCIE】08.MPLS VPN跨域AB

MPLS VPN跨域A ASBR之间交换IPV4路由&#xff0c;采用IPVR数据包转发数据。该方式易于理解 跨域的要求 两个ASBR之间不能开启LDP&#xff0c;可以开启MPLS 因为两个路由器处于不同的AS之间&#xff0c;之间的IGP是不能互通的&#xff0c;之前是没有路由的 之所以中间不能开…

Java | 网络编程

不爱生姜不吃醋⭐️ 如果本文有什么错误的话欢迎在评论区中指正 与其明天开始&#xff0c;不如现在行动&#xff01; 文章目录 &#x1f334;前言&#x1f334;一、网络编程1.概念2.常见软件架构 &#x1f334;二、网络编程三要素1. IP2. 端口号3.协议 &#x1f334;总结 &…

Mybatis学习笔记7 参数处理专题

Mybatis学习笔记6 使用时的一些小技巧_biubiubiu0706的博客-CSDN博客 1.单个简单类型参数 2.Map参数 3.实体类参数 4.多参数 5.Param注解(命名参数) 6.Param源码分析 建表 插入点数据 新建模块 pom.xml <?xml version"1.0" encoding"UTF-8"?&…

STM32 USB CDC 虚拟串口

// 用虚拟串口(USB CDC VCP)感觉有些不稳定&#xff0c;尤其是下位机掉电后再上电&#xff0c;上位机虚拟的那个串口根本不能用&#xff0c;还有就是 // 必须等虚拟串口出来后且知道串口号上位机才可以执行打开操作// 上面是实际情况&#xff0c;但并不是STM32的USB不行&#x…

JUnit5单元测试提示“Not tests were found”错误

JUnit5单元测试提示“Not tests were found”错误&#xff0c;如下图所示&#xff1a; 或者 问题解析&#xff1a; 1&#xff09;使用Test注解时&#xff0c;不能有返回值&#xff1b; 2&#xff09;使用Test注解时&#xff0c;不能使用private关键字&#xff1b; 存在以上情…

C语言——贪吃蛇小游戏

目录 一、ncurse 1.1 为什么需要用ncurse&#xff1a; 1.2 ncurse的输入输出&#xff1a; 1.2.1 如何使用ncurse&#xff1a; 1.2.2 编译ncurse的程序&#xff1a; 1.2.3 测试输入一个按键ncurse的响应速度&#xff1a; 1.3 ncurse上下左右键获取&#xff1a; 1.3.1 如…

移动 Web 第一天

目标&#xff1a;使用位移、缩放、旋转、渐变效果丰富网页元素的呈现方式。 文章目录 01-平面转换简介平移定位居中案例-双开门旋转转换原点案例-时钟多重转换缩放案例-播放特效倾斜 02-渐变线性渐变案例-产品展示径向渐变 03-综合案例导航-频道箭头旋转频道列表 渐变按钮搜索…