多线程锁
1. 公平锁与非公平锁
按照ReentrantLock的实现
1.1 公平锁
在多个线程操作一个资源的时候,再有线程添加进来,直接进入阻塞队列中等待,不会抢占资源
1.2 不公平锁
在多个线程操作一个资源的时候,再有线程添加进来,首先会判断当前资源有没有被加锁,如果没有被加锁,则尝试对该变量进行操作,不会加入阻塞队列中。如果没有,当时资源被加锁,当前线程添加的队列中
1.3 源码
公平锁
final void lock() {
acquire(1);
}
不公平锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
1.4 优缺点
非公平锁可以更加合理的利用CPU资源,效率更高,但是无序
公平锁有序,但是会浪费CPU资源
2. 可重入锁
已经获取了锁的对象,可以访问其他被该锁标记的代码块。
主要场景是在一个同步代码块中调用另一个加锁的方法
2.1 synchronzied可重入锁
class MyData{
public synchronized void printName(){
System.out.println(Thread.currentThread().getName()+"\t打印名字");
printAge();
}
public synchronized void printAge(){
System.out.println(Thread.currentThread().getName()+"\t打印年龄***********");
}
}
public class SynchronizedDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{myData.printName();},"线程一").start();
new Thread(()->{myData.printName();},"线程二").start();
}
}
**主要思想:**在printName()方法中直接调用另一个同一个锁的方法
2.2 ReentrantLock可重入锁
@Override
public void run() {
lock.lock();
try{
printID();
}finally {
lock.unlock();
}
}
private void printID() {
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"\t打印ID");
}finally {
lock.unlock();
}
}
**一个面试题:**如果有多个lock.lock()和与之匹配的lock.unlock()运行会报错吗?
**答案:**不会。可重入锁的原理,只要拿的是同一把锁就可以进入其他被该锁标记的代码段。
如果lock.lock()与lock.unlock()数目不匹配,运行会报错吗?
**答案:**不会。运行的时候会线程等待,由于没有释放锁
3. 自旋锁
通过循环的方式不断获取锁,避免线程上下文切换,耗费CPU资源。
由于使用循环,可能会导致CPU资源严重浪费
3.1 自实现自旋锁
**原理:**通过原子引用这个Thread线程,通过CAS设置这个当前获取锁的线程
class MyOwnLock{
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock(){
String name = Thread.currentThread().getName();
System.out.println(name+"准备获取锁");
//如果当前地址的引用为Null,则直接获取锁,跳出循环;如果不是则自旋不断获取锁
while (!atomicReference.compareAndSet(null,Thread.currentThread())){
}
System.out.println(name+"获取锁成功++++++");
}
public void unlock(){
String name = Thread.currentThread().getName();
atomicReference.compareAndSet(Thread.currentThread(),null);
System.out.println(name+"\t释放锁成功");
}
}
public class OwnCriDemo {
public static void main(String[] args) {
MyOwnLock myOwnLock = new MyOwnLock();
new Thread(()->{
myOwnLock.lock();
try{TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
myOwnLock.unlock();
},"A").start();
new Thread(()->{
myOwnLock.lock();
myOwnLock.unlock();
},"B").start();
}
}
//输出
A准备获取锁
A获取锁成功++++++
B准备获取锁
A 释放锁成功
B获取锁成功++++++
B 释放锁成功
4. 读写锁
在Java代码中的体现是AQS中的Node节点的Shared和EXCLUSIVE变量的值
4.1 独占锁
在写的时候锁独占,只能有一个线程来进行写操作
4.2 共享锁
在读的时候,可以有多个线程进行读操作
4.3 ReadWriteLock接口
实现了读写锁,底层还是AQS不过使用了Node节点中的锁模式,独占或者共享。
当为写的时候使用了独占锁;当为读的时候使用了共享锁
本文使用 mdnice 排版