1.公平锁和非公平锁
- 公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
- 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
并发包中ReentrantLock的创建可以指定构造函数Boolean类型来得到公平锁或非公平锁,默认是非公平锁。
关于两者的区别:
- 公平锁:公平锁就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。
- 非公平锁:非公平锁比较粗糙,上来就直接尝试占有锁,如果尝试失败,就再采取类似公平锁那种方式。
Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的有点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。
2.可重入锁(又名递归锁)
指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程的外层方法获取锁的时候,在进入内层方法会自动获取锁,也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
可重入锁最大的作用是避免死锁
- 2.1.synchronized就是一个典型的可重入锁
示例代码:
class Phone {
public synchronized void sendSMS() {
System.out.println(Thread.currentThread().getName() + "\tsendSMS()");
sendEmail();
}
public synchronized void sendEmail() {
System.out.println(Thread.currentThread().getName() + "\tsendEmail()");
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSMS();
}, "t1").start();
new Thread(() -> {
phone.sendSMS();
}, "t2").start();
}
}
运行结果:
t1 sendSMS() //t1线程在外层方法获取锁的时候
t1 sendEmail() //t1在进入内存方法会自动获取锁
t2 sendSMS()
t2 sendEmail()
- 2.2ReentrantLock也是典型可重入锁
示例代码:
class Phone {
Lock lock = new ReentrantLock();
public void sendSMS() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tsendSMS()");
sendEmail();
} finally {
lock.unlock();
}
}
public void sendEmail() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tsendEmail()");
} finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSMS();
}, "t1").start();
new Thread(() -> {
phone.sendSMS();
}, "t2").start();
}
}
运行结果:
t1 sendSMS() //t1线程在外层方法获取锁的时候
t1 sendEmail() //t1在进入内存方法会自动获取锁
t2 sendSMS()
t2 sendEmail()
3.自旋锁(spinlock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
如AtomicInteger
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
手动实现自旋锁:
public class SpinlockDemo {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
System.out.println(Thread.currentThread().getName() + "\t try get lock");
while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
}
System.out.println(Thread.currentThread().getName() + "\t get lock");
}
public void myUnlock() {
atomicReference.compareAndSet(Thread.currentThread(), null);
System.out.println(Thread.currentThread().getName() + "\t unlock");
}
public static void main(String[] args) throws InterruptedException {
SpinlockDemo spinlockDemo = new SpinlockDemo();
new Thread(() -> {
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinlockDemo.myUnlock();
}, "AA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinlockDemo.myUnlock();
}, "BB").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinlockDemo.myLock();
spinlockDemo.myUnlock();
}, "CC").start();
}
}
运行结果:
AA try get lock
AA get lock
BB try get lock
CC try get lock
AA unlock
CC get lock
CC unlock
BB get lock
BB unlock
4.独占锁(写锁),共享锁(读锁),互斥锁
独占锁:指该锁一次只能被一个线程所持有,对ReentrantLock和Synchronized而言都是独占锁。
共享锁:指该锁可被多个线程所持有。
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
不加锁出现线程安全问题:
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void set(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t 正在添加:" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 添加完成:" + key);
}
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//write
for (int i = 1; i <= 5; i++) {
final int tmp = i;
new Thread(() -> {
myCache.set(tmp + "", tmp + "");
}, "t" + i).start();
}
//read
for (int i = 1; i <= 5; i++) {
final int tmp = i;
new Thread(() -> {
myCache.get(tmp + "");
}, "t" + (i + 5)).start();
}
}
}
运行结果:
t2 正在添加:2
t4 正在添加:4
t3 正在添加:3
t5 正在添加:5
t1 正在添加:1
t6 正在读取:1
t7 正在读取:2
t8 正在读取:3
t9 正在读取:4
t10 正在读取:5
t3 添加完成:3
t7 读取完成:2
t10 读取完成:5
t8 读取完成:null
t2 添加完成:2
t9 读取完成:null
t1 添加完成:1
t6 读取完成:null
t5 添加完成:5
t4 添加完成:4
添加读写锁保证线程安全:
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void set(String key, Object value) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在添加:" + key);
TimeUnit.MICROSECONDS.sleep(300);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 添加完成:" + key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
public Object get(String key) {
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
} finally {
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//write
for (int i = 1; i <= 5; i++) {
final int tmp = i;
new Thread(() -> {
myCache.set(tmp + "", tmp + "");
}, "t" + i).start();
}
//read
for (int i = 1; i <= 5; i++) {
final int tmp = i;
new Thread(() -> {
myCache.get(tmp + "");
}, "t" + i).start();
}
}
}
运行结果:
t2 正在添加:2
t2 添加完成:2
t1 正在添加:1
t1 添加完成:1
t3 正在添加:3
t3 添加完成:3
t5 正在添加:5
t5 添加完成:5
t4 正在添加:4
t4 添加完成:4
t1 正在读取:1
t1 读取完成:1
t2 正在读取:2
t4 正在读取:4
t5 正在读取:5
t5 读取完成:5
t3 正在读取:3
t2 读取完成:2
t3 读取完成:3
t4 读取完成:4
从运行结果上看,写锁(独占锁)是互斥的,读锁是共享的,在某些场景下,读写锁(ReentrantReadWriteLock)在保证线程安全的前提下比Synchronized和ReentrantLock(读写操作都上锁)的并发性更高。即也是读写分离的思想。