在本节中,我们将详细讨论Java中的线程同步,包括synchronized
关键字的使用、wait()
和notify()
方法的使用以及java.util.concurrent
包中的高级同步工具。我们将通过实例来讲解每个知识点,确保你能够全面理解。
我们已经在上一节简要介绍了synchronized
关键字的使用。在本节中,我们将详细讨论synchronized
关键字。
synchronized
关键字可以修饰方法或者代码块,它能确保同一时刻只有一个线程可以执行被修饰的代码。当一个线程进入被synchronized
关键字修饰的方法或代码块时,其他线程将无法访问该方法或代码块,直到第一个线程执行完毕。
示例:银行账户类
public class BankAccount { private double balance; public BankAccount(double balance) { this.balance = balance; } public synchronized void deposit(double amount) { balance += amount; } public synchronized void withdraw(double amount) { balance -= amount; } public synchronized double getBalance() { return balance; } }
在上述示例中,我们使用synchronized
关键字修饰了deposit()
、withdraw()
和getBalance()
方法。这样,在同一时刻,只有一个线程可以访问这些方法。
示例:银行账户类
public class BankAccount { private double balance; public BankAccount(double balance) { this.balance = balance; } public void deposit(double amount) { synchronized (this) { balance += amount; } } public void withdraw(double amount) { synchronized (this) { balance -= amount; } } public double getBalance() { synchronized (this) { return balance; } } }
在上述示例中,我们使用synchronized
关键字修饰了代码块,实现了和修饰方法相同的效果。
wait()
、notify()
和notifyAll()
方法用于线程间的通信。这些方法是java.lang.Object
类的成员,因此每个Java对象都拥有这些方法。
wait()
方法用于让当前线程等待,直到其他线程调用该对象的notify()
或notifyAll()
方法。在调用wait()
方法时,当前线程会释放对象锁,进入等待状态。
notify()
方法用于唤醒在此对象监视器上等待的单个线程。如果有多个线程在等待,只会唤醒其中一个线程。
notifyAll()
方法用于唤醒在此对象监视器上等待的所有线程。
示例:生产者和消费者问题
import java.util.LinkedList; import java.util.Queue; class ProducerConsumer { private Queue<Integer> queue = new LinkedList<>(); private final int MAX_SIZE = 5; public void produce() { synchronized (this) { while (queue.size() == MAX_SIZE) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int value = (int) (Math.random() * 100); System.out.println("Produced: " + value); queue.add(value); notify(); } } public void consume() { synchronized (this) { while (queue.isEmpty()) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int value = queue.poll(); System.out.println("Consumed: " + value); notify(); } } } public class ProducerConsumerExample { public static void main(String[] args) { ProducerConsumer pc = new ProducerConsumer(); Thread producerThread = new Thread(() -> { while (true) { pc.produce(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread consumerThread = new Thread(() -> { while (true) { pc.consume(); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } }); producerThread.start(); consumerThread.start(); } }
在上述示例中,我们实现了一个生产者和消费者问题的解决方案。生产者和消费者共享一个队列,生产者向队列中添加数据,消费者从队列中取出数据。produce()
和consume()
方法使用synchronized
关键字保证线程安全,wait()
和notify()
方法用于线程间的通信。
java.util.concurrent
包提供了许多高级的并发工具,如Semaphore
、CountDownLatch
、CyclicBarrier
、ReentrantLock
等。这些工具可以帮助我们更容易地编写线程安全的代码。
Semaphore
用于限制可以访问某些资源(或者执行某些操作)的线程数量。它有一个计数器,当一个线程获得许可时,计数器会减1;当线程释放许可时,计数器会加1。
示例:限制同时访问文件的线程数量
import java.util.concurrent.Semaphore; public class FileAccessController { private Semaphore semaphore; public FileAccessController(int maxConcurrentAccess) { semaphore = new Semaphore(maxConcurrentAccess); } public void readFile() { try { semaphore.acquire(); System.out.println("Thread " + Thread.currentThread().getName() + " is reading file..."); Thread.sleep(2000); System.out.println("Thread " + Thread.currentThread().getName() + " finished reading file."); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } } public static void main(String[] args) { FileAccessController controller = new FileAccessController(3); for (int i = 0; i < 10; i++) { new Thread(() -> { controller.readFile(); }, "Thread-" + i).start(); } } }
在上述示例中,我们使用Semaphore
限制了同时访问文件的线程数量。当线程数量超过限制时,其他线程必须等待。
ReentrantLock
是一种可重入的互斥锁,与synchronized
关键字类似,但提供了更多的灵活性。ReentrantLock
允许我们显示地加锁和解锁,还支持公平锁和非公平锁。
示例:银行账户类
import java.util.concurrent.locks.ReentrantLock; public class BankAccount { private double balance; private ReentrantLock lock = new ReentrantLock(); public BankAccount(double balance) { this.balance = balance; } public void deposit(double amount) { lock.lock(); try { balance += amount; } finally { lock.unlock(); } } public void withdraw(double amount) { lock.lock(); try { balance -= amount; } finally { lock.unlock(); } } public double getBalance() { lock.lock(); try { return balance; } finally { lock.unlock(); } } }
在上述示例中,我们使用ReentrantLock
替换了synchronized
关键字,实现了相同的线程安全效果。
以上就是Java多线程与并发的“5.2 线程同步”的所有内容。通过这些示例,你应该对synchronized
关键字、wait()
、notify()
方法以及java.util.concurrent
包中的高级同步工具有了更深入的了解。请务必多实践,以巩固这些知识点。