临界区
- 为了解决并发编程中多线程访问或者修改数据所出现的错误和数据不一致性,提出了临界区的概念。
- 临界区是一个用以访问共享资源的代码块,这个代码块在同一时间只允许一个线程执行。
实现临界区并发访问的两只机制
synchronized关键字机制
- 用synchronized关键字声明的对象在同一时刻只允许一个线程访问,其他线程如果视图访问它,将会被挂起,直至当前线程访问完成。
- synchronized关键字锁定的是一个对象而不是类。(当然在静态方法中,synchronized锁定了整个类)
- synchronized的缺点:
- 当一个线程获取了synchronized代码块,其他线程只能等待。只有在以下两种情况才会释放锁:
- 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 线程执行发生异常,此时JVM会让线程自动释放锁。
这使得其他的等待线程在此期间无法进行其他工作,降低了执行效率。比如最常见的读写操作,其中写操作是对读操作和写操作排斥的,但读操作却只对写操作排斥,多个读操作是可以同时进行的,如果使用synchronized关键字就无法实现多个读操作同时执行。
- 当一个线程获取了synchronized代码块,其他线程只能等待。只有在以下两种情况才会释放锁:
Lock接口以及其实现机制
ReadWriteLock实现对资源的线程安全读写
- ReadWriteLock是一个接口,而ReentrantReadWriteLock则是其具体的实现。
- ReadWriteLock有两个锁:一个读操作锁和一个写操作锁。
- 使用读操作时可以允许多个线程同时访问,但在进行写操作时只允许一个线程进行写操作。
- 读操作锁由ReadWriteLock接口的readLock()方法获取,而readLock()方法实际返回一个Lock对象,所以我们可以对其进行lock(),unLock(),tryLock()操作。
- 写操作锁同理。
- 在获得读操作时如果进行修改数据的操作(虽然可以进行),否则就产生了多线程同时对数据进行写操作的问题。修改数据的操作应放在写操作锁中。
- 具体例子:
public class PricesInfo {
private double price1;
private double price2;
private ReadWriteLock lock;
public PricesInfo() {
this.price1 = 1.0;
this.price2 = 2.0;
this.lock = new ReentrantReadWriteLock();
}
public double getPrice1() {
lock.readLock().lock();
double value=price1;
price1=Math.random()*10;
lock.readLock().unlock();
return value;
}
public double getPrice2() {
lock.readLock().lock();
double value=price2;
price2=Math.random()*10;
lock.readLock().unlock();
return value;
}
public void setPrice(double price1, double price2) {
lock.writeLock().lock();
this.price1=price1;
this.price2=price2;
lock.writeLock().unlock();
}
}
首先定义了一个价格修改和查看的类,获取价格是用readLock的lock()方法锁定的,而设置价格则是由writeLock实现。
Lock和Synchronized的简单区别
- synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定。
- 而Lock的锁定和释放都是由代码控制的,需要手动释放锁,但也具有更高的灵活性。
- 在资源访问频繁的时候或者资源访问时间过长的时候synchronized性能会大大降低,而Lock由于其灵活性可用于多个线程的安全访问性能更好。
- ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
网友评论