代码中说JUC-2

本系列是写在代码里的记录,用来记录juc的学习过程

具体代码在github里嗷~

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/*
本章是对并发的共享模型的学习
*/
@Slf4j(topic = "n3")
public class n3 {

/*
首先尝试,两个线程同时对一个静态变量进行操作,会不会出问题
*/
static int res = 0;

@Test
public void problem5000() throws InterruptedException {
// 一个线程+,一个线程-,看看数据有没有变化
// 首先定义好两个线程
Thread add = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
res++;
}
});

Thread sub = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
res--;
}
});
add.start();
sub.start();
add.join();
sub.join();

log.debug(String.valueOf(res)); // 可能==0,可能!=0

/*
最后可以看到结果并不是0,由于分时系统,导致可能并没有写进数据,而时间片用完被收回
为什么没有写进去呢?

查看字节码(--)可以知道,静态变量的处理是四个操作指令。 ++类似
getstatic #29 <ln/n3/n3.res : I> 把常量池索引为29的静态字段的值放到操作栈上
>> 0
iconst_1 把1推到操作栈上
>> 0->1
isub 执行减操作
>> -1
putstatic #29 <ln/n3/n3.res : I> 把操作栈的数放到索引为29的静态字段上
>>

上面是正常情况下,单线程的读写操作,那多线程呢?
getstatic
>> 0
iconst_1
>> 0->1
isub
>> -1
|getstatic
>> 0
iconst_1
>> 0->1
iadd
>> 1
putstatic
>> 写入静态变量1
putstatic
>> 写入静态变量-1
这时原本好端端的0,就被迟来的指令改变了,成为了-1
*/
}

/*
可以使用互斥锁,将对象锁住进行操作
*/
static Object lock = new Object();
static Integer i = 0;

@Test
public void testSys() throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock) { //保证i++线程走完
for (int i1 = 0; i1 < 500; i1++) {
i++;
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) { // 保证循环内所有代码走完
for (int i1 = 0; i1 < 500; i1++) {
i--;
}
}
});

thread1.start();
thread2.start();

thread1.join();
thread2.join();

log.debug("结果为:{}", i);

/*
类似分析
线程1 |线程2 |锁对象
------------------------------------->获取锁
getstati
>> 0
iconst_1
>> 0->1
isub
>> -1
时间片切换 |
-------------------->获取锁,获取失败,进入block阻塞状态,切换时间片
putstatic
>>写入静态变量-1
------------------------------------->释放锁,并唤醒所有阻塞状态线程
-------------------->获取锁
getstati
>> -1
iconst_1
>> -1->1
isub
>> 0
putstatic
>>写入静态变量0
-------------------->释放锁,并唤醒所有阻塞状态线程
*/

}

/*
改进上面的方式,使用对象的方式进行保护
*/
private static class Room {
public int val = 0;

public int getVal() {
return val;
}

// 可以加在方法上面,相当于锁住了方法
public synchronized void setVal(int val) {
this.val = val;
}

public void addVal() {
synchronized (this) {
this.val++;
}
}

public void subVal() {
synchronized (this) {
this.val--;
}
}
}

@Test
public void testSysObject() throws InterruptedException {
// 只需要调用方法即可,对象自身方法实现了对象锁
Room room = new Room();
Thread thread = new Thread(() -> {
for (int i1 = 0; i1 < 500; i1++) {
room.addVal();
}
});
Thread thread1 = new Thread(() -> {
for (int i1 = 0; i1 < 500; i1++) {
room.subVal();
}
});
thread.start();
thread1.start();

thread.join();
thread1.join();

log.debug("结果为:{}", room.getVal());
}

/*
分析线程不安全的情况:
1. 成员变量和静态变量分析,静态变量在方法区中,成员变量在堆(对象里)中
它们如果是非共享的,没有安全问题
如果是共享的,如果有临界区,则有安全问题
2. 局部变量是否线程安全,局部变量在方法里,是线程安全的,但是局部变量引用的对象,安全问题
*/

@Test
public void threadUnsafe() {
/*
让多线程共享变量操作出现问题,因为list为共享变量,同时add实际会调用容量++,但容量++前面分析了,
在写入的时候,时间片用完,无法写入,后续再进行写入,数据不齐,导致临界区发送竞态条件
*/
// 定义一个内部类
class tUnsafe {
ArrayList<Integer> list = new ArrayList<>();

public void add() {
this.list.add(1);
}

public void remove() {
this.list.remove(0);
}
}

tUnsafe tu = new tUnsafe();
// 不断操作tUnsafe对象
for (int i1 = 0; i1 < 1000; i1++) {
new Thread(() -> {
for (; true; ) {
// 临界区
// 发生了竞态条件
tu.add();
tu.remove();
}
}, String.valueOf(i1)).start();
}
}

@Test
public void threadSafe() throws InterruptedException {
/*
在这里,list为局部变量,仅在一个方法中,不同方法的list不同,不干扰
*/
// 定义一个内部类
class tSafe {
public void m1() {
ArrayList<Integer> list = new ArrayList<>();
add(list);
remove(list);
}

public void add(ArrayList<Integer> list) {
list.add(1);
}

public void remove(ArrayList<Integer> list) {
list.remove(0);
}
}

tSafe ts = new tSafe();

// 由于线程正常,则定个计时器
Thread thread = new Thread(() -> {
while (true) {
}
});
thread.start();
// 不断操作tSafe对象
for (int i1 = 0; i1 < 1000; i1++) {
new Thread(() -> {
for (; true; ) {
ts.m1();
}
}, String.valueOf(i1)).start();
}
thread.join(100);
}

// 局部变量如果被暴露在外面,则还是会出现临界区
@Test
public void threadExpose() throws InterruptedException {
class t {
public void m1() {
// 可以看到,即使只操作一遍,但是由于多个线程共同操作共享变量,也会出现竞态条件
ArrayList<Integer> list = new ArrayList<>();
System.out.println(System.identityHashCode(list));
add(list);
remove(list);
}

public void add(ArrayList<Integer> list) {
list.add(1);
}

public void remove(ArrayList<Integer> list) {
list.remove(0);
}
}

// 这里继承重写方法,相当于多开了线程
class t1 extends t {
@Override
public void add(ArrayList<Integer> list) {
new Thread(() -> {
list.add(1);
}).start();
}

@Override
public void remove(ArrayList<Integer> list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}

t1 t1 = new t1();

Thread thread = new Thread(() -> {
for (; true; ) {
t1.m1();
}
});

thread.start();
thread.join();
}

/*
String 和 Integer为不可变类,保证线程安全,因为它不可变
*/
@Test
public void testNo() {
String a = "12345";
System.out.println(System.identityHashCode(a));
a = "2";
System.out.println(System.identityHashCode(a));
System.out.println(System.identityHashCode("2"));

Integer b = 129;
System.out.println(System.identityHashCode(b));
b = 130;
System.out.println(System.identityHashCode(b));
System.out.println(System.identityHashCode(130));

// 顺便说明在java中Integer,若取值在-127~127之间,则对象相同,
// 因为:
for (int i1 = 0; i1 < 300; i1++) {
Integer c = i1;
if (System.identityHashCode(c) != System.identityHashCode(i1)) {
System.out.println(i1);
break;
}
}
}

/*
探索轻量级锁
先加轻量级锁,在java虚拟机栈里存有栈帧lockRecord
*/
@Test
public void lightLock() {
final Object o = new Object();
// 首先Object对象头MonitorWord和主线程的lockRecord的MonitorWord相似的部分交换,
// 使得还没上锁的o,状态从01变成了monitorWord的00,同时记录相应的lockRecord信息
synchronized (o) {
// 查看如果是在本线程,就是只增加一个栈帧lockRecord,然后记录信息为null
synchronized (o) {
log.debug("hi");
}
}

// 这是简单且理想状态下:
/*
1.有线程已经拥有对象,则进入锁膨胀阶段
锁膨胀流程:
首先把原先的00,换成10,前面那两位(XX 10 MonitorWord4个字节)指向一个monitor对象
type monitor {
Thread owner;
Thread[] waitSet;
Thread[] entitySet;
}
entitySet把要申请锁的线程添加,并变成阻塞状态。

在解锁的时候,原先线程先尝试轻量级解锁方式,就是简单的交换monitorWord,如果行不通,则变成重量级锁解锁方式
2.自己线程已经拥有对象,则空加lockRecord
*/
}

// 探索偏向锁 Biased
// 在这里用了新的jar包,jol
// 记得vm参数有开启偏向锁
@Test
public void biasedLock() {
// 在这里先随便创建一个对象
ArrayList<Integer> o = new ArrayList<>();

// 先打印一下,看到
// 0 8 (object header: mark) 0x0000000000000005 (biasable; age: 0)
// 说明上锁
log.debug(ClassLayout.parseInstance(o).toPrintable());

// 使用锁
synchronized (o) {
// 打印
// 说明上锁,并标记线程为···
// 例如:0 8 (object header: mark) 0x000002d4e42bc005 (biased: 0x00000000b5390af0; epoch: 0; age: 0)
log.debug(ClassLayout.parseInstance(o).toPrintable());
}

// 线程直接使用锁
log.debug(ClassLayout.parseInstance(o).toPrintable());

// 换个线程使用
// 切换轻量级锁
Thread thread = new Thread(() -> {
synchronized (o) {
log.debug(ClassLayout.parseInstance(o).toPrintable());
}
});

thread.start();

// 最后检查,发现最后回到01,无锁
try {
thread.join();
log.debug(ClassLayout.parseInstance(o).toPrintable());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

// 小贴士:用hashCode会禁用偏向锁
@Test
public void hashCodeMagic() {
Object o = new Object();
o.hashCode();

// 例如:
// 0 8 (object header: mark) 0x000000146ba0ac01 (hash: 0x146ba0ac; age: 0)
log.debug(ClassLayout.parseInstance(o).toPrintable());
// 因为0 01状态(无锁)下,存储31位hashcode,
// 但是在1 01状态(偏向锁)下,不能存储hashcode,所以在调用hashCode后,不会给hashCode

// 那为什么不是偏向锁,轻量级锁和重量级锁没有hashcode,却能查询呢?
// 因为追起溯源,其实是markWord是交换过来的信息,也就是说线程存储之前交换的信息,可以用它找到hashCode
// 准确来说,轻量级锁存在线程栈帧里
// 重量级锁存在monitor对象里
// 而用偏向锁的话,直接是覆盖掉了,不是交换过去还存在
synchronized (o) {
o.hashCode();
log.debug(ClassLayout.parseInstance(o).toPrintable());
}
}

/*
偏向锁撤销:
1.使用hashCode
2.使用wait notify (只有重量级锁才有)
3.使用过一次其他的锁
4.批量撤销了,在之后类新建的对象也不会上偏向锁了
*/
@Test
public void testBatch() {
class Dog {
}

LinkedList<Dog> dogs = new LinkedList<>();

// 首先测试批量重偏向
Thread thread1 = new Thread(() -> {
for (int i1 = 0; i1 < 50; i1++) {
Dog dog = new Dog();
synchronized (dog) {
dogs.add(dog);
}
}

synchronized (dogs) {
dogs.notify();
}
}, "t0");

Thread thread2 = new Thread(() -> {
// 等待和打印
synchronized (dogs) {
try {
dogs.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug(ClassLayout.parseInstance(dogs.get(0)).toPrintable()); // 看第一个和最后一个是否上偏向锁d
log.debug(ClassLayout.parseInstance(dogs.get(dogs.size() - 1)).toPrintable());

for (Dog dog : dogs) {
synchronized (dog) {
}
}

log.debug(ClassLayout.parseInstance(dogs.get(0)).toPrintable()); // 看第一个和最后一个是否上偏向锁d
log.debug(ClassLayout.parseInstance(dogs.get(dogs.size() - 1)).toPrintable());
}, "t1");

thread1.start();
thread2.start();

try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

// 因为之前是达到20次,就重偏向2了,所以需要再来20次
for (Dog dog : dogs) {
synchronized (dog) {
}
}
log.debug(ClassLayout.parseInstance(dogs.get(0)).toPrintable()); // 看第一个和最后一个是否上偏向锁d
log.debug(ClassLayout.parseInstance(dogs.get(dogs.size() - 1)).toPrintable());

// 达到40次后,再无重偏向了
log.debug(ClassLayout.parseInstance(new Dog()).toPrintable());
}

}