《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁

目录

Lock接口

队列同步器

重入锁

synchronized关键字隐式支持可重入

ReentrantLock

公平锁与非公平锁的优劣

读写锁

LockSupport 工具

Condition接口


Lock接口

锁是用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源(读写锁可以允许多个线程并发的访问共享资源)。Java SE 5之前,Java程序是靠synchronized关键字实现锁功能,而Java SE 5 之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第1张图片

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第2张图片

 图片来源:百度

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
       // ...
    } finally {
       lock.unlock();
    }

在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。 


队列同步器

队列同步器(AbstractQueuedSynchronizer AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO(First in first out 先进先出) 队列来完成资源获取线程的排队工作。


重入锁

重入锁 ReentrantLock,它能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择(在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的)。 

synchronized关键字隐式支持可重入

public class ReentrantLockDemo {

    public static void main(String[] args) {
        ReentrantLockDemo lockDemo = new ReentrantLockDemo();
        lockDemo.synchronizedTest();
    }

    private void synchronizedTest() {
        new Thread(() -> {
            // 第一次
            synchronized (this) {
                System.out.println("first lock...");
                int count = 0;
                while (true) {
                    // 循环进入
                    synchronized (this) {
                        System.out.println(count + " lock...");
                        count++;
                        if (count == 3) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }

}

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第3张图片

ReentrantLock

public class ReentrantLockDemo {

    private final Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        ReentrantLockDemo lockDemo = new ReentrantLockDemo();
        lockDemo.reentrantLockTest();
    }

    private void reentrantLockTest() {
        new Thread(() -> {
            // 第一次
            lock.lock();
            System.out.println("first lock...");
            try {
                int count = 0;
                while (true) {
                    // 循环进入
                    lock.lock();
                    System.out.println(count + " lock...");
                    count++;
                    if (count == 3) {
                        break;
                    }
                }
            } finally {
                lock.unlock();
            }
        }).start();
    }

}

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第4张图片

ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

公平锁与非公平锁的优劣

公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平锁虽然可能造成 “饥饿”,但极少的线程切换保证了其更大的吞吐量。 


读写锁

读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第5张图片

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第6张图片图片来源:百度

public class ReentrantReadWriteLockDemo {
    
    static Map cacheMap = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    // 分别获取读写锁
    static Lock readLock = rwl.readLock();
    static Lock writeLock = rwl.writeLock();

    /**
     * 获取一个 key 对应的 value
     */
    public static final Object get(String key) {
        readLock.lock();
        try {
            return cacheMap.get(key);
        } finally {
            readLock.unlock();
        }
    }

    /**
     * 设置 key 对应的 value,并返回旧的 value
     */
    public static final Object put(String key, Object value) {
        writeLock.lock();
        try {
            return cacheMap.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * 清空所有的内容
     */
    public static final void clear() {
        writeLock.lock();
        try {
            cacheMap.clear();
        } finally {
            writeLock.unlock();
        }
    }
    
}

上述示例中,mapCache组合了一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证mapCache是线程安全的。在读操作get(String key)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put(String key, Object value)方法和clear() 方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。mapCache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程。


LockSupport 工具

LockSuppot 定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread) 方法用来唤醒一个被阻塞的线程。

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第7张图片

public class ParkTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockSupport.park();
            System.out.println("park...");
        });
        Thread t2 = new Thread(() -> {
            // unpark 和 park 顺序颠倒不影响
            System.out.println("unpark...");
            LockSupport.unpark(t1);
        });

        t1.start();
        t2.start();
    }
}

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第8张图片


Condition接口

Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。 

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第9张图片

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第10张图片

public class ConditionDemo {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    static long start = 0l;

    public void conditionWait() throws InterruptedException {
        start = System.currentTimeMillis();
        lock.lock();
        try {
            System.out.println("conditionWait await..." + start);
            condition.await();
        } finally {
            lock.unlock();
        }
    }

    public void conditionSignal() {
        lock.lock();
        try {
            System.out.println("conditionSignal signal..." + System.currentTimeMillis());
            condition.signal();
            System.out.println("cost time: " + (System.currentTimeMillis() - start));
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionDemo conditionDemo = new ConditionDemo();
        new Thread(() -> {
            try {
                conditionDemo.conditionWait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(1000);
        new Thread(conditionDemo::conditionSignal).start();
    }

}

《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁_第11张图片

你可能感兴趣的:(Java并发编程与实战落地,java,并发,锁,多线程,并发编程)