实现Java函数的线程同步和互斥:使用synchronized关键字和Lock接口
实现Java函数的线程同步和互斥是为了解决多个线程同时访问共享资源时出现的竞争条件和数据的不一致。线程同步和互斥是Java多线程编程中非常重要的概念,可以使用synchronized关键字和Lock接口来实现。
1. synchronized关键字
synchronized关键字可以用来保护一个代码块或方法,只有获得锁的线程才能进入代码块或方法执行,其他线程必须等待锁释放才能进入。synchronized关键字有两种使用方式,一种是修饰代码块,一种是修饰方法。
1.1 修饰代码块
synchronized关键字可以修饰一个代码块,格式为:
synchronized(obj){
//需要同步的代码
}
其中,obj是一个共享资源对象,多个线程需要访问这个对象时,必须使用该对象作为锁来保证线程同步。
例如:
public class SyncDemo {
private int count = 0;
private Object lock = new Object();
public void increase() {
synchronized (lock) {
count++;
}
}
}
在这个例子中,需要保护count变量的多个线程可以使用同一把锁lock,通过synchronized(lock){...}来实现对count变量的同步控制。
1.2 修饰方法
synchronized关键字也可以修饰一个方法,格式为:
public synchronized void method() {
//需要同步的代码
}
这种方式会将整个方法变成一个同步块,其锁对象是当前实例对象(this)。
例如:
public class SyncDemo {
private int count = 0;
public synchronized void increase() {
count++;
}
}
在这个例子中,需要保护count变量的多个线程可以使用synchronized来实现同步控制。
synchronized关键字的优缺点:
- 优点:使用简单,不需要额外的锁对象,可以确保线程的同步和互斥。
- 缺点:只能实现互斥同步,无法实现读写分离,锁的粒度较大,容易引起死锁。
2. Lock接口
Lock接口是Java提供的另一种实现线程同步和互斥的方式,它是在JDK 5.0中引入的。相比于synchronized关键字,Lock接口具有更强的灵活性和扩展性,可以实现读写分离锁、公平锁、可重入锁等高级的锁机制。
2.1 Lock的基本用法
Lock接口有三个主要方法:lock()、unlock()和tryLock()。
- lock()方法:获取锁。如果锁已经被其他线程获取,调用该方法的线程将会被阻塞,直到获取到锁。
- unlock()方法:释放锁。该方法必须在获取锁的代码块内部使用,否则会抛出IllegalMonitorStateException异常。
- tryLock()方法:尝试获取锁。如果锁被其他线程获取,该方法会立即返回false,否则返回true。
例如:
public class LockDemo {
private Lock lock = new ReentrantLock();
private int count = 0;
public void increase() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
在这个例子中,需要保护count变量的多个线程可以使用Lock接口的方式实现。
2.2 Lock的高级功能
Lock接口提供了很多高级功能,例如可重入锁、读写锁、公平锁等。
2.2.1 可重入锁
可重入锁是指同一个线程可以重复获取同一把锁,ReentrantLock实现了可重入锁。例如:
public class LockDemo {
private Lock lock = new ReentrantLock();
private int count = 0;
public void increase() {
lock.lock();
try {
count++;
increase1();
} finally {
lock.unlock();
}
}
public void increase1() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
在这个例子中,increase1()方法可以再次获取lock锁,因为当前线程已经持有了该锁,这是可重入的。
2.2.2 读写锁
读写锁是指一个资源既可以被多个线程同时读取,也可以被一个线程独占写入。读写锁分为读锁和写锁,读锁可以被多个线程同时获取,写锁只能被一个线程获取,如果有线程获取了写锁,则其他线程无法获取读锁或写锁。ReentrantReadWriteLock实现了读写锁。
例如:
public class ReadWriteLockDemo {
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
private int count = 0;
public void increase() {
writeLock.lock();
try {
count++;
} finally {
writeLock.unlock();
}
}
public int getCount() {
readLock.lock();
try {
return count;
} finally {
readLock.unlock();
}
}
}
在这个例子中,getCount()方法使用读锁来保护count变量的读操作,increase()方法使用写锁来保护count变量的写操作。
2.2.3 公平锁
公平锁是指多个线程获取锁的顺序与它们发出获取锁请求的顺序相同,即先请求的线程先获得锁,ReentrantLock可以实现公平锁。例如:
public class LockDemo {
private Lock lock = new ReentrantLock(true);
private int count = 0;
public void increase() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
在这个例子中,使用ReentrantLock(true)来创建一个公平锁。
Lock接口的优缺点:
- 优点:具有更强的灵活性和扩展性,可以实现读写分离锁、公平锁、可重入锁等高级的锁机制。
- 缺点:使用比synchronized关键字复杂一些,容易出现死锁和其他问题。
总结:
synchronized关键字和Lock接口都可以用来保证Java函数的线程同步和互斥,使用场景不同,选择使用哪种方式要根据实际需求来决定。对于简单的同步需求,使用synchronized关键字即可;对于更高级的锁机制需求,可以使用Lock接口来实现。使用锁的过程中应该注意锁的粒度、锁的可重入性、死锁和其他性能问题。
