7、线程同步机制(锁)

2019-04-17  本文已影响0人  小manong

一、锁

(1)一个线程在访问共享数据时候,必须先去申请锁(类似许可证),这个工作成为锁的获得,一个线程获得了锁,就称该线程为相应锁的持有线程,一个锁一次只能被一个线程持有。(2)锁的持有线程可以对所保护的资源进行访问,访问结束后该线程必须释放相应的锁。

1、锁的作用

(1)保障原子性
锁的互斥性保证了原子性。因为互斥性,一个锁一次只能被一个线程所持有,其他线程只能等待持有锁的线程释放锁才能有机会获取锁,这保证了一个线程在执行临界区时候,其他线程不能够访问共享数据,使得临界区的代码自然变成不可分割的特性,及具备了原子性。

(2)保障可见性
可见性保证是通过写线程冲刷处理器缓存和读线程刷新处理器缓存完成的。而锁的获得隐含了刷新处理器缓存的动作,而锁的释放隐含了冲刷处理器缓存的工作,因此锁能够保障可见性。(实际上底层是通过内存屏障来实现的)

由于锁的互斥性和可见性保障合并在一起,因此锁能都保障临界区内的代码能够读取到共享数据的最新值。

(3)保障有序性
写线程在临界区中所执行的一些列操作在读线程所执行临界区看起来像是完成按照源代码的顺序执行的,即读线程对这些操作的感知顺序与源代码顺序一致。保障了顺序性,但是这不意味着在临界区中的代码不能够被重排序,这种在临界区中的重排序并不会影响线程的安全性。(实际上底层是也是通过内存屏障来实现的)

总结:

(1)线程访问同一组数据的时候必须使用同一个锁
(2)任何一个线程,即时仅仅是读取这组共享数据而没有对其进行更新的话,也需要持有相应的锁

2、锁的几个重要概念

(1)可重入性

3、锁的开销以及可能导致的问题

二、sychronized

1、作为锁句柄的变量常用final修饰,因为要保证只有的锁一样。比如private final Object object=new Object;
2、线程对内部锁的申请和释放的动作是jvm负责代为实施的,内部锁不会造成锁泄露问题。

三、显示锁:Lock接口

1、显示锁使用步骤

1、创建Lock接口的实例(无特别要求,默认实现类ReentrantLock)
2、在访问共享数据前申请相应的显示锁,lock.lock()
3、在临界区访问共享变量
4、共享数据访问结束后释放锁。lock.unlock()

 private final Lock lock = new ReentrantLock();
    private int sequence = 1;

    public int nextSequence() {
        lock.lock();
        try {
            if (sequence > 100) {
                sequence = 0;
            } else {
                sequence++;
            }
            return sequence;
        } finally {
            lock.unlock();
        }
    }
2、显示锁的调度
3、显示锁和内部锁的比较

(1)内部锁是基于代码块的锁,基本无灵活性可言。显示锁是基于对象的锁,可以充分发挥面向对象的灵活性。(比如显示锁的锁释放可以跨方法)
(2)内部锁简单易用,且不会造成内存泄露。显示锁会造成内存泄露,需要在finally中释放锁资源。
(3)内部锁可能会因为某种原因造成阻塞,而使其他的任务无法完成。但是显示锁可以通过tryLock解决配合if从句解决。

if (lock.tryLock()) {
            //访问共享数据
            try {
            } finally {
                lock.unlock();
            }
        } else {
            //执行其他操作
        }

(4)锁的调度方面,内部锁仅支持公平性调度,而显示锁支持公平和非公平调度。
(5)显示锁提供了一些api可以用来对锁的相关信息进行监控,而内部锁不支持
(6)在jdk1.6之前,显示锁的性能优于内部锁,jdk1.6之后,对内部锁做了很多优化,显示锁和内部的性能差不多。

四、读写锁

 private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();

    public void reader() {
        readLock.lock();
        try {
            //在此区域进行读取共享数据
        } finally {
            readLock.unlock();
        }
    }

    public void writer() {
        writeLock.lock();
        try {
            //在此区域进行读、写共享数据
        } finally {
            writeLock.unlock();
        }
    }

读写锁和锁的对比:

(1)读写锁和排他锁一样也能保障原子性、可见性、有序性。但是读写锁内部实现复杂,开销比排他锁大
(2)读写锁的使用场合(只读操作比写操作频繁的多、读线程持有锁的时间比较长)

五、锁总结

1、check-then-act操作(if-do)
2、read-modify-write操作(i++)
3、多个线程对多个共享数据进行更新时候

上一篇下一篇

猜你喜欢

热点阅读