• content

总结java线程基础知识

  • ReentrantLock
  • ReentrantReadWriteLock

ReentrantLock

与synchronized的相同点

  • 都是排它锁,性能差不多
  • 都支持重入

与synchronized的不同点

  • ReentrantLock同一个锁加锁和解锁的次数必须一致,少解锁一次会导致其他线程一直在等待,造成死锁
  • ReentrantLock支持公平锁/非公平锁,而synchronized只是非公平锁
  • ReentrantLock可以响应中断
  • ReentrantLock可以进行加锁超时设置
  • ReentrantLock提供Condition来实现等待-通知机制,用来替换Object.waite(),Object.nofity()方法

示例代码:利用Condition来创建自定义的阻塞队列

//实现一个阻塞队列
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MyBlockingQueue<E> {

    //阻塞队列最大容量
    int size;

    ReentrantLock lock = new ReentrantLock();

    //队列底层实现
    LinkedList<E> list=new LinkedList<>();

    //队列满时的等待条件
    Condition notFull = lock.newCondition();
    //队列空时的等待条件
    Condition notEmpty = lock.newCondition();

    public MyBlockingQueue(int size) {
        this.size = size;
    }

    public void enqueue(E e) throws InterruptedException {
        try {
            lock.lock();

            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" 开始生产,锁的hashCode: "+lock.hashCode());

            //队列已满,在notFull条件上等待
            while (list.size() ==size)
            {
                notFull.await();
            }
            //入队:加入链表末尾
            list.add(e);
            System.out.println("入队:" +e);
            //通知在notEmpty条件上等待的线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public E dequeue() throws InterruptedException {
        try {
            lock.lock();
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" 开始消费,锁的hashCode: "+lock.hashCode());

            //队列为空,在notEmpty条件上等待
            while (list.size() == 0)
            {
                notEmpty.await();
            }
            //出队:移除链表首元素
            E e = list.removeFirst();

            System.out.println(threadName + " 出队:"+e);
            //通知在notFull条件上等待的线程
            notFull.signal();
            return e;
        } finally {
            lock.unlock();
        }
    }
}

公平锁

ReentrantLock 实现原理(公平锁和非公平锁)

ReentrantLock默认是非公平锁,直接先通过CAS将state+1尝试获取锁,多线程会争抢锁,而公平锁是直接插入到CLH队列的尾部等待锁,先到先得

ReetrantReadWriteLock

读写锁的机制:

  • “读-读” 不互斥
  • “读-写” 互斥
  • “写-写” 互斥

锁的升级、降级:

写锁可以降级为读锁,反过来读锁不可以升级为写锁。就是说同一个线程获取写锁之后还可以再次获取读锁,不会死锁,但是反过来不行。

//同一个线程写锁通过重入的方式可以降级为读锁,反过来读锁不支持升级到写锁
ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
rwLock.writeLock().lock();
System.out.println("get write lock");
rwLock.readLock().lock();
System.out.println("get read lock");
rwLock.readLock().unlock();
System.out.println("read unlock");
rwLock.writeLock().unlock();
System.out.println("write unlock");

System.out.println(rwLock.getReadHoldCount());
System.out.println(rwLock.getWriteHoldCount());

读写缓存的示例,锁降级的目的是为了提高执行效率:

public class CacheDemo {
    private Map<String, Object> map = new HashMap<>(128);
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public Object get(String id){
        Object value = null;
        //首先开启读锁,开始读缓存数据
        rwl.readLock().lock();
        try{
            //如果缓存中没有释放读锁,上写锁
            if(map.get(id) == null){
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try{
                    //防止多写线程重复查询赋值
                    if(map.get(id) == null){
                        //此时可以去数据库中查找,这里简单的模拟一下
                        value = "value from thread: "+Thread.currentThread().getName();
                        map.put(id,value);
                    }

                    //写完成之后立即加读锁进行降级,目的是让写操作之后的逻辑不会影响其他线程的读操作(提高效率),但读锁这个时候还没有释放,可以继续执行读操作或者其它逻辑
                    rwl.readLock().lock();
                }finally{
                    //写操作完成之后立即降级为读锁,然后释放写锁
                    rwl.writeLock().unlock();
                }
            }

            //进行读操作
            return map.get(id);
        }finally{
            rwl.readLock().unlock(); //最终释放读锁
        }
    }
}

读锁之间可以共享,同一把锁多个线程可以同时加锁。写锁是独占的,写锁与写锁,写锁与读锁,都不能同时加锁。

public class ReadAndWriteLockTest {

    public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public static void main(String[] args) {

        //同时写
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new Runnable() {
            @Override
            public void run() {
                writeFile(Thread.currentThread());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                writeFile(Thread.currentThread());
            }
        });

        service.shutdown();
    }

    // 读操作
    public static void readFile(Thread thread) {
        lock.readLock().lock();
        boolean isWriteLocked = lock.isWriteLocked();
        if (!isWriteLocked) {
            System.out.println("当前为读锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行读操作……");
            }
            System.out.println(thread.getName() + ":读操作完毕!");
        } finally {
            System.out.println("释放读锁!");
            lock.readLock().unlock();
        }
    }

    // 写操作
    public static void writeFile(Thread thread) {
        lock.writeLock().lock();
        boolean isWriteLocked = lock.isWriteLocked();
        if (isWriteLocked) {
            System.out.println("当前为写锁!");
        }
        try {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(thread.getName() + ":正在进行写操作……");
            }
            System.out.println(thread.getName() + ":写操作完毕!");
        } finally {
            System.out.println("释放写锁!");
            lock.writeLock().unlock();
        }
    }
}

Synchronized与Lock的区别

  1. synchronized是java内置关键字,在jvm层面,Lock是java语言层面的接口;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到;
  3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
  5. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平
  6. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
  7. Lock是一个接口,synchronized是一个关键字,synchronized放弃锁只有两种情况:①线程执行完了同步代码块的内容②发生异常;而lock不同,它可以设定超时时间,也就是说他可以在获取锁时便设定超时时间,如果在你设定的时间内它还没有获取到锁,那么它会放弃获取锁然后响应放弃操作。

参考

ReentrantLock(重入锁)功能详解和应用演示

从源码角度彻底理解ReentrantLock(重入锁)

ReadWriteLock读写锁的使用

*****Java多线程之ReentrantLock与Condition

透彻理解Java并发编程