Java学习之线程同步与线程间通信详解
为什么需要线程同步和线程间通信
在多线程编程中,由于多个线程可能同时执行同一任务,可能会导致竞态条件(Race Condition)的出现,即数据被多个线程同时修改,从而导致程序运行出错。为了避免这种情况,需要通过线程同步机制来协调多个线程的共同操作。
而线程间通信则是线程同步机制的一种实现方式,它可以让线程之间传递消息或者其它数据,从而协调彼此的执行顺序。
关键字synchronized
Java中的synchronized关键字用于实现线程同步,可以用来修饰方法或者代码块。
当一个线程访问一个被synchronized修饰的方法或代码块时,需要获得对象的锁,如果此时锁被其它线程持有,则这个线程就会被阻塞等待锁的释放。只有获得锁的线程才能执行synchronized修饰的代码块,其它线程需要等待。
示例一:使用synchronized关键字实现线程同步
class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
counter.decrement();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter.getCount()); // 0
}
}
上述代码中,Counter类表示一个计数器,increment()方法用于增加计数器的值,decrement()方法用于减少计数器的值。由于这两个方法被synchronized修饰,所以它们的访问是互斥的,即同一时间只有一个线程可以执行其中的方法,从而避免了竞态条件的出现。
在main方法中,我们创建了两个线程,分别用于对count变量进行加操作和减操作,最终输出的结果应该为0,证明线程同步机制有效地保证了count的正确性。
示例二:获得对象锁实现线程同步
除了对方法或代码块使用synchronized之外,还可以通过获得对象锁的方式实现线程同步。通过synchronized(obj)来获取obj对象的锁,从而执行同步代码块。
class PrintThread implements Runnable {
private Object lock;
public PrintThread(Object lock) {
this.lock = lock;
}
public void run() {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
public class Main {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(new PrintThread(lock));
Thread thread2 = new Thread(new PrintThread(lock));
thread1.start();
thread2.start();
}
}
上述代码中,我们通过创建一个包含10个元素的数组,并将它传递给两个线程使用synchronized(lock)协调彼此的顺序。由于syncronized(lock)语句块的存在,两个线程的执行顺序被互相协调,最终输出结果为:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
线程间通信
在线程间通信中,涉及到两个关键方法wait()和notify()/notifyAll()。
wait()
wait()方法使当前线程进入等待状态,并释放持有的对象锁,直到其它线程调用了相同对象的notify()或notifyAll()方法来唤醒线程。
notify()/notifyAll()
notify()方法会随机唤醒一个正在等待相同对象锁的线程,而notifyAll()方法会唤醒所有正在等待相同对象锁的线程。
示例三:使用wait()和notify()实现简单线程间通信
class Producer implements Runnable {
private List<Integer> buffer;
public Producer(List<Integer> buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (buffer) {
buffer.add(i);
System.out.println("Producer produced " + i);
buffer.notify();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private List<Integer> buffer;
public Consumer(List<Integer> buffer) {
this.buffer = buffer;
}
public void run() {
while (true) {
synchronized (buffer) {
if (!buffer.isEmpty()) {
int num = buffer.remove(0);
System.out.println("Consumer consumed " + num);
} else {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Main {
public static void main(String[] args) {
List<Integer> buffer = new ArrayList<>();
Thread producerThread = new Thread(new Producer(buffer));
Thread consumerThread = new Thread(new Consumer(buffer));
producerThread.start();
consumerThread.start();
}
}
上述代码中,我们通过创建一个包含10个元素的List,并交替地向其中添加和删除元素实现了基本的线程间通信。在Producer类中,我们通过synchronized(buffer)来获得对象锁,然后调用了buffer.notify()方法唤醒正在等待buffer对象锁的线程;在Consumer类中,我们通过synchronized(buffer)来获得对象锁,然后调用了buffer.wait()方法释放对象锁,并进入等待状态,等待其它线程调用notify()方法来唤醒它。
在Main方法中,我们创建了共享的buffer对象,并将它传递给Producer和Consumer线程,从而实现了两者之间的基本通信。
综合应用
线程同步和线程间通信是多线程编程中最基础和也是最关键的知识点,仅仅阅读理论是远远不够的,需要我们通过实际操作来掌握和理解。建议读者通过实践来加深对于线程同步和线程间通信这两个知识点的理解。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java学习之线程同步与线程间通信详解 - Python技术站