Lock接口

在Java 1.5中,官方在concurrent并发包中加入了Lock接口。

  1. public interface Lock {
  2. void lock();//加锁
  3. //可中断获取锁,与lock()不同之处在于可响应中断操作
  4. void lockInterruptibly() throws InterruptedException;
  5. //试非阻塞获取锁,调用该方法后立即返回结果,如果能够获取则返回true,否则返回false
  6. boolean tryLock();
  7. //根据传入的时间段获取锁,在指定时间内没有获取锁则返回false,如果在指定时间内当前线程未被中并断获取到锁则返回true
  8. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  9. void unlock();//解锁
  10. //获取等待通知组件,该组件与当前锁绑定,当前线程只有获得了锁,才能调用该组件方法
  11. Condition newCondition();
  12. }

lock方法

lock()方法是无返回值,如果锁已被其他线程获取则进行等待。lock必须主动去释放锁,并且在发生异常时,不会自动释放锁。故使用lock必须在try-catch-finally块中进行,以保证锁一定被被释放,防止死锁的发生。

public class LockTest {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        final LockTest test = new LockTest();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();

        new Thread(){
            public void run(){
                test.insert(Thread.currentThread());
            }
        }.start();
    }

    public void insert(Thread thread){
        lock.lock();
        try {
            System.out.println(thread.getName() + "得到了锁");
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
            }
        } catch (Exception e) {
        } finally {
            System.out.println(thread.getName() + "释放了锁");
            lock.unlock();
        }
    }
}

Thread-1得到了锁
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
Thread-1释放了锁
Thread-0得到了锁
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
Thread-0释放了锁

tryLock方法

tryLock()方法是有返回值的,如果获取锁成功则返回true,如果获取失败则返回false,这个方法无论如何都会立即返回,拿不到锁时也不会一直在那等待。

tryLock(long time, TimeUnit unit)方法拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

public class LockTest {
    private Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        LockTest test = new LockTest();
        new Thread(()->{test.insert(Thread.currentThread());}).start();
        new Thread(()->{test.insert(Thread.currentThread());}).start();
    }
    public void insert(Thread thread){
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了锁");
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
                }
            } catch (Exception e) {
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"获取锁失败");
        }
    }
}

Thread-0得到了锁
ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
Thread-0释放了锁
Thread-1得到了锁
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
Thread-1释放了锁

lockInterruptibly方法

lockInterruptibly()无返回值,但是会抛出异常,如果线程正在等待获取锁,则这个线程能中断线程的等待状态。即当两个线程同时通过lockInterruptibly()想获取某个锁时,若线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
当一个线程获取了锁之后,是不会被interrupt()方法中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

public class LockTest {
    private Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        LockTest test = new LockTest();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //两秒后thread2中断
        thread2.interrupt();
    }
    public void insert(Thread thread) throws InterruptedException {
        lock.lockInterruptibly();
        try {
                System.out.println(thread.getName()+"得到了锁");
            long startTime = System.currentTimeMillis()/1000;
            //超过4s就会释放锁
            for (; ;) {
                if (System.currentTimeMillis()/1000 - startTime >= 4)
                    break;
                }
            } catch (Exception e) {
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
    }

}
class MyThread extends Thread {
    private LockTest test;
    public MyThread(LockTest test) {
        this.test = test;
    }
    @Override
    public void run() {
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "被中断");
        }
    }
}

Thread-0得到了锁
Thread-1被中断
Thread-0释放了锁

public class LockTest {
    private Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        LockTest test = new LockTest();
        new Thread(()->{
            try {
                test.insert(Thread.currentThread());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                test.insert(Thread.currentThread());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
    public void insert(Thread thread) throws InterruptedException {
        lock.lockInterruptibly();
        try {
                System.out.println(thread.getName()+"得到了锁");
            long startTime = System.currentTimeMillis()/1000;
            //超过2s就会释放锁
            for (; ;) {
                if (System.currentTimeMillis()/1000 - startTime >= 2)
                    break;
                }
            } catch (Exception e) {
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
    }
}

Thread-1得到了锁
Thread-1释放了锁
Thread-0得到了锁
Thread-0释放了锁

重入锁ReetrantLock

ReetrantLock,JDK 1.5新增的类,实现了Lock接口,作用与synchronized关键字相当,但比synchronized更加灵活。
ReetrantLock本身也是一种支持重进入的锁,即该锁可以支持一个线程对资源重复加锁,同时也支持公平锁与非公平锁。
image.png
image.png

public class ReenterLock implements Runnable{
    public static ReentrantLock lock=new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        for(int j=0;j<10000000;j++){
            lock.lock();
            //支持重入锁
            lock.lock();
            try{
                i++;
            }finally{
                //执行两次解锁
                lock.unlock();
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReenterLock tl=new ReenterLock();
        Thread t1=new Thread(tl);
        Thread t2=new Thread(tl);
        t1.start();t2.start();
        t1.join();t2.join();
        //输出结果:20000000
        System.out.println(i);
    }
}

Condition

Condition可通过多个Condition实例对象建立更加精细的线程控制。AQS中存在两种队列,一种是同步队列,一种是等待队列,而等待队列就相对于Condition而言的。
使用Condition前必须获得锁,其结点的waitStatus的值为CONDITION,在实现类ConditionObject中有两个结点分别是firstWaiter和lastWaiter。


 public class ConditionObject implements Condition, java.io.Serializable {
    //等待队列第一个等待结点
    private transient Node firstWaiter;
    //等待队列最后一个等待结点
    private transient Node lastWaiter;
    //省略其他代码.......
}

ReentrantReadWriteLock

public class ReadWrite {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final MyCache myCache=new MyCache();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(new Runnable() {
                @Override
                public void run() {

                    myCache.write(temp+"", temp+"");
                }
            },String.valueOf(i)).start();
        }

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myCache.read(temp+"");
                }
            },String.valueOf(i)).start();
        }
    }

}
class MyCache{
    private volatile Map<String,Object> map=new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    //新建一个集合 储存读或者写的内容
    public void  read(String key) {
        reentrantReadWriteLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t正在读取"); 
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result= map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
        }finally {
            reentrantReadWriteLock.readLock().unlock();
        }

    }

    public void  write(String key,Object value) {
        reentrantReadWriteLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t正在写入:"+key);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+"\t  写入完成!");
        }finally {
            reentrantReadWriteLock.writeLock().unlock();
        }

    } 
}

image.png

3.Synchronized 与Lock

  • Synchronized 是Java关键字,属于 JVM 层面,底层是通过 monitorenter 和 monitorexit 完成,依赖于 monitor 对象来完成;Lock是java.util.concurrent.locks.lock,底层是AQS。
  • synchronized是不可中断的,而Lock可以是不可中断的也可以是可中断的。
  • Synchronized 会自动释放锁,Lock必须要手动加锁和手动释放锁,可能会遇到死锁。
  • Synchronized 无法判断获取锁的状态,Lock可以判断。
  • synchronized能锁住方法和代码块,而Lock只能锁住代码块。
  • Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
  • synchronized是非公平锁,ReentrantLock可以控制是否是公平锁。
  • Lock可以使用读锁提高多线程效率。
  • synchronized随机唤醒一个或全部线程,Lock可以精确唤醒。

    参考

    基于并发AQS的(独占锁)重入锁(ReetrantLock)及其Condition实现原理