多线程打印奇偶数,怎么控制打印的顺序
可以利用wait()和notify()来控制线程的执行顺序。
以下是一个基于这种方法的简单示例:
public class PrintOddEven {
private static final Object lock = new Object();
private static int count = 1;
private static final int MAX_COUNT = 10;
public static void main(String[] args) {
Runnable printOdd = () -> {
synchronized (lock) {
while (count <= MAX_COUNT) {
if (count % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Runnable printEven = () -> {
synchronized (lock) {
while (count <= MAX_COUNT) {
if (count % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + count++);
lock.notify();
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Thread oddThread = new Thread(printOdd, "OddThread");
Thread evenThread = new Thread(printEven, "EvenThread");
oddThread.start();
evenThread.start();
}
}
在上面的示例中,通过一个共享的锁对象lock来控制两个线程的交替执行。一个线程负责打印奇数,另一个线程负责打印偶数,通过wait()和notify()方法来在两个线程之间实现顺序控制。当当前应该打印奇数时,偶数线程会进入等待状态,反之亦然。
- 创建 3 个并发执行的线程,在每个线程的任务结束时调用
countDown
方法将计数器减 1。 - 创建第 4 个线程,使用
await
方法等待计数器为 0,即等待其他 3 个线程完成任务。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
// 创建一个 CountDownLatch,初始计数为 3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动 3 个并发线程
for (int i = 0; i < 3; i++) {
final int threadNumber = i + 1;
new Thread(() -> {
try {
System.out.println("Thread " + threadNumber + " is working.");
// 模拟线程执行任务
Thread.sleep((long) (Math.random() * 1000));
System.out.println("Thread " + threadNumber + " has finished.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 任务完成后,计数器减 1
latch.countDown();
}
}).start();
}
// 创建并启动第 4 个线程,等待其他 3 个线程完成
new Thread(() -> {
try {
System.out.println("Waiting for other threads to finish.");
// 等待计数器为 0
latch.await();
System.out.println("All threads have finished, this thread starts to work.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
代码解释:
- 首先,创建了一个
CountDownLatch
对象latch
,并将其初始计数设置为 3。 - 然后,使用
for
循环创建并启动 3 个线程。每个线程会执行一些工作(这里使用Thread.sleep
模拟),在工作完成后,会调用latch.countDown()
方法,将latch
的计数减 1。 - 最后,创建第 4 个线程。这个线程在开始时调用
latch.await()
方法,它会阻塞,直到latch
的计数为 0,即前面 3 个线程都调用了countDown()
方法。一旦计数为 0,该线程将继续执行后续任务。
#单例模型既然已经用了synchronized,为什么还要在加volatile?
使用 synchronized
和 volatile
一起,可以创建一个既线程安全又能正确初始化的单例模式,避免了多线程环境下的各种潜在问题。这是一种比较完善的线程安全的单例模式实现方式,尤其适用于高并发环境。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
synchronized
关键字的作用用于确保在多线程环境下,只有一个线程能够进入同步块(这里是 synchronized (Singleton.class)
)。在创建单例对象时,通过 synchronized
保证了创建过程的线程安全性,避免多个线程同时创建多个单例对象。
volatile
确保了对象引用的可见性和创建过程的有序性,避免了由于指令重排序而导致的错误。
instance = new Singleton();
这行代码并不是一个原子操作,它实际上可以分解为以下几个步骤:
- 分配内存空间。
- 实例化对象。
- 将对象引用赋值给
instance
。
由于 Java 内存模型允许编译器和处理器对指令进行重排序,在没有 volatile
的情况下,可能会出现重排序,例如先将对象引用赋值给 instance
,但对象的实例化操作尚未完成。
这样,其他线程在检查 instance == null
时,会认为单例已经创建,从而得到一个未完全初始化的对象,导致错误。
volatile
可以保证变量的可见性和禁止指令重排序。它确保对 instance
的修改对所有线程都是可见的,并且保证了上述三个步骤按顺序执行,避免了在单例创建过程中因指令重排序而导致的问题。
#3个线程并发执行,1个线程等待这三个线程全部执行完在执行,怎么实现?
可以使用 CountDownLatch
来实现 3 个线程并发执行,另一个线程等待这三个线程全部执行完再执行的需求。以下是具体的实现步骤:
- 创建一个
CountDownLatch
对象,并将计数器初始化为 3,因为有 3 个线程需要等待。 - 创建 3 个并发执行的线程,在每个线程的任务结束时调用
countDown
方法将计数器减 1。 - 创建第 4 个线程,使用
await
方法等待计数器为 0,即等待其他 3 个线程完成任务。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
// 创建一个 CountDownLatch,初始计数为 3
CountDownLatch latch = new CountDownLatch(3);
// 创建并启动 3 个并发线程
for (int i = 0; i < 3; i++) {
final int threadNumber = i + 1;
new Thread(() -> {
try {
System.out.println("Thread " + threadNumber + " is working.");
// 模拟线程执行任务
Thread.sleep((long) (Math.random() * 1000));
System.out.println("Thread " + threadNumber + " has finished.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 任务完成后,计数器减 1
latch.countDown();
}
}).start();
}
// 创建并启动第 4 个线程,等待其他 3 个线程完成
new Thread(() -> {
try {
System.out.println("Waiting for other threads to finish.");
// 等待计数器为 0
latch.await();
System.out.println("All threads have finished, this thread starts to work.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
代码解释:
- 首先,创建了一个
CountDownLatch
对象latch
,并将其初始计数设置为 3。 - 然后,使用
for
循环创建并启动 3 个线程。每个线程会执行一些工作(这里使用Thread.sleep
模拟),在工作完成后,会调用latch.countDown()
方法,将latch
的计数减 1。 - 最后,创建第 4 个线程。这个线程在开始时调用
latch.await()
方法,它会阻塞,直到latch
的计数为 0,即前面 3 个线程都调用了countDown()
方法。一旦计数为 0,该线程将继续执行后续任务。
#假设两个线程并发读写同一个整型变量,初始值为零,每个线程加 50 次,结果可能是什么?
在没有任何同步机制的情况下,两个线程并发对同一个整型变量进行 50 次加 1 操作,最终结果可能是 100,也可能小于 100,最坏的结果是 50,也就是最终的结果可能是在 [50, 100] 。
小于 100 情况的分析,由于对整型变量的 num++
操作不是原子操作,它实际上包含了三个步骤:读取变量的值、将值加 1、将新值写回变量。在多线程环境下,可能会出现线程安全问题。例如,线程 1 和线程 2 同时读取了变量的当前值,然后各自将其加 1,最后都将相同的新值写回变量,这就导致了一次加 1 操作的丢失。这种情况会多次发生,最终结果就会小于 100。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerAddition {
private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
num.incrementAndGet();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
num.incrementAndGet();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果: " + num.get());
}
}
第二种方式:通过 synchronized
关键字或 ReentrantLock
确保操作的互斥性,代码如下:
public class SynchronizedAddition {
private static int num = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
synchronized (lock) {
num++;
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
synchronized (lock) {
num++;
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果: " + num);
}
}