本文主要介绍Java的一些锁:公平锁,非公平锁,可重入锁,自旋锁,以及它们的使用案例
公平锁与非公平锁
- 公平锁就是多线程按照申请锁的顺序来获取锁,先来后到。
- 非公平锁是多线程获取锁的顺序不是按照申请锁的顺序。可能造成优先级反转和饥饿现象,如果能抢占就抢占,否则就按照公平锁进行处理。
ReentrantLock默认的时候是非公平锁,但是也可以设置为公平锁。synchronized也是非公平锁。
可重入锁
可重入锁就是外层函数获取锁之后,内层代码仍然可以获取该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。线程可以进入任何一个它所拥有锁的同步代码块。
从下列代码可以看出,线程A获取锁,进入到 increment() 方法,然后调用reduce方法,reduce方法也加了锁,但是可以直接获取到锁。不需要重新申请。
// synchronized
class Cat {
private volatile int num = 0;
public synchronized void increment() {
num ++;
System.out.println(Thread.currentThread().getName() + " " + num);
reduce();
}
public synchronized void reduce() {
num--;
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
public class ReDemo {
public static void main(String[] args) {
Cat c = new Cat();
new Thread(() -> {
c.increment();
},"A").start();
new Thread(() -> {
c.increment();
},"B").start();
}
}
lock 版本:
class Cat {
private volatile int num = 0;
Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
num ++;
System.out.println(Thread.currentThread().getName() + " " + num);
reduce();
}catch(Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void reduce() {
lock.lock();
try {
num--;
System.out.println(Thread.currentThread().getName() + " " + num);
}catch(Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReDemo {
public static void main(String[] args) {
Cat c = new Cat();
new Thread(() -> {
c.increment();
},"A").start();
new Thread(() -> {
c.increment();
},"B").start();
}
}
自旋锁
说到自旋锁,unsafe 类中就使用到了,自旋就是使用循环的方式去获取锁,这样可以减少线程上下文之间的切换。
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 SprinLock {
AtomicReference<Thread> reference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println("尝试获取锁" + thread.getName());
while (!reference.compareAndSet(null, thread)) {
}
System.out.println("获取到锁" + thread.getName());
}
public void myUnLock() {
Thread thread = Thread.currentThread();
reference.compareAndSet(thread, null);
System.out.println("解锁" + thread.getName());
}
public static void main(String[] args) {
SprinLock sprinLock = new SprinLock();
new Thread(() -> {
sprinLock.myLock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
sprinLock.myUnLock();
}, "A").start();
new Thread(() -> {
sprinLock.myLock();
sprinLock.myUnLock();
}, "B").start();
}
}
读写锁
读写锁就是再多线程的条件下,读读不加锁,读写和写读和写写都需要加锁。
读写锁demo:
class MyData {
Map<String, Object> map = new HashMap<>();
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object object) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在写入线程" + key);
Thread.sleep(300);
map.put(key, object);
System.out.println(Thread.currentThread().getName() + " 写入完成" + key);
}catch(Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在读取线程" + key);
Thread.sleep(300);
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 读取完成" + result);
}catch(Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
public class RreadOnWriteLockDemo {
public static void main(String[] args) {
MyData data = new MyData();
for(int i=0; i<5; i++) {
final int tmp = i;
new Thread(() ->{
data.put(tmp +"", tmp);
},String.valueOf(i)).start();
}
for(int i=0; i<5; i++) {
final int tmp = i;
new Thread(() ->{
data.get(tmp + "");
},String.valueOf(i)).start();
}
}
}