并发(三):Lcok与synchronized区别

来源:互联网 发布:小米网络电视机 编辑:程序博客网 时间:2024/05/01 21:14

1.什么是锁?

锁是用来控制多个线程访问共享资源的方式。一般来说,一个锁能够防止多个线程同时访问共享资源。

2.synchronized和lock比较:



Lock接口虽然需要通过显示方法来获取和释放锁,但是却拥有了锁获取与释放的可操作性、可中断的获取锁、以及超时获取锁等多种synchronized关键字不具备的同步性。


获取锁前:

a.锁获取与释放的可操作性:

1.对锁的获取我可以显示的使用lock()方法,锁的释放显示的使用unlock()方法。需要同步的代码写在加锁和释放锁的中间,可以灵活调整加锁和解锁方法的位置。
2.尝试非阻塞的获取锁(tryLock()):当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功获取并持有锁。如果这一时刻锁被其他线程获取到,当前线程可以选择继续等也可以选择不继续等,而synchronized锁是必须死等。

b.可中断的获取锁:

Lock接口提供lock.lockInterruptibly();方法,可中断的获取锁,和lock()不同之处在于该方法可以响应中断,即线程在等待获取锁的过程中(还没获取)线程调用interrupt()方法终止等待。


c.超时获取锁:

lock接口提供了tryLock(long time,TimeUnit unit)方法,在指定的截止时间内获取锁,如果截止时间到了仍旧无法获取,则返回。

获取锁中:

condition监视器:

Lock内嵌创建Condition监视器的方法newCondition()。synchronized使用object类中的监视器方法wait和notify而且只能创建一个监视器队列,但是Lock接口可以创建多个Condition实例然后调用await和singal来操作,两种监视器就不多解释了。


synchronized和ReentrantLock优缺点对比及疑问:

并发编程艺术一书上写:

轻量级锁:优点:竞争的线程不会阻塞,提高了程序的相应速度。
     缺点:如果始终得不到锁竞争的线程,使用自旋会消耗CPU。
适用场景:追求响应时间,同步块执行速度非常快。
重量级锁:优点:线程竞争不使用自旋,不会消耗CPU
缺点:线程阻塞,响应时间缓慢。
适用场景:追求吞吐量,同步块执行速度较长。

为什么说synchronized(重量级锁)竞争的线程是阻塞的而不是就绪等待?ReentrantLock线程运行的时候也是添加到同步队列中,为什么你的线程不会阻塞?难道你

ReentrantLock比我多个可以停止等待就高级了,你线程不停自旋还浪费cpu呢?

个人理解:
1) 就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。
2.) 阻塞状态,又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

synchronized锁竞争原则是,所有就绪的线程一起去抢这把锁,一个线程抢到就进入运行状态,而其他线程因为竞争失败而进入了阻塞状态(因为所有竞争线程都参与了锁的竞争,所以全部进入了阻塞状态,等待锁释放。当锁释放后从就绪到竞争到阻塞,反复执行),阻塞状态响应是很缓慢的。

ReentrantLock锁竞争原则是,线程来了排好队,只让队列首的那个一个线程去执行(就一个线程执行何谈争抢)直到该线程执行完毕,在从队列取一个线程继续执行。(缺点:很有可能死锁,出去执行的这个线程不回来报道了,队列中其他线程不敢轻举妄动).




两种锁获取采用的是不同的机制,访问synchronized代码块的进程都去抢一把锁抢不到锁线程变为阻塞。

ReentrantLock修饰的代码块,由Java来控制访问的先后顺序,每次只让一个线程去执行,其他等待。通过运行代码查看下线程状态。


public class testZKlock  {@Testpublic  void main() throws InterruptedException {Thread t1=new Thread(new Thread1());t1.setName("t1");t1.start();Thread t2=new Thread(new Thread2());t2.setName("t2");t2.start();System.out.println("t1状态"+t1.getState());Thread.sleep(1000);System.out.println("t2状态"+t2.getState());Thread.sleep(5000);}}class Thread1 implements Runnable{@Overridepublic void run() {MyService.method();}}class Thread2 implements Runnable{@Overridepublic void run() {MyService.method();}}class MyService{static Lock lock=new ReentrantLock();public /*synchronized*/ static void method(){lock.lock();try {System.out.println(Thread.currentThread().getName()+"==>执行");Thread.sleep(5000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}finally{lock.unlock();}}}

ReejtrantLock锁执行时:



synchronized锁执行时:



可见ReentrantLock是不会阻塞,只会等待的。而synchronized只会阻塞。


3.Lock子类实现流程:

lock接口实现类创建模式:如果把lock的实现类比作飞机供人使用,那么AbstractQueuedSynchronizer(AQS)就好比飞机的发动机供飞机使用。
1.这个AQS中有一个state变量,我们通过对该变量的不同CAS操作可以实现各种各样的锁,比如某一时刻只能两个2线程同时使用的锁(将state设置为2),再比如ReentrantReadWriteLock通过将state变量分成16位读16位写来实现。
2.如果是共享锁要重写tryAcquireShared()+tryRelaseShared(),如果是独占锁重写tryAcquire()+tryRelase(),然后调用AQS的模板方法acquire()获取acquireShared()等来实现自己的需求。
3.AQS维持一个双向链表队列,线程未获取到锁时会CAS加入到队列尾部,释放的时候head指向正在执行节点的下一个节点这时候不用CAS操作因为执行的时候就一个不存在不安全情况。每个节点通过自旋判断当前节点的前一个节点是不是head是的话就执行。(还是看看源码吧!表达能力太差了!)


4.ReentrantLock:公平锁和不公平锁:
默认是不公平的,其实之前我有一点不明白明明AQS队列按FIFO来组织起来的,获取锁的肯定是队列中最前头的节点,肯定是公平的为啥还来个公平锁。

不公平的获取锁方法调用:
 final boolean nonfairTryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                if (compareAndSetState(0, acquires)) {                    setExclusiveOwnerThread(current);                    return true;                }            }            else if (current == getExclusiveOwnerThread()) {                int nextc = c + acquires;                if (nextc < 0) // overflow                    throw new Error("Maximum lock count exceeded");                setState(nextc);                return true;            }            return false;        }

公平获取锁:

protected final boolean tryAcquire(int acquires) {            final Thread current = Thread.currentThread();            int c = getState();            if (c == 0) {                if (!hasQueuedPredecessors() &&                    compareAndSetState(0, acquires)) {                    setExclusiveOwnerThread(current);                    return true;                }            }            else if (current == getExclusiveOwnerThread()) {                int nextc = c + acquires;                if (nextc < 0)                    throw new Error("Maximum lock count exceeded");                setState(nextc);                return true;            }            return false;        }

相对谁是不公平的?
队列中的锁都是公平的,因为大家按顺序排列FIFO。而对刚刚执行的线程来说就是不公平的,你刚刚执行完了,很有可能还要再执行一遍(明明一人只能买一张票,你确帮别人买了一张,这不公平)。

公平锁相对于不公平相对多了一句!hasQueuedPredecessors(),也就是判断下当前线程是不是在队列中,在的话才执行。(P139)

公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换,非公平锁虽然可能造成队列中的线程较长时间获取不到锁,但是极少线程切换,保证了更大的吞吐量。

总结:Lock的核心就是AQS,AQS玩的就是state。



5.锁类型:

可重入锁:在执行对象中所有同步方法不用再次获得锁。ReentrantLock和synchronized
可中断锁:在等待获取锁过程中可中断。ReentrantLock
公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利.ReentrantLock --FairSync
读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写。ReentrantReadWriteLock

原创粉丝点击