公平锁与非公平锁
- 公平锁:多个线程按照申请锁的顺序来获取锁,按照FIFO规则从等待队列中拿到等待线程获取相应锁。
- 非公平锁:多个线程并不是按照申请锁的顺序来获取锁,有可能出现后申请锁的线程先申请到锁。在高并发环境下,非公平锁有可能造成优先级反转或者饥饿的现象。如果非公平锁抢占失败,就要继续采取类似公平锁的机制。非公平锁的优点在于吞吐量大。
常见的非公平锁:ReentrantLock可以通过指定构造函数的boolean类型来获取公平/非公平锁,默认情况下是非公平锁。synchronized也是一种非公平锁。
可重入锁
在同一个线程外层函数获得锁之后,内层递归函数仍然能够获取该锁的代码,即进入内层函数时会自动获取锁。线程可以进入任何一个它已经拥有的锁所同步的代码块。一个同步方法内部仍然存在一个同步方法,那么可以进入内层同步方法,且内层同步方法和外层同步方法持有的是同一把锁。可重入锁的最大作用就是防止死锁,因为多层嵌套的锁,其实锁的是同一个对象,另一个含义就是,嵌套方法持有的是同一把锁。
- ReentrantLock
- synchronized
public class ReentrantLockDemo {
public synchronized void lockReentrant() {
// println方法内部就使用了synchronized,锁住了this
System.out.println("hello");
}
public static void main(String[] args) {
new ReentrantLockDemo().lockReentrant();
}
}
synchronized是使用一种monitor机制,在进入锁时候先执行monitorenter指令。退出的时候执行monitorexit指令。synchronized是可重入锁,每个对象中都含有一个计数器当前线程再次获取锁,计数器+1,退出时候计算器-1,直到计数器为0才释放锁资源,唤醒其他线程来争抢资源。任意一个对象都拥有自己的监视器,只有在线程获取到监视器锁时才会进入代码中,否则就进入阻塞状态。
synchronized锁升级
synchronized在1.6以前是重量级锁,当前只有一个线程执行,其他线程阻塞。为了减少获得锁和释放锁带来的性能问题,而引入了偏向锁、轻量级锁以及锁的存储过程和升级过程。在1.6后锁分为了无锁、偏向锁、轻量锁、重量锁,锁的状态在多线程竞争的情况下会逐渐升级,只能升级而不能降级,这样是为了提高锁获取和释放的效率。
自旋锁
自旋锁尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁。优点是避免线程上下文切换的消耗,缺点是如果一直自旋会消耗CPU。
public class SpinLockDemo {
// 原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + " come on");
while (!atomicReference.compareAndSet(null, thread)) {
}
}
public void myUnlock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + " unlock");
}
public static void main(String[] args) throws Exception {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
}, "B").start();
}
}
读写锁
- 写锁(独占锁):指该锁一次只能被一个线程所持有,ReentrantLock和Synchronized都是独占锁。
- 读锁(共享锁):指该锁可以被多个线程所持有。
读锁的共享锁可保证并发读是非常高效的,读写、写读、写写的过程都是互斥的。
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) throws Exception {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + ",正在写入,key=" + key);
TimeUnit.SECONDS.sleep(1);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + ",写入完成,key=" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) throws Exception {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + ",正在读取,key=" + key);
TimeUnit.SECONDS.sleep(1);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + ",读取完成,result=" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}