欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java多线程之Lock介绍

程序员文章站 2024-01-09 17:49:22
...

java.util.concurrent包中有关于Lock锁的定义。它提供了ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,重入锁、读锁和写锁等。

ReentrantLock

公平锁和非公平锁

公平锁当线程请求锁时,会将其加入到请求队列中,
非公平锁就是当前线程不管有无请求队列,先去请求锁,如果请求不到,加入到队列末尾。
具体分析可以参考
http://www.jianshu.com/p/f7d05d06ef54

默认构造方法创建非公平锁

public ReentrantLock() {
        sync = new NonfairSync();
    }

Lock与synchronized的区别

  1. lock是显示的锁,调用lock()添加锁,必须调用unlock()释放锁。
  2. 使用lock.lock()调用线程的interrupt会中断线程,而synchronized锁调用interrupt不会中断线程。

实例来证明2的结论

我们知道synchronized的interrupt方法只是设置中断标志,并没有真正的中断线程。下面这个代码示例用于说明,
代码如下:

设计两个线程,一个read线程,一个write线程。
当read线程正在read的时候,write线程处于等待状态、
然后中断write线程,看会有什么情况发生
如果write中断,会打印 write end…
因为Reentrant对中断锁做出响应,会中断线程的执行。

public static void syncReaderWriterTest(){
        MyReentrantLockBuffer mySyncBuffer = new MyReentrantLockBuffer();
        final Thread reader = new Thread(new SyncReader(mySyncBuffer));
        final Thread writer = new Thread(new SyncWriter(mySyncBuffer));
        reader.start();
        writer.start();

        System.out.println(Thread.currentThread().getName() +  " writer 不在等待了...");
        writer.interrupt();
        System.out.println(Thread.currentThread().getName() +  " writer 已经中断, " + writer.isInterrupted());
    }

    /**
     * 写操作,传入MySyncBuffer的构造方法,保证两个对象持有相同的对象锁
     */
    static class SyncWriter implements Runnable{

        private MyReentrantLockBuffer mySyncBuffer;

        public SyncWriter(MyReentrantLockBuffer mySyncBuffer){
            this.mySyncBuffer = mySyncBuffer;
        }
        public void run() {
            try {
                mySyncBuffer.write();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " write end." + Thread.currentThread().isInterrupted());
        }
    }

    /**
     * 读操作,传入MySyncBuffer的构造方法,保证两个对象持有相同的对象锁
     */
    static class SyncReader implements Runnable{

        private MyReentrantLockBuffer mySyncBuffer;

        public SyncReader(MyReentrantLockBuffer mySyncBuffer){
            this.mySyncBuffer = mySyncBuffer;
        }
        public void run() {

            mySyncBuffer.read();

        }
    }

可重入性

锁的可重入性体现在两个方面
1. methodA和methodB拥有同一个对象锁,线程进入A方法中,在没有释放锁的前提下,可以直接调用methodB方法。

static class ReentrantDemoRunnable implements Runnable{
        final ReentrantLock reentrantLock = new ReentrantLock();
public void methodA() {
            System.out.println(" lock before methodAAAAAA ========" );
            reentrantLock.lock();

            try {
                System.out.println(" run methodA and lock..." );
                this.methodB();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println(" run methodA end unlock..." );
                reentrantLock.unlock();
            }
            System.out.println(" methodAAAAAA ======== end =====" );
        }

        public void methodB() {
            System.out.println(" lock before methodBBBBBB ========" );
            reentrantLock.lock();

            try {
                System.out.println(" run methodB and lock..." );

            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println(" run methodB end unlock..." );
                reentrantLock.unlock();
            }
            System.out.println(" methodBBBBBB ======== end =====" );
        }
  }

多线程调用methodA执行的顺序为:

 lock before methodAAAAAA ========
 run methodA and lock...
 lock before methodBBBBBB ========
 run methodB and lock...
 run methodB end unlock...
 methodBBBBBB ======== end =====
 run methodA end unlock...
 methodAAAAAA ======== end =====
  1. 在加锁的前提下,在子类方法中调用父类的方法。
static class Father{
        final ReentrantLock lock = new ReentrantLock();
        public void method(){
            lock.lock();
            try {
                System.out.println("father do something ");
            } finally {
                lock.unlock();
            }
        }
    }

    static class SubClass extends Father{

        @Override
        public void method(){
            lock.lock();
            try {
                System.out.println("subClass do something ");
                super.method();
            } finally {
                lock.unlock();
            }
        }
    }

public static void reentrantTest2(){
        SubClass subClass = new SubClass();
        subClass.method();
    }

打印结果:

subClass do something 
father do something 

ReentrantReadWriteLock

读写锁,它有两个内部类,分别是ReadLock和WriteLock
这两个锁分别有以下特性

多个读锁不互斥(证明)

一个方法内使用readLock.lock锁,当多个线程访问时候,如果线程A持有锁,线程B也可以持有该锁。两个线程无需互相等待。

 static class ReadRunnable implements Runnable{

        final ReentrantReadWriteLock.ReadLock readLock = new ReentrantReadWriteLock().readLock();

        public void run() {
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " read start...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " read end...");
                readLock.unlock();
            }
        }
    }
public static void readLockNotMutex(){
        ReadRunnable readRunnable = new ReadRunnable();
        //创建3个多线程,来说明读锁不互斥
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(readRunnable);
            thread.start();
        }
    }

例子说明:创建多个线程,每个线程执行MyRunnable的run方法,run方法中使用读锁设置同步。发现会有多个run方法中打印乱序。
打印结果

     Thread-0 read start...
     Thread-1 read start...
     Thread-2 read start...
     Thread-0 read end...
     Thread-1 read end...
     Thread-2 read end...

说明读锁不互斥

多个写锁互斥(证明)

原理与读锁相反

static class WriteRunnable implements Runnable{

        final ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock().writeLock();

        public void run() {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " write start...");
            try {
                Thread.sleep(2000);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " write end...");
                writeLock.unlock();
            }
        }
    }
public static void writeLockMutex(){
        WriteRunnable writeRunnable = new WriteRunnable();
        //创建3个多线程,来说明读锁不互斥
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(writeRunnable);
            thread.start();
        }
    }

创建多个线程,每个线程执行MyRunnable的run方法,run方法中使用写锁设置同步。发现会有多个run方法中打印不乱序。
打印结果:

     Thread-0 write start...
     Thread-0 write end...
     Thread-1 write start...
     Thread-1 write end...
     Thread-2 write start...
     Thread-2 write end...

说明写锁互斥

读锁和写锁互斥

  • 线程A执行writeMethod()方法,获取到写锁,
  • 此时线程B执行readMethod()方法,因为读写锁(写读锁)互斥,所以要等待线程A释放锁,然后才能继续执行。
    实例如下:
static class ReadAndWriteLock{
        final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        final ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
        final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();

        public void writeMethod(){
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " 当前为write Lock========");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " write lock end ========");
                writeLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序结束 end");
        }

        public void readMethod(){
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " 当前为read Lock========");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println(Thread.currentThread().getName() + " read lock end ========");
                readLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序结束 end");
        }
    }
public static void readWriteMutex(){
        ReadAndWriteLock readAndWriteLock = new ReadAndWriteLock();

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndWriteLock.writeMethod();
            }
        });
        t.start();


        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                readAndWriteLock.readMethod();
            }
        });
        t1.start();
    }

打印结果
读锁与写锁内容分别顺序打印。说明读写锁互斥。

     Thread-0 当前为write Lock========
     Thread-0 write lock end ========
     Thread-0 程序结束 end
     Thread-1 当前为read Lock========
     Thread-1 read lock end ========
     Thread-1 程序结束 end

读锁不支持Condition操作,而写锁支持

从源码中看到,ReadLock的newCondition方法被禁用了

 public Condition newCondition() {
            throw new UnsupportedOperationException();
        }

而WriteLock可以创建

public Condition newCondition() {
            return sync.newCondition();
        }

支持降级锁,不支持升级锁

降级锁: 线程进入writeLock,没有释放锁,同时获取到readLock。这时,writeLock会降级为readLock。但是writeLock的锁仍需手动释放。ReentrantReadWriteLock支持降级锁。

public void downGradeLock(){
            writeLock.lock();

            try {
                System.out.println(Thread.currentThread().getName() + " 当前进入写锁 write...");
                readLock.lock();
                System.out.println(Thread.currentThread().getName() + " 当前进入读锁 read...");
            } finally {
                System.out.println(Thread.currentThread().getName() + " 释放读锁...");
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + " 释放写锁...");
                writeLock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 程序结束...");
        }

打印结果:
程序在持有写锁的同时,可以获得读锁。说明,ReentrantReadWriteLock支持降级锁。

main 当前进入写锁 write...
main 当前进入读锁 read...
main 释放读锁...
main 释放写锁...
main 程序结束...

升级锁: 线程进入readLock,没有释放锁,同时要获取writeLock。
这时,程序进入死锁状态。ReentrantReadWriteLock不支持升级锁。
调用下面的方法,会出现死锁现象。

public void upGradeLock(){
            readLock.lock();
            System.out.println(Thread.currentThread().getName() + " 当前进入写锁 read...");
            writeLock.lock();
            System.out.println(Thread.currentThread().getName() + " 当前进入写锁 write...");
            writeLock.unlock();
            System.out.println("程序结束....");
        }

打印结果:

main 当前进入写锁 read...

并且程序一直在等待,尝试获取writeLock。