说起线程安全,我们第一时间想到的就是加锁,通过锁来保证同一时间只有一个线程访问。最常使用的就是synchronized和lock关键字,以下是我对于使用这两个锁的一些总结。
synchronized
1.锁的对象
我们先来看下面这一段代码
public class Test {
private String str;
public Test(String str){
this.str = str;
}
public void print(){
//1
synchronized (str){
System.out.println(str);
}
}
//2
public synchronized void println(){
System.out.println(str);
}
//3
public static synchronized void print(String str){
System.out.println(str);
}
}
这是我们经常使用的几种方法,
第一处可以很明显的看出,锁的对象是str;
第二处修饰的是实例方法,锁的对象是对应的实例,例如
Test t = new Test("str");
t.println();
此时println方法锁的是对象t;
第三处修饰的是类方法,锁的对象是Class对象,此时锁的是Test.class。
2.锁的性质
1)可重入
synchronized是可重入锁,即对于同一个线程,不会出现自己把自己锁死的情况,同步块在已进入的线程执行完之前,会阻塞后面其他的线程。
2)非公平
有若干个线程在等待同一个锁,当锁被释放的时候,这些线程都有机会获得锁,而并非按照时间先后顺序获得。
3)注意的点
synchronized是一个重量级的锁,每次加锁都需要从用户态转到核心态,对于一些简单的操作,有可能转换的时间会比操作执行的时间还要长,所以使用起来需要慎重。
lock
1.常见的用法
Lock lock = new ReentrantLock();
lock.lock();
try{
}catch(Exception e){
}finally {
lock.unlock();
}
需要注意的是,使用lock锁,需要我们自己去维护释放锁,若是不小心忘记了释放,就很容易出现死锁。
lock还提供给了我们一个方法trylock,让我们可以试着去获取锁。
if(lock.tryLock()){
//do somthing
lock.unlock();
}else{
//没有获取到锁
}
此外还有ReadWriteLock,分为读锁和写锁,读锁能够允许多个线程同时访问,而写锁同一时间只允许一个线程访问。
2.锁的性质
1)性能好
lock可以把锁分成读锁和写锁,在一定程度上比synchronized性能要好。不过虚拟机也针对synchronized做了很多的优化,比如加入自选锁和锁消除等等,而且synchronized不需要自己手动释放,也在一定程度上避免了因为代码的逻辑漏洞而产生死锁的风险。
2)公平锁
ReentranLock默认是非公平锁,可以通过带参数的构造函数指定使用公平锁。公平锁就是按照时间的先后顺序获取锁。
Lock lock = new ReentrantLock(true);
3)可重入
lock也是可重入锁。
总结
lock和synchronized都是基于悲观锁的思想而实现的,
悲观锁就是总是认为只要不做正确的同步措施,就一定会出问题,无论数据是否真的会出现竞争,都必须要加锁。而与之对应的就是乐观锁,就是先进行操作,若没有其他线程竞争,那我的操作就成功了,若有其他的线程竞争,产生了冲突,再采取补救的措施。Atomic就是采用的乐观锁的思想实现的。