数据同步
首先为什么要同步?
普通的程序在串行化任务执行的过程中不存在线程安全、资源共享的情况,但是效率低下资源无法得到充分应用;A餐厅等他吃完了,b才能进来吃.
资源共享就是多个线程同时对同一个资源进行访问。资源同步就是多个线程访问到的数据都是相同的。
引入之前的例子
使用之前用静态关键字进行解决的例子 多次运行对比结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Threadsharedresource extends Thread{ private final String number; private static final int MAX=50; private static int index=1; public void run(){ while(index<=MAX) { System.out.println("人事"+number+"招聘到了 第"+(index++)+"个java开发"); } } public Threadsharedresource(String number) { this.number=number; } public static void main(String[] args) { Threadsharedresource tsd1=new Threadsharedresource("001"); tsd1.start(); Threadsharedresource tsd2=new Threadsharedresource("002"); tsd2.start(); Threadsharedresource tsd3=new Threadsharedresource("003"); tsd3.start(); } }
|
多次显示

线程1执行498+1还没执行完成,执行权到了线程2的手里也执行498+1.所以就出现了俩499
被略过

这个和上个问题有些相似,线程1 2都得到了index=8的位置 线程1执行了9+1 ,还未执行输出,执行权到了线程2 线程2执行10+1了。 那么线程1输出11
超过最大值
多线程的资源同步和线程安全、锁
这个就是线程1 线程2 线程3 执行到499 3个线程都进入了while里面
1 2 3 4 5 6 7 8 9 10
| while(index<=MAX) { System.out.println("人事"+Thread.currentThread()+"招聘到了 第"+(index++)+"个java开发"); try { Thread.sleep(100); }catch (InterruptedException e) { e.printStackTrace(); } }
|
此时其中某个线程获得了执行权+1 其他的渐渐的也获得了权力 +1 +1 导致最后结果为502
引入syschronized解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class Runningablesharedresource implements Runnable{
private final static int MAX=500; private int index=1; private final static Object MUTE=new Object(); public void run(){ synchronized (Runningablesharedresource.class) { while (index <= MAX) { System.out.println("人事" + Thread.currentThread() + "招聘到了 第" + (index++) + "个java开发"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public static void main(String[] args) { final Runningablesharedresource runningablesharedresource=new Runningablesharedresource(); Thread thread1=new Thread(runningablesharedresource,"001"); Thread thread2=new Thread(runningablesharedresource,"002"); Thread thread3=new Thread(runningablesharedresource,"003"); Thread thread4=new Thread(runningablesharedresource,"004"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); }
}
|
执行查看结果不在出现之前的情况.
注意一个点,对比syschronized关键字和之前runningable+static的处理时间

上面是synchronized3秒228毫秒
下面是之前的705毫秒。这说明了synchronized3关键字是牺牲了效率来达到资源同步和线程安全的。之后会写一篇深入synchronized3关键字的文章,这里只分析显而易见的特性;
这个应用到现实生活中就像安检,每次只能通过一个,通过牺牲检查并发通过量来达到违禁物品的绝对杜绝。
使用synchronized的注意事项
与monitor关联的对象不能为null,无法关联;
1 2 3 4 5 6 7 8
| private finall Object mutex=null; public void syscMethod() { sysnchronized(mutex) { ....... } }
|
synchronized作用域太大;
sysnchroized关键字存在排他性,线程必须串行的经过sysnchroized修饰的区域,如果作用域过大效率就会被降低,失去并发的优势
不同monitor企图锁相同方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Runningablesharederror { public static class Task implements Runnable{ private final static Object MUTE=new Object(); public void run(){ synchronized (MUTE) {
} } } public static void main(String[] args) { for (int i=0;i<5;i++) { new Thread(Task::new).start(); } }
}
|
此代码构造的5个线程争夺的是5个不同的monitor,sysnchroized无效;
锁交叉引发死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Runningablesharederror2 { private final Object MUTEX_READ = new Object(); private final Object MUTEX_WARITE = new Object(); public void read(){ synchronized(MUTEX_READ){ synchronized(MUTEX_WARITE){
} } } public void write(){ synchronized(MUTEX_WARITE){ synchronized(MUTEX_READ){
} } }
}
|
这个是个大坑,因为是不会报错的;
线程a获得了read等待获取write,线程b获得了write等待read
锁
第一点syschronized(mutex)不是锁;
互斥机制,同一时间内只能有一个线程取访问同步资源;
synchronized准确来说是某线程获取了与mutex关联的monitor锁;
idea右键以visualVm启动上面的例子

可以发现只有一个在休眠状态,其他的都处于监视状态也就是BLOCKED被阻塞状态;
使用jstack命令验证此结果.

可以看到只有线程001处于sleep状态,其他都处于BLOCKED被阻塞的状态;
使用javap反编译.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| E:\EnvironmentalScience\jdk1.8\bin>javap -c E:\tool\ideaIU-2018.2.5\project\Thraad\src\Runningablesharedresource.class Compiled from "Runningablesharedresource.java" public class Runningablesharedresource implements java.lang.Runnable { public Runningablesharedresource(); Code: 0: aload_0 1: invokespecial 4: aload_0 5: iconst_1 6: putfield 9: return
public void run(); Code: 0: ldc 2: dup 3: astore_1 4: monitorenter 5: aload_0 6: getfield 9: sipush 500 12: if_icmpgt 83 15: getstatic 18: new 21: dup 22: invokespecial 25: ldc 27: invokevirtual 30: invokestatic 33: invokevirtual 36: ldc 38: invokevirtual 41: aload_0 42: dup 43: getfield 46: dup_x1 47: iconst_1 48: iadd 49: putfield 52: invokevirtual 55: ldc 57: invokevirtual 60: invokevirtual 63: invokevirtual 66: ldc2_w 69: invokestatic 72: goto 5 75: astore_2 76: aload_2 77: invokevirtual 80: goto 5 83: aload_1 84: monitorexit 85: goto 93 88: astore_3 89: aload_1 90: monitorexit 91: aload_3 92: athrow 93: return Exception table: from to target type 66 72 75 Class java/lang/InterruptedException 5 85 88 any 88 91 88 any
public static void main(java.lang.String[]); Code: 0: new 3: dup 4: invokespecial 7: astore_1 8: new 11: dup 12: aload_1 13: ldc 15: invokespecial 18: astore_2 19: new 22: dup 23: aload_1 24: ldc 26: invokespecial 29: astore_3 30: new 33: dup 34: aload_1 35: ldc 37: invokespecial 40: astore 4 42: new 45: dup 46: aload_1 47: ldc 49: invokespecial 52: astore 5 54: aload_2 55: invokevirtual 58: aload_3 59: invokevirtual 62: aload 4 64: invokevirtual 67: aload 5 69: invokevirtual 72: return
static {}; Code: 0: new 3: dup 4: invokespecial 7: putstatic 10: return
|
查找monitorenter和monitorexit,发现这俩很多且都是成对的。
monitorenter
参考书说的很nice
java每个对象都会与一个monitore相关联,一个monitore的lock锁只能被一个线程在同时间获得;当有一个线程尝试或者此对象关联的monitor的所有权会发生:
- 如果monitor的计数器为0,那么就表示这个monitor的lock还没有被获得,某线程获得后,计数器立即+1,此线程就是这个monitor的所有者了.
- 如果此monitor计数器不为0,已经被某线程获得了,那么其他线程尝试获得此monitore时,会进入BLOCKED被阻塞状态,直到此monitor计数器为0,才能再次尝试获得此monitor的所有权。
- 如果一个已经拥有此monitor的线程再次进入 ,会导致monitor计数器再次累加
monitorexit
这个通俗来说就是解锁,但是要想解锁肯定要现有monitorenter加锁,过程很简单monitor-1,此线程不在拥有monitor的所有权,同时被该monitor block的线程将再次尝试获得该monitor的所有权.