乐观锁:假定没有冲突,在修改数据如果发现数据和之前获取的不一致,则读取最新的数据,再次尝试修改。
悲观锁:假定会发生冲突,同步所有的数据的相关操作,从读数据就开始上锁。
独占锁:给资源加上写锁,当前线程可以修改资源,其它线程不能再加锁(单写)
共享锁:给资源加上读锁后只能读不能改,不能加写锁(多读)
重入锁:同时加两次锁,不会出现死锁(再次拿到锁)
可重入锁:同时加两次锁,会出现死锁(阻塞)
公平锁:抢到锁的顺序和抢锁顺序相同则为公平锁
非公平锁:抢到锁的顺序和抢锁顺序无关。
synchronized 的特性:
1. 用于实例方法,静态方法时,隐式指定锁对象
2. 用于代码块时,显示指定锁对象
3. 锁的作用域:对象锁,类锁,分布式锁
4. 是可重入锁,也是独享锁和悲观锁
/**
* 测试 synchronized 的可重入性
*/
public class RecursiveTest {
public static void main(String[] args) throws InterruptedException {
recursive();
}
public static synchronized void recursive() throws InterruptedException {
System.out.println("here i am ...");
Thread.sleep(1000L);
recursive();
}
}
示例代码:
public class Demo {
public static void main(String[] args) {
int a = 1;
SalesOrder order = new SalesOrder();
order.cust = new Customer();
}
}
class SalesOrder {
Long id = 1001L;
int age = 50;
Customer cust;
public void discount() {
//do something
}
}
class Customer {
String name = "Julie";
int age = 18;
boolean gender = false;
}
对象头部
轻量级锁:
当没有成功的线程自旋达到一定次数时,锁升级为重量级锁:
根据上述,锁有四个状态,分别为:
争抢能够访问资源的一种权限。
Lock接口API:
public interface Lock {
//获取锁(不死不休)
void lock();
//尝试获取锁(浅尝辄止)
boolean tryLock();
//获取锁(过时不候)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//获取锁(任人摆布)
void lockInterruptibly() throws InterruptedException;
//释放锁
void unlock();
//返回一个新Condition绑定到该实例Lock实例。
Condition newCondition();
}
lock() 最常用
lockInterruptibly() 方法一般更昂贵,有的实现类中可能没有实现lockInterruptibly() ,只有真的需要中断时才使用,使用之前看看实现该方法的描述。
Condition
Object 中的wait(),notify(),notifyAll() 只能和synchronized配合使用,可以唤醒一个或者全部(单个等待集);而Condition需要与Lock配合使用,提供多个等待集合,更精确的控制。以下是使用Condition的示例:
public class Demo {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
lock.lock();
try {
System.out.println("子线程调用condition.await()...");
//子线程调用await()后,会自动释放锁以备主线程调用。
condition.await();
System.out.println("子线程condition.await() 后面的代码...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
Thread.sleep(5000L);
lock.lock();
condition.signalAll();
lock.unlock();
}
}
Condition中使用时应避免出现死锁场景:在未调用await()之前调用了signal() 或signalAll() :
public class Demo {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程调用lock()");
lock.lock();
try {
System.out.println("获得锁,调用condition.await()\n");
condition.await();
System.out.println("唤醒了...\n");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
lock.lock();
condition.signalAll();
System.out.println("主线程调用signalAll()");
lock.unlock();
}
}
synchronized
优点:
1. 使用简单,语义清晰。
2. 由JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
3. 锁的释放由JVM来完成,不用人工干预,也降低死锁的可能性。
缺点:
4. 无法实现一些锁的高级功能,如公平锁,中断锁,读写锁,共享锁等。
Lock
优点: 所有synchronized的缺点,可以实现更多的功能 。
缺点:需要释放锁,使用不当可能造成死锁。
读写锁:在读与写的操作中,读可以并发读,写只能用一个线程写。但是读锁与写锁之间是互斥的。
ReadWriteLock:
维护一对关联锁,一个只用于读操作,一个只用于写操作;读锁可以由多个读线程同时持有,写锁是排他锁。同一时间,读锁和写锁不能被不同的线程持有。
读写锁改造HashMap:
public class Demo {
private final Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public Object[] allKeys() {
readLock.lock();
try {
return map.keySet().toArray();
} finally {
readLock.unlock();
}
}
public Object put(String key, Object value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
Demo demo= new Demo ();
for (int i = 0; i < 100000; i++) {
int n = i;
new Thread(() -> {
if (n % 1 == 0)
demo.put(n + "", "n % 1 == 0");
else
demo.put(n + "", n + "_123");
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println(demo.get("39333"));
}
}