java 锁

455 阅读4分钟

公平锁 && 非公平锁

公平锁:来了一个线程之后,根据FIFO排队;

非公平锁:来了一个线程之后,直接争抢锁,未抢到采取公平锁策略。

可重入锁

下列代码可以运行,

Lock lock = new ReentrantLock();
lock.lock();
lock.lock();
lock.unlock();
lock.unlock();

自旋锁

public class Test1 {

    //原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.currentThread().getName() + "\t come in");
        while (!atomicReference.compareAndSet(null, thread)) {
    
        }
    }
    
    public void myUnlock() {
        Thread thread = Thread.currentThread();
    
        atomicReference.compareAndSet(thread, null);
        System.out.println(thread.currentThread().getName() + "\t invoked muUnlock");
    }
    
    public static void main(String[] args) {
        Test1 test = new Test1();
        new Thread(() -> {
            test.myLock();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test.myUnlock();
        }, "AA").start();
    
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        new Thread(() -> {
            test.myLock();
            test.myUnlock();
        }, "BB").start();
    }

}

读写锁

ReentrantReadWriteLock

读写锁验证:


public class ReadWriteTest {
 
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 100个线程写
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                myCache.put(Strin, n);
            }, String.valueOf(i)).start();
        }
        // 100个线程读
        for (int i = 0; i < 5; i++) {
            final String n = String.valueOf(i);
            new Thread(() -> {
                myCache.get(n);
            }, String.valueOf(i)).start();
        }
    }
}
 
// 自定义缓存
class MyCache{
 
    private volatile Map<String, Object> map = new HashMap<>();
    // 读写锁
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
 
    public void put(String key, Object value)  {
        rwLock.writeLock().lock();
        try {
           System.out.println(Thread.currentThread().getName() + "线程开始写入");
           map.put(key, value);
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + "线程写入完成");
       }catch (Exception e){
          e.printStackTrace();
       }finally {
           rwLock.writeLock().unlock();
       }
    }
 
    /**
     * 获取缓存
     * @param key
     */
    public Object get(String key) {
        rwLock.readLock().lock();
        try {
             System.out.println(Thread.currentThread().getName() + "线程开始读取");
             Object result = map.get(key);
             // 模拟读取耗时
             try {
                 Thread.sleep(300);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName() + "线程读取完成" + result);
             return result;
         }catch (Exception e){
            e.printStackTrace();
             return null;
         }finally {
             rwLock.readLock().unlock();
         }
    }
}

synchronized 和lock 的区别

sync是关键字,属于JVM层次;
Reentrantlock是具体类,是api层面的锁;
sync不需要用户手动释放锁,当代码执行完后系统会自动让线程释放对锁的占用;
ReentrantLock则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象;
     需要lock()和unlock()方法配合try/finally语句块来完成;
sync不可中断,除非抛出异常或者正常运行完成;
reentrantlock 可中断;
    1. 设置超市方法tryLock(long timeout, TimeUnit unit);
    2. lockInterruptibly()放代码块中,调用interrupt()方法可中断;
sync 是非公平锁;
ReentrantLock默认非公平锁;
sync 不能绑定多个条件condition;
ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像sync要么随机唤醒一个要么唤醒全部线程。

synchronized 实现原理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport中的park()和 unpark()的作用分别是阻塞线程和解除阻塞线程。

LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。

可以把许可看成是一种(0.1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1。

LockSupport.park();        // permit 减 1
LockSupport.uppark(thread) // permit 加 1,permit最大值也为1

AQS

AbstractQueuedSynchronizer 抽象队列同步器。

通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态.AQS中的队列是 CLH(单向链表) 变体的虚拟双向队列FIFO。

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现。它将请求共享资源的线程封装成队列的结点(Node),通过CAS、自旋以及LockSupportpark的方式,维护state变量的状态,使并发达到同步的控制效果。

AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFo队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node,节点来实现锁的分配,通过CAS完成对State值的修改。