多线程的资源同步和线程安全以及锁

数据同步

首先为什么要同步?

普通的程序在串行化任务执行的过程中不存在线程安全、资源共享的情况,但是效率低下资源无法得到充分应用;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();
}
}

多次显示

1571065968768

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

被略过

1571067068853

这个和上个问题有些相似,线程1 2都得到了index=8的位置 线程1执行了9+1 ,还未执行输出,执行权到了线程2 线程2执行10+1了。 那么线程1输出11

超过最大值

多线程的资源同步和线程安全、锁1571067931007

这个就是线程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;//不再使用static修饰
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的处理时间

1571576057801

上面是synchronized3秒228毫秒

下面是之前的705毫秒。这说明了synchronized3关键字是牺牲了效率来达到资源同步和线程安全的。之后会写一篇深入synchronized3关键字的文章,这里只分析显而易见的特性;

这个应用到现实生活中就像安检,每次只能通过一个,通过牺牲检查并发通过量来达到违禁物品的绝对杜绝。

使用synchronized的注意事项

  1. 与monitor关联的对象不能为null,无法关联;

    1
    2
    3
    4
    5
    6
    7
    8
    private finall Object mutex=null;
    public void syscMethod()
    {
    sysnchronized(mutex)
    {
    .......
    }
    }
  2. synchronized作用域太大;

    sysnchroized关键字存在排他性,线程必须串行的经过sysnchroized修饰的区域,如果作用域过大效率就会被降低,失去并发的优势

  3. 不同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无效;

  4. 锁交叉引发死锁

    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启动上面的例子

1571578694492

可以发现只有一个在休眠状态,其他的都处于监视状态也就是BLOCKED被阻塞状态;

使用jstack命令验证此结果.

1571579150818

可以看到只有线程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 #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field index:I
9: return

public void run();
Code:
0: ldc #3 // class Runningablesharedresource
2: dup
3: astore_1
4: monitorenter
5: aload_0
6: getfield #2 // Field index:I
9: sipush 500
12: if_icmpgt 83
15: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
18: new #5 // class java/lang/StringBuilder
21: dup
22: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
25: ldc #7 // String 人事
27: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: invokestatic #9 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
33: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
36: ldc #11 // String 招聘到了 第
38: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
41: aload_0
42: dup
43: getfield #2 // Field index:I
46: dup_x1
47: iconst_1
48: iadd
49: putfield #2 // Field index:I
52: invokevirtual #12 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
55: ldc #13 // String 个java开发
57: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
60: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: ldc2_w #16 // long 100l
69: invokestatic #18 // Method java/lang/Thread.sleep:(J)V
72: goto 5
75: astore_2
76: aload_2
77: invokevirtual #20 // Method java/lang/InterruptedException.printStackTrace:()V
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 // class Runningablesharedresource
3: dup
4: invokespecial #21 // Method "<init>":()V
7: astore_1
8: new #22 // class java/lang/Thread
11: dup
12: aload_1
13: ldc #23 // String 001
15: invokespecial #24 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;Ljava/lang/String;)V
18: astore_2
19: new #22 // class java/lang/Thread
22: dup
23: aload_1
24: ldc #25 // String 002
26: invokespecial #24 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;Ljava/lang/String;)V
29: astore_3
30: new #22 // class java/lang/Thread
33: dup
34: aload_1
35: ldc #26 // String 003
37: invokespecial #24 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;Ljava/lang/String;)V
40: astore 4
42: new #22 // class java/lang/Thread
45: dup
46: aload_1
47: ldc #27 // String 004
49: invokespecial #24 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;Ljava/lang/String;)V
52: astore 5
54: aload_2
55: invokevirtual #28 // Method java/lang/Thread.start:()V
58: aload_3
59: invokevirtual #28 // Method java/lang/Thread.start:()V
62: aload 4
64: invokevirtual #28 // Method java/lang/Thread.start:()V
67: aload 5
69: invokevirtual #28 // Method java/lang/Thread.start:()V
72: return

static {};
Code:
0: new #29 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #30 // Field MUTE:Ljava/lang/Object;
10: return

查找monitorenter和monitorexit,发现这俩很多且都是成对的。

monitorenter

参考书说的很nice

java每个对象都会与一个monitore相关联,一个monitore的lock锁只能被一个线程在同时间获得;当有一个线程尝试或者此对象关联的monitor的所有权会发生:

  1. 如果monitor的计数器为0,那么就表示这个monitor的lock还没有被获得,某线程获得后,计数器立即+1,此线程就是这个monitor的所有者了.
  2. 如果此monitor计数器不为0,已经被某线程获得了,那么其他线程尝试获得此monitore时,会进入BLOCKED被阻塞状态,直到此monitor计数器为0,才能再次尝试获得此monitor的所有权。
  3. 如果一个已经拥有此monitor的线程再次进入 ,会导致monitor计数器再次累加

monitorexit

这个通俗来说就是解锁,但是要想解锁肯定要现有monitorenter加锁,过程很简单monitor-1,此线程不在拥有monitor的所有权,同时被该monitor block的线程将再次尝试获得该monitor的所有权.


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!