多线程锁

287 阅读4分钟

多线程锁

1. 公平锁与非公平锁

按照ReentrantLock的实现

1.1 公平锁

在多个线程操作一个资源的时候,再有线程添加进来,直接进入阻塞队列中等待,不会抢占资源

1.2 不公平锁

在多个线程操作一个资源的时候,再有线程添加进来,首先会判断当前资源有没有被加锁,如果没有被加锁,则尝试对该变量进行操作,不会加入阻塞队列中。如果没有,当时资源被加锁,当前线程添加的队列中

1.3 源码

公平锁

final void lock() {
    acquire(1);
}

不公平锁

if (compareAndSetState(01))
   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 排版