Java线程之间的共享与协作详解
Java中的线程通常需要协作和共享资源。在这篇文章中,我们将学习如何共享数据以及如何在多个线程之间协作。我们将从基本的概念开始,从一个简单的示例开始,然后深入研究线程的同步和互斥机制。最后,我们将介绍Java5中的并发工具包。
线程之间的数据共享
Java中的线程可以访问自己创建的对象。如果你创建了一个对象并将其传递给多个线程,则这些线程可以同时访问该对象。
例如,考虑以下代码片段:
class MyObject {
private int counter = 0;
public int getCounter() {
return counter;
}
public void incrementCounter() {
counter++;
}
}
在这里,我们定义了一个类MyObject,它有一个计数器字段和两个方法:getCounter()和incrementCounter()。注意,counter字段是私有的,这意味着它只能由该类中的方法访问。
如果我们创建了多个线程来访问MyObject对象,则它们可以同时访问counter字段,因为它是MyObject的实例字段。例如,我们可以使用以下代码片段:
MyObject obj = new MyObject();
Runnable r = () -> {
for (int i = 0; i < 1000000; i++) {
obj.incrementCounter();
}
};
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(obj.getCounter());
这段代码创建了两个线程,它们都调用obj.incrementCounter()方法1000000次。最后,我们打印出了计数器的最终值。由于两个线程同时增加计数器的值,最终的计数器值应该是2000000。
线程之间的协同
除了共享数据外,线程之间还需要协作。例如,一个线程可能需要等待另一个线程完成某项任务。在Java中,你可以使用以下构造来实现线程之间的协作:
wait():使线程等待某些条件满足。
notify():通知等待的线程某些条件已经满足。
notifyAll():通知等待的线程所有条件已经满足。
这些方法只能在同步代码块中调用,而同步代码块要么使用synchronized方法,要么使用synchronized块。
让我们看一个例子。假设我们有两个线程,一个生产者和一个消费者,它们共享一个队列。
class MyQueue {
private List<String> buffer = new ArrayList<>();
public synchronized void put(String value) {
buffer.add(value);
notify();
}
public synchronized String take() {
while (buffer.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
// ignore
}
}
return buffer.remove(0);
}
}
在这里,我们定义了一个类MyQueue,它有一个buffer字段和两个方法:put()和take()。put()方法允许生产者将元素添加到队列中,而take()方法允许消费者从队列中获取元素。注意,put()和take()方法都是synchronized方法,这将使这些方法成为临界区域,这些方法在同时只能被一个线程执行。此外,put()和take()方法都包含wait()和notify()方法。
当生产者调用put()方法时,它将元素添加到队列中并调用notify()方法。notify()方法将通知正在等待的线程,即消费者线程,某些条件已经满足。当一个消费者调用take()方法时,它将在队列为空时一直等待。在等待期间,消费者线程调用wait()方法进入等待状态。当一个生产者调用put()方法并添加元素时,它将调用notify()方法,这将通知消费者线程,队列中有新元素可用。
同步和互斥
上面的示例中,我们使用了synchronized方法来实现同步和互斥。实际上,Java提供了多种不同的同步和互斥机制。
synchronized方法和synchronized块:这些机制可以保证同一时间只有一个线程可以在临界区域中执行。当然,它们比较简单,但也比较局限。
ReentrantLock:这是Java5中引入的一个新特性。它提供了更灵活的方式来管理锁。它允许线程获取锁并尝试多次获取锁。它还提供了一些高级功能,如超时、可中断锁和公平性设置。
Semaphore:它允许多个线程同时访问某个资源。例如,你可以使用Semaphore管理有限的数据库连接池,这样多个线程可以同时获取数据库连接。
CountDownLatch:它允许一个或多个线程等待一个或多个其他线程完成操作。例如,你可以创建一个CountDownLatch,该CountDownLatch等待一组线程完成它们的操作,然后实现一个主要的启动器线程,该主要的启动器线程等待这些线程的完成。
CyclicBarrier:它允许一组线程等待彼此操作完成。例如,你可以创建一个CyclicBarrier,该CyclicBarrier等待多个线程完成相同的操作,然后实现一个主要的启动器线程,该主要的启动器线程等待这些线程完成其操作。
Java5的并发工具包
随着Java5的到来,Java引入了一个新的并发工具包。这个包提供了一些高级的、线程安全的数据结构和锁,例如:ConcurrentHashMap,BlockingQueue,Semaphore,CountDownLatch等等。通过使用这些新的工具,你可以更容易地实现高性能和线程安全的应用程序。
结论
Java中的线程可以轻松地共享数据和协同工作。我们可以使用wait()和notify()方法来实现线程之间的协同,而synchronized块和synchronized方法可以用来保证同步和互斥。此外,Java5引入了一个新的并发工具包,它提供了一些高级特性来更轻松地实现并发应用程序。
