多个线程对index变量(共享变量/资源)同时操做引发的,再JDK1.5之前,要解决这个问题须要使用synchronized关键字,synchronized提供了一种排他的机制,同一个时间只能有一个线程执行某些操做。java
synchronized关键字能够实现一个简单的策略来放置线程干扰和内存一致行错误,若是一个对象多个线程是可见的,那么该对象全部读或者些都将经过同步的方式来进行。数据库
synchronized关键字提供了一种锁机制,可以确认保存共享变量的互斥访问,从而放置数据不一致问题的出现。缓存
synchronized关键字包括monitor enter和monitor exit 两个JVM指令,它可以保证再任什么时候候任何线程执行到monitor enter成功以前都必须从主内存中获取数据,而不是从缓存中,再monitor exit和运行成功以后,共享变量被更新后的值必须刷入主内存。安全
synchronized指令严格遵照java happens-before规则,一个monitor exit指令以前必需要由一个monitor enter。并发
syncronized关键字提供了一种互斥机制,也就是说在统一时刻,只能有一个线程访问同步资源,synchronized是某线程取了与mutex关联的monitor锁. 例子:app
public class Demo { private final static Object MUTEX = new Object(); public void accessResource() { synchronized (MUTEX) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final Demo mutex = new Demo(); for (int i = 0; i < 5; i++) { new Thread(mutex::accessResource).start(); } } }
线程状态查看: 方法一:jvm
方法二:ide
使用JDK自带javap工具堆Demo class进行反编译,输出大量的JVM指令,在这些指令中,monitor enter和monitor exit是成对出现,有可能出现一个monitor,多个monitor exit,可是每一个monitor exit以前必须有对应的monitor enter.工具
PS E:\book\allCode\JavaConcurrency\target\classes\chapter_4\com\flexible> javap -c Demo 警告: 二进制文件Demo包含chapter_4.com.flexible.Demo Compiled from "Demo.java" public class chapter_4.com.flexible.Demo { public chapter_4.com.flexible.Demo(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void accessResource(); Code://获取MUTEX 0: getstatic #2 // Field MUTEX:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter//执行monitor enter jvm 6: getstatic #3 // Field java/util/concurrent/TimeUnit.MINUTES:Ljava/util/concurrent/TimeUnit; 9: ldc2_w #4 // long 10l 12: invokevirtual #6 // Method java/util/concurrent/TimeUnit.sleep:(J)V 15: goto 23//跳转到23 18: astore_2 19: aload_2 20: invokevirtual #8 // Method java/lang/InterruptedException.printStackTrace:()V 23: aload_1 24: monitorexit //执行monitor exitjvm指令 25: goto 33 28: astore_3 29: aload_1 30: monitorexit 31: aload_3 32: athrow 33: return Exception table: from to target type 6 15 18 Class java/lang/InterruptedException 6 25 28 any 28 31 28 any public static void main(java.lang.String[]); Code: 0: new #9 // class chapter_4/com/flexible/Demo 3: dup 4: invokespecial #10 // Method "<init>":()V 7: astore_1 8: iconst_0 9: istore_2 10: iload_2 11: iconst_5 12: if_icmpge 42 15: new #11 // class java/lang/Thread 18: dup 19: aload_1 20: dup 21: invokevirtual #12 // Method java/lang/Object.getClass:()Ljava/lang/Class; 24: pop 25: invokedynamic #13, 0 // InvokeDynamic #0:run:(Lchapter_4/com/flexible/Demo;)Ljava/lang/Runnable; 30: invokespecial #14 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 33: invokevirtual #15 // Method java/lang/Thread.start:()V 36: iinc 2, 1 39: goto 10 42: return static {}; Code: 0: new #16 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: putstatic #2 // Field MUTEX:Ljava/lang/Object; 10: return }
1.Monitorenter 每一个对象都与一个monitor相关联,一个monitor的lock的锁只能被一个线程同时得到,在一个线程尝试得到与对象关联的monitor的全部权时会发生以下的事情。flex
若是monitor的计数器为0,则觉得着该monitor的lock尚未被得到,某个线程得到以后将当即计数器加1.今后该线程就是这个monitor的全部者。
若是一个已经永远了该monitor全部权的线程重入,则会致使monitor计数器再次加1
若是monitor已经被其余线程所拥有,则其余的线程尝试获取该monitor的全部权时,会被现如阻塞状态知道monitor计数器变为0,才能再次尝试获取堆monitor的全部权.
2.Monitorexit
释放对monitor的全部权,想要释放对某个对象关联的monitor的全部权的前提时,你增长得到了全部权。释放monitor的全部权的过程比较简单,就是将monitor的计数器减1,若是计数器的结果为0,那么就觉得这个该线程再也不拥有对该monitor的全部权,通俗的讲就是解锁。
1.与monitor关联的对象不能为空(null)
2.synchronized做用域不要太大(好比直接将synchronized方法放在方法)
3.不一样的monitor企图锁相同的方法 public class MyTask implements Runnable { private static final Object MUTEX = new Object(); @Override public void run() { synchronized (MUTEX){ //... System.out.println(Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } }
} public static void main(String[] args) { for (int i=0;i<5;i++){ new Thread(MyTask::new).start(); } } }
4.多锁交叉致使死锁
public class ThisMonitorDemo { public synchronized void method_1() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void method_2() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public void method_3() { synchronized (this) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThisMonitorDemo demo = new ThisMonitorDemo(); Thread t1 = new Thread(demo::method_1, "T1"); Thread t2 = new Thread(demo::method_2, "T2"); Thread t3 = new Thread(demo::method_2, "T3"); t1.start(); t2.start(); t3.start(); } }
执行的结果:
由上可知三个线程都是同一个实例锁
在修饰类方法的时候使用的是类监视器而不是实例监视器,经过下面的例子就能够看出来。
public class ClssMonitorDemo { public static synchronized void method_1() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public static synchronized void method_2() { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } public static void method_3() { synchronized (ClssMonitorDemo.class) { try { TimeUnit.MINUTES.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ThisMonitorDemo demo = new ThisMonitorDemo(); Thread t1 = new Thread(ClssMonitorDemo::method_1, "T1"); Thread t2 = new Thread(ClssMonitorDemo::method_2, "T2"); Thread t3 = new Thread(ClssMonitorDemo::method_2, "T3"); t1.start(); t2.start(); t3.start(); } }
根据下图的画红线的地方有java.lang.Class就能够看出来了。
1.交叉锁可致使程序出现死锁 例如线程1获取道L1锁,等待获取L2锁,线程2得到了L2锁,等待会的L1锁,这种"哲学家们吃面"的场景就会致使死锁。
2.内存不足 在并发请求的系统可能内存时,若是内存时。可能会出现死锁的状况,线程1和线程2,若是线程一执行都各须要10M,而内存只有10M,那么线程1和线程2都在等待对方释放内存资源而致使死锁。
3.一问一答式数据交换 服务段开启某个端口,等待客户访问,客户端发送请求当即等待接收,因为某种缘由服务段错过了客户端的请求,仍然在等待一问以打的式的数据交换,此时,服务端和客户端都在等待双方发送数据。
4.数据库锁 不管数据库表级锁,仍是行级锁,好比某个线程执行for update语句推出了事务,其余线程访问该数据库时都会现如死锁。
5.文件锁 某线程得到文件锁意外退出,其余读取该文件的线程也将会进入死锁,知道系统释放文件句柄资源。
6.死循环引发的死锁。 程序因为代码缘由或者对某个一次处理不当,进入了死循环,虽然看线程堆栈信息不会发现任何死锁的集乡,可是程序不工做了,CPU的使用率居高不下,这种死锁现象通常称为系统假死,这种状况通常排查比较困难。
1.交叉锁引发的死锁 通常使用jstack -l PID 或者使用jconsole图形界面上直接查看.
2.死循环引发的死锁(假死) 通常使用jstack,jconsole,jvisualvm工具或者jProfiler(收费)进行检查,四实查看没法获得很明显的结果,由于线程没有死锁,只是一致runnbale并且CPU的使用率居高不下.