Java并发编程基础(二)

线程安全与数据同步

synchronized关键字

多个线程对index变量(共享变量/资源)同时操做引发的,再JDK1.5之前,要解决这个问题须要使用synchronized关键字,synchronized提供了一种排他的机制,同一个时间只能有一个线程执行某些操做。java

synchronized关键字的解释

synchronized关键字能够实现一个简单的策略来放置线程干扰和内存一致行错误,若是一个对象多个线程是可见的,那么该对象全部读或者些都将经过同步的方式来进行。数据库

  • synchronized关键字提供了一种锁机制,可以确认保存共享变量的互斥访问,从而放置数据不一致问题的出现。缓存

  • synchronized关键字包括monitor enter和monitor exit 两个JVM指令,它可以保证再任什么时候候任何线程执行到monitor enter成功以前都必须从主内存中获取数据,而不是从缓存中,再monitor exit和运行成功以后,共享变量被更新后的值必须刷入主内存。安全

  • synchronized指令严格遵照java happens-before规则,一个monitor exit指令以前必需要由一个monitor enter。并发

synchronized关键字的理解

线程堆栈分析

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

JVM指令分析

使用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的全部权,通俗的讲就是解锁。

使用synchronized须要注意的问题

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.多锁交叉致使死锁

This Monitor和Class Monitor的详细介绍

This monitor

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();
	}
}

执行的结果:

由上可知三个线程都是同一个实例锁

class monitor

在修饰类方法的时候使用的是类监视器而不是实例监视器,经过下面的例子就能够看出来。

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的使用率居高不下.

相关文章
相关标签/搜索