一、 synchronized
原生关键字
二、 Lock
lock()方法:获取锁,被占用则等待,直到等到为止,即阻塞式获取;
lock.lockInterruptibly()方法:类似lock(),唯一不同的是,这个获取(阻塞)过程可以代码去中断的,即假设B线程在等待A线程的锁,B线程可以通过调用interrupt()方法中断自己的等待。注意:只能中断阻塞线程,不会中断执行中的线程。 更确切 的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞, 那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
tryLock():非阻塞式获取,即获取不到不会等待,立即返回失败
tryLock(long time, TimeUnit unit):非阻塞式获取,即获取不到,则会等待unit时间,如果还未获取到,则返回失败。
newCondition(): 返回一个绑定到lock对象上的condition对象,作用是处理一些条件下的问题,如边界问题(如数组满则不能put,数组空则不能take等)
ReentrantLock,即 可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
参考:
三、 ReadWriteLock
自定义
四、 synchronized 与 Lock
Lock提供了比synchronized更多的功能。但是要注意以下几点:
1)synchronized是Java语言的关键字,因此是内置特性,Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。
2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。
参考:
Synchronized与ReentrantLock区别总结(简单粗暴,一目了然)
五、可重入锁与不可重入锁
可重入锁, 即一个线程可以进入任何一个 该线程 已经拥有的锁所同步着的代码块 。
参考: https://www.cnblogs.com/xdyixia/p/9383388.html
六、关于公平锁和非公平锁
然后将当前线程记录为独占锁的线程,继续判断当前线程是否为独断锁的线程,ReentrantLock是可冲入的,线程可以不停地Lock来增加state的值,对应的需要unlock来解锁,减少state的值
如果上面的条件判断失败,即获取锁失败,则将线程加入到等待线程队列队尾,然后阻塞线程,等待被唤醒
非公平锁:非公平锁逻辑基本跟公平锁一致,最本质的区别是,当当前的锁状态没有被占用时,当前线程可以直接占用,而不需要判断当前队列中是否有等待线程。
参考:
七、AQS
AQS即 AbstractQueuedSynchronizer ,类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...。
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:
- getState()
- setState()
- compareAndSetState()
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
参考:Java并发之AQS详解
八、
九、AtomicInteger原理
利用的CAS原理: CAS的思想很简单,三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
AtomicInteger主要作用是解决i++不是原子操作的问题,i++在并发程序下,可能会导致i的数值不对,使用AtomicInteger则不会。
java的并发原子包里面提供了很多可以进行原子操作的类,比如:
- AtomicInteger
- AtomicBoolean
- AtomicLong
- AtomicReference
我们知道基本类型的赋值操作是原子操作,但是类似这种i++
的操作并不是原子操作,通过反编译代码我们可以大致了解此操作分为三个阶段:
tp1 = i; //1
tp2 = tp1 + 1; //2
i = tp2; //3
如果有两个线程m和n要执行i++操作,因为重排序的影响,代码执行顺序可能会发生改变。如果代码的执行顺序是m1 - m2 - m3 - n1 - n2 - n3,那么结果是没问题的,如果代码的执行顺序是m1 - n1 - m2 - n2 - m3 - n3那么很明显结果就会出错。
参考:
来源:oschina
链接:https://my.oschina.net/weiweiblog/blog/3160612