若是向一个变量写值,而这个变量接下来可能会被另外一个线程所读取,或者从一个变量读值,而它的值多是前面由另外一个线程写入的,此时就必须使用同步。html
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码,它是在软件层面依赖JVM实现同步。 java
synchronized 方法或语句的使用提供了对与每一个对象相关的隐式监视器锁的访问,但却强制全部锁获取和释放均要出如今一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与全部锁被获取时相同的范围内释放全部锁。git
经过在方法声明中加入 synchronized关键字来声明 synchronized 方法。github
synchronized 方法控制对类成员变量的访问:每一个类实例对应一把锁,每一个 synchronized 方法都必须得到调用该方法的类实例的锁方能执行,不然所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能得到该锁,从新进入可执行状态。这种机制确保了同一时刻对于每个类实例,其全部声明为 synchronized 的成员函数中至多只有一个处于可执行状态(由于至多只有一个可以得到该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要全部可能访问类成员变量的方法均被声明为 synchronized)。编程
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized,因为在线程的整个生命期内它一直在运行,所以将致使它对本类任何 synchronized 方法的调用都永远不会成功。多线程
解决synchronized 方法的缺陷 并发
经过 synchronized关键字来声明synchronized 块。分布式
synchronized(lock) { // 访问或修改被锁保护的共享状态 }
其中的代码必须得到对象 syncObject (类实例或类)的锁方能执行。因为能够针对任意代码块,且可任意指定上锁的对象,故灵活性较高。ide
当两个并发线程访问同一个对象中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。函数
当一个线程访问对象的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该对象中的非synchronized(this)同步代码块。其余线程对对象中全部其它synchronized(this)同步代码块的访问将被阻塞。
若是线程进入由线程已经拥有的监控器保护的 synchronized 块,就容许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
在修饰代码块的时候须要一个reference对象做为锁的对象。在修饰方法的时候默认是当前对象做为锁的对象。在修饰类时候默认是当前类的Class对象做为锁的对象.
锁是用来控制多个线程访问共享资源的方式,通常来讲,一个锁可以防止多个线程同时访问共享资源(可是有些锁能够容许多个线程并发的访问共享资源,好比读写锁)。在Lock接口出现以前,Java程序是靠synchronized关键字实现锁功能的,而JavaSE5以后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字相似的同步功能,只是在使用时须要显式地获取和释放锁。虽然它缺乏了(经过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,可是却拥有了锁获取与释放的可操做性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具有的同步特性。
使用synchronized关键字将会隐式地获取锁,可是它将锁的获取和释放固化了,也就是先获取再释放。固然,这种方式简化了同步的管理,但是扩展性没有显示的锁获取和释放来的好。例如,针对一个场景,手把手进行锁获取和释放,先得到锁A,而后再获取锁B,当锁B得到后,释放锁A同时获取锁C,当锁C得到后,再释放B同时获取锁D,以此类推。这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却容易许多。Lock 接口的实现容许锁在不一样的做用范围内获取和释放,并容许以任何顺序获取和释放多个锁,从而支持使用这种技术。在大多数状况下,应该使用如下语句:
Lock l = ...; //lock接口的实现类对象 l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
在finally块中释放锁,目的是保证在获取到锁以后,最终可以被释放。
不要将获取锁的过程写在try块中,由于若是在获取锁(自定义锁的实现)时发生了异常,异常抛出的同时,也会致使锁无端释放。
Lock接口提供的synchronized关键字所不具有的主要特性以下表所示。
Lock是一个接口,它定义了锁获取和释放的基本操做,Lock的API以下表所示。
Lock 接口实现提供了比使用 synchronized 方法和语句可得到的更普遍的锁定操做。此实现容许更灵活的结构,能够具备差异很大的属性,能够支持多个相关的 Condition 对象。在硬件层面依赖特殊的CPU指令实现同步更加灵活。
什么是Condition ? Condition 接口将 Object 监视器方法(wait、notify 和 notifyAll)分解成大相径庭的对象,以便经过将这些对象与任意 Lock 实现组合使用,为每一个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
在java.util.concurrent.locks包中有不少Lock的实现类,经常使用的有ReentrantLock、ReadWriteLock(实现类ReentrantReadWriteLock).它们是具体实现类,不是java语言关键字。
一个可重入的互斥锁 Lock,它具备与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
最典型的代码以下:
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
重入性:指的是同一个线程屡次试图获取它所占有的锁,请求会成功。当释放锁的时候,直到重入次数清零,锁才释放完毕。
ReentrantLock 的lock机制有2种,忽略中断锁和响应中断锁,这给咱们带来了很大的灵活性。好比:若是A、B 2个线程去竞争锁,A线程获得了锁,B线程等待,可是A线程这个时候实在有太多事情要处理,就是 一直不返回,B线程可能就会等不及了,想中断本身,再也不等待这个锁了,转而处理其余事情。这个时候ReentrantLock就提供了2种机制,第一,B线程中断本身(或者别的线程中断它),可是ReentrantLock 不去响应,继续让B线程等待,你再怎么中断,我全当耳边风(synchronized原语就是如此);第二,B线程中断本身(或者别的线程中断它),ReentrantLock 处理了这个中断,而且再也不等待这个锁的到来,彻底放弃。
ReentrantLock相对于synchronized多了三个高级功能:
①等待可中断
在持有锁的线程长时间不释放锁的时候,等待的线程能够选择放弃等待.
tryLock(long timeout, TimeUnit unit)
②公平锁
按照申请锁的顺序来一次得到锁称为公平锁.synchronized的是非公平锁,ReentrantLock能够经过构造函数实现公平锁.
new RenentrantLock(boolean fair)
公平锁和非公平锁。这2种机制的意思从字面上也能了解个大概:即对于多线程来讲,公平锁会依赖线程进来的顺序,后进来的线程后得到锁。而非公平锁的意思就是后进来的锁也能够和前边等待锁的线程同时竞争锁资源。对于效率来说,固然是非公平锁效率更高,由于公平锁还要判断是否是线程队列的第一个才会让线程得到锁。
③绑定多个Condition
经过屡次newCondition能够得到多个Condition对象,能够简单的实现比较复杂的线程同步的功能.经过await(),signal();
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。
synchronized采用的是CPU悲观锁机制,即线程得到的是独占锁。独占锁意味着其余线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引发线程上下文切换,当有不少线程竞争锁的时候,会引发CPU频繁的上下文切换致使效率很低。
Lock用的是乐观锁方式。每次不加锁而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试,直到成功为止。乐观锁实现的机制就 是CAS操做(Compare and Swap)。
ReentrantLock必须在finally中释放锁,不然后果很严重,编码角度来讲使用synchronized更加简单,不容易遗漏或者出错。
ReentrantLock提供了可轮询的锁请求,他能够尝试的去取得锁,若是取得成功则继续处理,取得不成功,能够等下次运行的时候处理,因此不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,因此更容易产生死锁。
synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock由于是方法调用,能够跨方法,灵活性更大。
通常状况下都是用synchronized原语实现同步,除非下列状况使用ReentrantLock:
①某个线程在等待一个锁的控制权的这段时间须要中断
②须要分开处理一些wait-notify,ReentrantLock里面的Condition应用,可以控制notify哪一个线程
③具备公平锁功能,每一个到来的线程都将排队等候
https://github.com/Mr-YangCheng/ForAndroidInterview/blob/master/java/%5BJava%5D%20%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5%E7%9A%84%E6%96%B9%E6%B3%95%EF%BC%9Asychronized%E3%80%81lock%E3%80%81reentrantLock%E5%88%86%E6%9E%90.md
分布式系统互斥性与幂等性问题的分析与解决:https://tech.meituan.com/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html
方腾飞:<Java并发编程的艺术>