在并发场景中,保证同一时刻只有一个线程对有并发隐患的代码进行操做java
需求:两个线程对 count 变量进行200000次循环增长,预期结果是400000次多线程
public class SynchronizedDemo implements Runnable { private static int count = 0; static SynchronizedDemo synchronizedInstance = new SynchronizedDemo(); public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance); Thread t2 = new Thread(synchronizedInstance); t1.start(); t2.start(); try { t1.join(); t2.join(); System.out.println("count 最终的值为: " + count); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { synchronized (this) { for (int i = 0; i < 200000; i++) { count++; } } } }
结果 :显然不等于400000次因此出现了运算错误并发
缘由:异步
count++;
该语句包含三个操做:ide
注意:他们是将本身工做内存中的值进行改变刷回主内存,假设当前count的值为8,t一、t2将count的值复制到本身的工做内存中进行修改,若是此时t1将count变成九、t2此时也将count的值变成9,当t一、t2两个线程都将值刷回主内存的时候count值为9,并非10,这个时候就会形成最后的结果和预期的不一致。性能
@Override public void run() { synchronized (this) { for (int i = 0; i < 200000; i++) { count++; } } }
@Override public synchronized void run() { for (int i = 0; i < 200000; i++) { count++; } }
@Override public void run() { for (int i = 0; i < 200000; i++) { synchronized (SynchronizedDemo.class) { count++; } } }
输出结果:优化
后文详细讲解四种加 synchronized 的方式this
2.1.1 方法锁操作系统
修饰普通方法默认锁对象为this当前实例对象线程
public synchronized void method() ;在普通方法上面加synchronized
public class SynchronizedDemo3 implements Runnable { static SynchronizedDemo3 synchronizedDemo3 = new SynchronizedDemo3(); public synchronized void method() { System.out.println("线程名称" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedDemo3); t1.setName("我是线程 t1"); Thread t2 = new Thread(synchronizedDemo3); t2.setName("我是线程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果: 线程 t1 和线程 t2 执行过程是顺序执行的
2.1.2 同步代码块
输出结果:线程 t1 和线程 t2 交叉执行造成了乱序
输出结果:线程 t1 和线程 t2 执行过程是顺序执行的
输出结果:线程 t1 和线程 t2 执行造成了顺序,这种状况下和this没有什么区别,可是若是是多个同步代码块的话就须要进行自定义对象锁了
输出结果:输出顺序线程t1 和线程t2 代码进行了交叉执行,出现了乱序
输出结果:线程 t1 和线程 t2 执行造成了顺序
特色:类锁只能在同一时间被一个对象拥有(不管有多少个实例想访问也是一个对象持有它)
2.2.1 synchronized修饰静态的方法
代码示例: synchronized 加在普通方法上面
public class SynchronizedDemo4 implements Runnable { private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4(); private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4(); public synchronized void method() { System.out.println("线程名称" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是线程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是线程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果:输出顺序线程t1 和线程t2 代码进行了交叉执行,出现了乱序
public static synchronized void method();使用方式
public class SynchronizedDemo4 implements Runnable { private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4(); private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4(); public static synchronized void method() { System.out.println("线程名称" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成"); } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是线程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是线程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果:线程 t1 和线程 t2 执行造成了顺序
2.2.2 指定锁对象为Class对象
*synchronized (**SynchronizedDemo5.class**)*
public class SynchronizedDemo5 implements Runnable { private static SynchronizedDemo5 synchronizedInstance1 = new SynchronizedDemo5(); private static SynchronizedDemo5 synchronizedInstance2 = new SynchronizedDemo5(); void method() { synchronized (SynchronizedDemo5.class) { //类锁只有一把 System.out.println("线程名称" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成"); } } @Override public void run() { method(); } public static void main(String[] args) { Thread t1 = new Thread(synchronizedInstance1); t1.setName("我是线程 t1"); Thread t2 = new Thread(synchronizedInstance2); t2.setName("我是线程 t2"); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
输出结果: 线程 t1 和线程 t2 执行造成了顺序
就是说你已经获取了一把锁,等想要再次请求的时候不须要释放这把锁和其余线程一块儿竞争该锁,能够直接使用该锁
好处:避免死锁
粒度:线程而非调用
代码实例:
package synchronizedPage; public class SynchronizedDemo6 { int count = 0; public static void main(String[] args) { SynchronizedDemo6 synchronizedDemo6 = new SynchronizedDemo6(); synchronizedDemo6.method(); } private synchronized void method() { System.out.println(count); if (count == 0) { count++; method(); } } }
输出结果:
代码实例:
package synchronizedPage; public class SynchronizedDemo7 { private synchronized void method1() { System.out.println("method1"); method2(); } private synchronized void method2() { System.out.println("method2"); } public static void main(String[] args) { SynchronizedDemo7 synchronizedDemo7 = new SynchronizedDemo7(); synchronizedDemo7.method1(); } }
输出结果:
代码实例:
package synchronizedPage; public class SynchronizedDemo8 { public synchronized void doSomething() { System.out.println("我是父类方法"); } } class childrenClass extends SynchronizedDemo8{ public synchronized void doSomething() { System.out.println("我是子类方法"); super.doSomething(); } public static void main(String[] args) { childrenClass childrenClass = new childrenClass(); childrenClass.doSomething(); } }
输出结果:
当A线程持有这把锁时,B线程若是也想要A线程持有的锁时只能等待,A永远不释放的话,那么B线程永远的等待下去。
public void test() { synchronized(this){ count++; } }
利用 javap -verbose 类的名字查看编译后的文件
monitorenter:每一个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的全部权,过程以下:
monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的全部者。指令执行时,monitor的进入数减1,若是减1后进入数为0,那线程退出monitor,再也不是这个monitor的全部者。其余被这个monitor阻塞的线程能够尝试去获取这个 monitor 的全部权
monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁
public synchronized void test() { count++; }
利用 javap -verbose 类的名字查看编译后的文件
方法的同步并无经过指令monitorenter和monitorexit来完成,不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,若是设置了,执行线程将先获取monitor,获取成功以后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其余任何线程都没法再得到同一个monitor对象,其实底层仍是monitor对象锁。
从JDK6开始,就对synchronized的实现机制进行了较大调整,包括使用JDK5引进的CAS自旋以外,还增长了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁这些优化策略。因此synchronized关键字的优化使得性能极大提升,同时语义清晰、操做简单、无需手动关闭,因此推荐在容许的状况下尽可能使用此关键字,同时在性能上此关键字还有优化的空间。
无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
锁的膨胀过程:
无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁
只能从低到高升级,不会出现锁的降级
所谓自旋锁,就是指当一个线程尝试获取某个锁时,若是该锁已被其余线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。(减小线程切换)
使用场景: 自旋锁适用于锁保护的临界区很小的状况,临界区很小的话,锁占用的时间就很短。
缺点:虽然它能够避免线程切换带来的开销,可是它占用了CPU处理器的时间。若是持有锁的线程很快就释放了锁,那么自旋的效率就很是好,反之,自旋的线程就会白白消耗掉处理的资源,它不会作任何有意义的工做,因此增长了适应性自选锁
所谓自适应就意味着自旋的次数再也不是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
线程若是自旋成功了,那么下次自旋的次数会更加多,由于上次成功了,那么这次自旋也颇有可能会再次成功,那么它就会容许自旋等待持续的次数更多。反之,不多可以成功,那么之后自旋的次数会减小甚至省略掉自旋过程,以避免浪费处理器资源。
为了保证数据的完整性,在进行操做时须要对这部分操做进行同步控制,可是在有些状况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。做为写程序的人应该会知道哪里存在数据竞争,不可能随便的加锁。
将多个连续的加锁、解锁操做链接在一块儿,扩展成一个范围更大的锁。虽然咱们平时倡导把加锁的片断尽可能小为了增长并发效率和性能。可是若是一系列的连续加锁解锁操做,可能会致使没必要要的性能损耗,因此引入锁粗化。
在大多数状况下,锁不只不存在多线程竞争,并且老是由同一线程屡次得到,为了让线程得到锁的代价更低,引进了偏向锁。偏向锁是在单线程执行代码块时使用的机制,若是在多线程并发的环境下(即线程A还没有执行完同步代码块,线程B发起了申请锁的申请),则必定会转化为轻量级锁或者重量级锁。
引入偏向锁主要目的是:为了在没有多线程竞争的状况下尽可能减小没必要要的轻量级锁执行路径。由于轻量级锁的加锁解锁操做是须要依赖屡次CAS原子指令的,而偏向锁只须要在置换ThreadID的时候依赖一次CAS原子指令。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,之后该线程进入和退出同步块时不须要花费CAS操做来争夺锁资源,只须要检查是否为偏向锁、锁标识为以及ThreadID便可,处理流程以下:
偏向锁的获取和撤销流程:
引入轻量级锁的主要目的是在没有多线程竞争的前提下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁致使偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤以下:
轻量级锁的释放也是经过CAS操做来进行的,主要步骤以下:
问题:
由于在申请对象锁时须要以该值做为CAS的比较条件,同时在升级到重量级锁时,能经过这个比较断定是否在持有锁的过程当中此锁被其余线程申请过,若是被其余线程申请了,则在释放锁的时候要唤醒被挂起的线程。
Synchronized是经过对象内部的一个叫作监视器锁(Monitor)来实现的。可是监视器锁本质又是依赖于底层的操做系统的Mutex Lock来实现的。而操做系统实现线程之间的切换这就须要从用户态转换到核心态,这个成本很是高,性能消耗特别严重。 所以,这种依赖于操做系统Mutex Lock所实现的锁咱们称之为 “重量级锁”。