Java中如何实现线程同步和互斥访问?
Java中的线程同步和互斥访问是保证多个线程访问公共资源时的正确性和稳定性的重要手段。线程同步和互斥访问可以通过synchronized关键字和Lock接口实现,下面分别进行介绍。
一、synchronized关键字
synchronized是Java语言中实现线程同步和互斥访问的最常用的关键字。在Java中,每一个对象都可以作为锁,synchronized可以将任意一个对象作为同步锁来实现线程同步和互斥访问。
synchronized的使用方式有两种:方法级别的同步和代码块级别的同步。
1. 方法级别的同步
方法级别的同步是指使用synchronized修饰方法,使得在调用该方法时,系统自动获取该对象的锁,在执行完该方法后自动释放该锁。方法级别的同步是最简单的线程同步方式,但是它也有其局限性:如果一个类中有多个方法需要同步,并且它们之间不能交替执行,那么就无法使用方法级别的同步实现线程同步。
下面是一个使用方法级别的同步实现线程同步的示例:
public class MyThread implements Runnable {
private int count = 10;
public synchronized void run() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
new Thread(myThread, "Thread" + i).start();
}
}
}
上述代码中,MyThread实现了Runnable接口,并使用了synchronized修饰run方法,使得多个线程同时执行run方法时能够互斥访问count变量,从而满足线程安全的要求。当多个线程同时执行时,会出现以下输出:
Thread0 count = 9 Thread3 count = 8 Thread1 count = 7 Thread2 count = 6 Thread4 count = 5
2. 代码块级别的同步
代码块级别的同步是指使用synchronized包裹一个代码块,使得在进入该代码块之前需要获取该对象的锁,在执行完该代码块之后会自动释放该锁。代码块级别的同步相比于方法级别的同步更加灵活,可以根据需要选择需要同步的代码块。
下面是一个使用代码块级别的同步实现线程同步的示例:
public class MyThread implements Runnable {
private int count = 10;
private Object lock = new Object();
public void run() {
synchronized (lock) {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
new Thread(myThread, "Thread" + i).start();
}
}
}
上述代码中,MyThread实现了Runnable接口,并将要同步的代码块包裹在了synchronized中,通过获取lock对象的锁来实现线程同步。当多个线程同时执行时,会出现以下输出:
Thread0 count = 9 Thread2 count = 8 Thread1 count = 7 Thread4 count = 6 Thread3 count = 5
二、Lock接口
除了synchronized关键字外,Java还提供了Lock接口实现线程同步和互斥访问。Lock接口是Java1.5版本中新增的接口,它提供了一种比synchronized更加灵活、更加强大的实现线程同步和互斥访问的方式。
Lock接口的提供了以下常用的方法:
- lock():获取锁,如果锁已经被其他线程获取,则当前线程会进入阻塞状态,直到其他线程释放锁。
- unlock():释放锁。
- tryLock():尝试获取锁,如果锁已经被其他线程获取,那么当前线程不会进入阻塞状态,会直接返回false。
- tryLock(long timeout, TimeUnit unit):在指定的时间范围内尝试获取锁,如果锁已经被其他线程获取,则当前线程会进入阻塞状态,并在指定时间过后返回false。
下面是一个使用Lock接口实现线程同步的示例:
public class MyThread implements Runnable {
private int count = 10;
private Lock lock = new ReentrantLock();
public void run() {
lock.lock();
try {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
new Thread(myThread, "Thread" + i).start();
}
}
}
上述代码中,MyThread实现了Runnable接口,并使用了Lock接口实现线程同步,通过获取lock对象的锁来实现线程同步。当多个线程同时执行时,会出现以下输出:
Thread0 count = 9 Thread1 count = 8 Thread3 count = 7 Thread2 count = 6 Thread4 count = 5
总结:
Java中可以通过synchronized关键字和Lock接口实现线程同步和互斥访问。synchronized是最常用的实现线程同步和互斥访问的方式,它提供了方法级别的同步和代码块级别的同步两种方式。Lock接口相比于synchronized更加灵活、更加强大,可以通过lock、unlock等方法控制锁的获取和释放。无论是使用synchronized还是Lock接口,都需要遵循“加锁-访问共享资源-释放锁”的步骤,以保证多个线程访问同一共享资源时的正确性和稳定性。
