
本教程深入探讨java多线程环境下,一个线程递增变量,另一个线程周期性打印的实现方法。文章阐述了共享变量的挑战及java内存模型,并提供了两种线程安全方案:利用atomicinteger进行原子操作以确保数据一致性,以及通过linkedblockingqueue实现生产者-消费者模式进行线程间通信,从而有效解决并发问题。
在多线程编程中,实现一个线程递增共享变量,而另一个线程周期性地读取并打印该变量的值,是一个常见的需求。然而,直接在多个线程间共享一个基本类型变量(如int)并进行读写操作,往往会遇到线程安全问题,导致数据不一致或不可预测的结果。本教程将深入探讨这一挑战,并提供两种健壮且常用的解决方案。
当多个线程同时访问和修改同一个变量时,如果不采取适当的同步机制,就会出现问题。这主要是因为Java虚拟机(JVM)为了优化性能,可能会对指令进行重排序,或者允许每个线程拥有变量的本地缓存副本。这意味着一个线程对变量的修改,可能不会立即对其他线程可见。
考虑以下一个简单的、不安全的共享变量示例:
class UnsafeCounter {
int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
public class UnsafeSharedVariableDemo {
public static void main(String[] args) throws InterruptedException {
UnsafeCounter unsafeCounter = new UnsafeCounter();
// 线程1:递增计数器
Thread incrementer = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
unsafeCounter.increment();
}
System.out.println("Incrementer finished. Final count (from incrementer's perspective): " + unsafeCounter.getCounter());
});
// 线程2:周期性打印计数器
Thread printer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Printer read counter: " + unsafeCounter.getCounter());
Thread.sleep(100); // 每100毫秒打印一次
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
incrementer.start();
printer.start();
incrementer.join(); // 等待递增线程结束
printer.join(); // 等待打印线程结束
System.out.println("Main thread: Final counter value: " + unsafeCounter.getCounter());
}
}在上述代码中,printer线程可能会多次打印出相同的旧值,甚至在incrementer线程已经修改了counter之后。这是因为Java内存模型(JMM)规定,没有同步机制时,一个线程对共享变量的修改不保证对其他线程立即可见。为了解决这个问题,我们需要建立“Happens-Before”关系,确保操作的可见性和有序性。
立即学习“Java免费学习笔记(深入)”;
java.util.concurrent.atomic包中的原子类提供了无锁的线程安全操作。它们通过使用底层的硬件指令(如CAS,Compare-And-Swap)来保证操作的原子性,并确保变量的修改对所有线程立即可见,从而建立Happens-Before关系。AtomicInteger是处理整数计数的理想选择。
以下是使用AtomicInteger实现线程安全计数器和周期性打印的示例:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounterDemo {
private final AtomicInteger counter = new AtomicInteger(0); // 使用AtomicInteger作为共享计数器
public void startThreads() {
// 线程1:递增计数器
Thread incrementerThread = new Thread(() -> {
try {
System.out.println("Incrementer thread started.");
for (int i = 0; i < 50; i++) {
counter.incrementAndGet(); // 原子性递增
Thread.sleep(50); // 模拟工作负载
}
System.out.println("Incrementer thread finished. Final count: " + counter.get());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Incrementer thread interrupted.");
}
}, "Incrementer");
// 线程2:周期性打印计数器
Thread printerThread = new Thread(() -> {
try {
System.out.println("Printer thread started.");
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Current counter value: " + counter.get()); // 原子性获取
Thread.sleep(200); // 每200毫秒打印一次
}
} catch (InterruptedException e) {
System.out.println("Printer thread interrupted. Exiting loop.");
} finally {
System.out.println("Printer thread finished.");
}
}, "Printer");
incrementerThread.start();
printerThread.start();
// 等待递增线程完成
try {
incrementerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 递增线程完成后,中断打印线程
printerThread.interrupt();
try {
printerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All threads finished. Final counter value from main: " + counter.get());
}
public static void main(String[] args) {
new AtomicCounterDemo().startThreads();
}
}工作原理与优点:
除了直接共享变量,另一种更解耦、更健壮的线程间通信方式是使用消息队列。一个线程作为“生产者”将数据放入队列,另一个线程作为“消费者”从队列中取出数据。java.util.concurrent包提供了多种并发集合,其中LinkedBlockingQueue是一个常用的选择,它支持阻塞式的存取操作,非常适合实现生产者-消费者模式。
以下是使用LinkedBlockingQueue实现计数器和周期性打印的示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class MessageQueueCounterDemo {
private final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10); // 容量为10的阻塞队列
public void startThreads() {
// 线程1:生产者 - 递增并放入队列
Thread producerThread = new Thread(() -> {
try {
System.out.println("Producer thread started.");
for (int i = 1; i <= 50; i++) {
queue.put(i); // 将当前计数值放入队列,如果队列满则阻塞
System.out.println("Producer added: " + i + ", Queue size: " + queue.size());
Thread.sleep(50); // 模拟生产数据的耗时
}
System.out.println("Producer finished adding 50 items. Signaling consumer to stop.");
queue.put(-1); // 发送一个特殊值作为结束信号
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Producer thread interrupted.");
} finally {
System.out.println("Producer thread finished.");
}
}, "Producer");
// 线程2:消费者 - 周期性从队列取出并打印
Thread consumerThread = new Thread(() -> {
try {
System.out.println("Consumer thread started.");
while (true) {
Integer value = queue.take(); // 从队列取出数据,如果队列空则阻塞
if (value == -1) { // 收到结束信号
System.out.println("Consumer received termination signal. Exiting.");
break;
}
System.out.println("Consumer retrieved: " + value + ", Queue size: " + queue.size());
Thread.sleep(200); // 模拟消费数据的耗时
}
} catch (InterruptedException e) {
System.out.println("Consumer thread interrupted. Exiting loop.");
} finally {
System.out.println("Consumer thread finished.");
}
}, "Consumer");
producerThread.start();
consumerThread.start();
// 等待生产者和消费者线程完成
try {
producerThread.join();
consumerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("All threads finished. Final queue size: " + queue.size());
}
public static void main(String[] args) {
new MessageQueueCounterDemo().startThreads();
}
}工作原理与优点:
在Java中实现多线程共享变量并周期性输出,关键在于正确处理线程安全和数据可见性。
原子类(AtomicInteger等):
消息队列(LinkedBlockingQueue等):
在实际开发中,应根据具体的业务需求、数据特性和性能要求来选择合适的并发工具。java.util.concurrent包是Java并发编程的核心,其中包含了大量强大的工具,如Semaphore、CountDownLatch、CyclicBarrier、ExecutorService等,深入学习这些工具将极大地提升多线程编程的能力。始终记住,并发编程是复杂的,彻底理解Java内存模型和Happens-Before关系是编写正确、高效并发代码的基础。
以上就是Java多线程安全共享变量与周期性输出实践指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号