保证多线程顺序执行,四种方案,你知道几种?

1. 前言

在多线程编程中,有时需要保证线程之间的执行顺序,这对于一些需要严格控制执行顺序的场景非常重要。本文将介绍四种保证多线程顺序执行的方案,并对其进行比较和分析。

2. 使用Thread.join()

2.1 Thread.join()方法说明

在Java中,每个线程实例都有一个join()方法,调用该方法可以使当前线程等待被调用线程执行完成。

具体地,调用线程调用被等待线程的join()方法后,它将进入阻塞状态,直到被等待线程执行完成,并结束其生命周期,调用线程才能继续执行。

下面是Thread.join()方法的签名:

public final void join() throws InterruptedException

其中,join()方法没有参数,它将当前线程阻塞直到被等待线程执行完成。如果被等待线程在阻塞期间被中断,则join()方法将抛出InterruptedException。

2.2 示例代码

下面是一个使用Thread.join()方法保证多线程顺序执行的示例代码:

public class ThreadJoinDemo {

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

for (int i = 0; i < 10; i++) {

System.out.println("Thread 1: " + i);

}

});

Thread t2 = new Thread(() -> {

for (int i = 0; i < 10; i++) {

System.out.println("Thread 2: " + i);

}

});

t1.start();

t1.join();

t2.start();

t2.join();

}

}

上述代码中,我们先创建了两个线程t1和t2,它们分别输出1到10的整数。接着我们先启动t1线程,然后调用t1.join()方法,使得t1线程先执行完成后,才能启动t2线程。

2.3 优缺点

使用Thread.join()方法保证多线程顺序执行的优点是容易实现,代码易读、易懂,并且无需使用额外的锁。然而,它的缺点是比较低效,因为它需要等待被等待线程执行完成后才能启动下一个线程,这样会增加总执行时间。

3. 使用synchronized关键字

3.1 synchronized关键字说明

synchronized关键字是Java中一个重要的同步机制,它能够给代码块、方法或类加锁,使得同一时间只有一个线程可以访问相关的代码块。在多线程编程中,synchronized关键字可以保证线程安全性。

具体地,当一个线程试图访问一个被synchronized关键字保护的代码块时,它必须先获得这个锁,如果锁已经被其他线程占用,则该线程会一直等待,直到获取到锁为止。

下面是synchronized关键字的基本使用方法:

public class SynchronizedDemo {

private int count = 0;

public synchronized void increment() {

count++;

}

public synchronized void decrement() {

count--;

}

}

在上述代码中,我们使用synchronized关键字给increment()和decrement()方法加锁,这样就保证了它们的原子性和线程安全性。

3.2 示例代码

下面是一个使用synchronized关键字保证多线程顺序执行的示例代码:

public class SynchronizedOrderDemo {

private int count = 0;

public synchronized void increment() {

while (count != 0) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count++;

System.out.println(Thread.currentThread().getName() + ": " + count);

notifyAll();

}

public synchronized void decrement() {

while (count == 0) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count--;

System.out.println(Thread.currentThread().getName() + ": " + count);

notifyAll();

}

public static void main(String[] args) {

SynchronizedOrderDemo demo = new SynchronizedOrderDemo();

Thread t1 = new Thread(() -> {

while (true) {

demo.increment();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 1");

Thread t2 = new Thread(() -> {

while (true) {

demo.decrement();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 2");

t1.start();

t2.start();

}

}

在上述代码中,我们使用synchronized关键字给increment()和decrement()方法加锁,并且在方法中使用while循环和wait()、notifyAll()方法实现了多线程的顺序执行。具体地,当count的值不满足条件时,线程会等待,直到满足条件时才会执行相应的操作。

3.3 优缺点

使用synchronized关键字保证多线程顺序执行的优点是代码实现简单,且能够保证线程的顺序执行,而且不会阻塞线程。然而,它的缺点是可能会产生死锁,同时它的效率相对于其他方案较低。

4. 使用Lock和Condition

4.1 Lock和Condition说明

在Java中,Lock和Condition是一种高级的同步机制,它可以替代synchronized关键字,并且提供了更丰富的方法来控制线程的同步。使用Lock和Condition可以更加灵活地实现多线程的顺序执行。

具体地,Lock是Java中的互斥锁,它相当于synchronized关键字,它可以保证同时只有一个线程能够获取到这个锁。Condition是一个线程等待唤醒机制,它可以让线程挂起等待某个条件被满足,或者唤醒一个或多个正在等待该条件的线程。

4.2 示例代码

下面是一个使用Lock和Condition保证多线程顺序执行的示例代码:

public class LockConditionDemo {

private Lock lock = new ReentrantLock();

private Condition condition = lock.newCondition();

private volatile int count = 0;

public void increment() {

lock.lock();

try {

while (count != 0) {

condition.await();

}

count++;

System.out.println(Thread.currentThread().getName() + ": " + count);

condition.signalAll();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

public void decrement() {

lock.lock();

try {

while (count == 0) {

condition.await();

}

count--;

System.out.println(Thread.currentThread().getName() + ": " + count);

condition.signalAll();

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

public static void main(String[] args) {

LockConditionDemo demo = new LockConditionDemo();

Thread t1 = new Thread(() -> {

while (true) {

demo.increment();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 1");

Thread t2 = new Thread(() -> {

while (true) {

demo.decrement();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 2");

t1.start();

t2.start();

}

}

在上述代码中,我们使用ReentrantLock创建了一个锁,并使用newCondition()方法创建了一个条件变量。在increment()和decrement()方法中,我们使用lock.lock()获取锁,然后使用while循环和condition.await()方法等待某个条件被满足,或者使用condition.signalAll()方法唤醒正在等待该条件的线程。

4.3 优缺点

使用Lock和Condition保证多线程顺序执行的优点是效率高,可以替代synchronized关键字,并且不容易产生死锁。同时,它的灵活性比synchronized关键字更高。缺点是使用Lock和Condition需要注意释放锁的问题,否则容易引起死锁。

5. 使用Semaphore

5.1 Semaphore说明

Semaphore是一种基于计数器的同步机制,在Java中,Semaphore可以用来控制同时访问某个特定资源的线程数。

具体地,Semaphore维护一个许可证集合,线程可以通过调用acquire()方法获取一个许可证,或者通过release()方法释放一个许可证。如果许可证已经全部被获取,则后续的线程等待直到有许可证被释放。

5.2 示例代码

下面是一个使用Semaphore保证多线程顺序执行的示例代码:

public class SemaphoreDemo {

private Semaphore semaphore = new Semaphore(1);

public void increment() {

try {

semaphore.acquire();

System.out.println(Thread.currentThread().getName() + ": " + 1);

semaphore.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public void decrement() {

try {

semaphore.acquire();

System.out.println(Thread.currentThread().getName() + ": " + 0);

semaphore.release();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public static void main(String[] args) {

SemaphoreDemo demo = new SemaphoreDemo();

Thread t1 = new Thread(() -> {

while (true) {

demo.increment();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 1");

Thread t2 = new Thread(() -> {

while (true) {

demo.decrement();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, "Thread 2");

t1.start();

t2.start();

}

}

在上述代码中,我们创建了一个Semaphore实例,它的初始值为1。在increment()和decrement()方法中,我们使用semaphore.acquire()获取许可证,然后输出相应的数值,最后使用semaphore.release()释放许可证。

5.3 优缺点

使用Semaphore保证多线程顺序执行的优点是效率较高,并且能够灵活地控制线程数量,同时实现代码较为简单。不足之处在于,当许可证的数量比较大时,会降低控制线程数的精度。

6. 总结

本文介绍了四种保证多线程顺序执行的方案,并对其进行比较和分析。Thread.join()方法是最简单的实现方式,但效率较低;synchronized关键字实现起来比较简单,并且能够保证线程的顺序执行,但可能会产生死锁;使用Lock和Condition可以替代synchronized关键字,同时效率较高,但需要注意释放锁的问题;Semaphore可以灵活地控制线程数量,但需要注意许可证数量的设置。

具体选择何种方案,需要视需求而定,根据实际场景进行选择。

后端开发标签