Java并发编程基础(二)

线程安全与数据同步

synchronized关键字

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

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锁. 例子:

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指令分析

使用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的所有权时会发生如下的事情。

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

转载于:https://my.oschina.net/u/3474937/blog/3002062

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值