-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
3539 lines (3139 loc) · 355 KB
/
search.xml
File metadata and controls
3539 lines (3139 loc) · 355 KB
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
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>JAVA Map&&Stream流</title>
<url>/2021/10/13/Article10/</url>
<content><![CDATA[<p>Map 【重点】<br> 什么是Map<br> Map<K,V><br> 将键映射到值的对象。 Map不能包含重复的键; 每个键可以映射最多到一个值。<br> Map是一个双列集合,存储的每一个元素都是由两列组成,<br> 第一列我们称之为键,第二列称之为值,把这种由键和值组成的每一个元素称之为键值对。</p>
<pre><code> <K,V>是泛型,K变量用于规定键存储的数据类型,V变量用于规定值存储的数据类型
键和值的数据类型可以相同也可以不同。
K:Key
V:Value
Map的特点
1、Map不能包含重复的键
如果键重复,则值覆盖
2、Map的值可以重复
3、Map的键和值是一一对应的
通过一个键只能找到唯一对应的值
Map的体系结构
Map
|-HashMap
|-TreeMap
Map的常用方法
基本方法
V put(K key, V value)
void clear()
V remove(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()
int size()
Collection<V> values()
遍历方法
Set<K> keySet()
V get(Object key)
Set<Map.Entry<K,V>> entrySet()
default void forEach(BiConsumer<? super K,? super V> action)
</code></pre><p>可变参数 【重点】<br> 什么是可变参数<br> 参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了<br> JDK5.0的新特性(自动拆装箱、增强for、泛型和可变参数)</p>
<pre><code> 方法的参数类型已经确定,个数不确定,我们可以使用可变参数
可变参数定义格式
修饰符 返回值类型 方法名(数据类型... 变量名) {
方法体;
}
可变参数的注意事项
1、可变参数本质上是一个数组,但是它的使用位置是有局限的,只能用在方法的形参处(定义方法时的小括号中)
2、可变参数表示参数个数可变,我们传递实参的时候可以传递
将多个参数存放到数组中,将数组进行传递
0~无穷多个参数
3、如果一个方法有多个参数,包含可变参数,可变参数要放在最后
一个方法的可变参数只能定义一个
</code></pre><p>Stream流 【重点】<br> 什么是Stream流<br> Stream流用于简化集合和数组操作的代码<br> Stream流类似于生产流水线,下一步的操作都是基于上一步的,并且流水线只能一直往下一步操作,不能回头。</p>
<p> Stream流的操作<br> 1、获取Stream流(将源数据->Stream流)<br> 2、操作Stream流<br> 中间方法<br> 终结方法<br> 3、收集Stream流(Stream流->源数据)</p>
<p> 获取Stream流(将源数据->Stream流)<br> 集合<br> 单列集合<br> 集合对象.stream();</p>
<pre><code> 双列集合
不能直接获取,需要间接获取(先将双列集合转为单列集合,然后再获取)
集合对象.keySet().stream();
集合对象.entrySet().stream()
集合对象.values().stream()
数组
Arrays.stream(数组名);
Stream.of(数据1,数据2,数据3......);
</code></pre><p> 操作Stream流<br> 中间方法:<br> Stream<T> filter(Predicate<? super T> predicate);<br> Stream<T> skip(long n);<br> Stream<T> limit(long n);<br> public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)<br> Stream<T> distinct();</p>
<pre><code> 如果一个方法返回值还是Stream,那么这个方法就是中间方法
流水线的中间步骤,操作完之后还可以继续操作流
终结方法
void forEach(Consumer<? super T> action);
long count();
如果一个方法返回值不是Stream,那么这个方法就是终结方法
</code></pre><p> 收集Stream流(Stream流->源数据)<br> 集合<br> 将Stream流中的数据收集到集合,不论是单列集合,还是双列集合使用的使用一下这个方法(Stream的方法):<br> <R, A> R collect(Collector<? super T, A, R> collector);<br> 到底收集到什么集合中,和你往方法中传递的值有关:<br> stream.collect(Collectors.toList()):收集到List集合中<br> stream.collect(Collectors.toSet()):收集到Set集合中<br> stream.collect(Collectors.toMap(…)):收集到Map集合中</p>
<pre><code> 单列集合
List
stream.collect(Collectors.toList())
Set
stream.collect(Collectors.toSet())
双列集合
stream.collect(Collectors.toMap(...)):收集到Map集合中
数组
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
</code></pre>]]></content>
</entry>
<entry>
<title>JAVA File类&& IO流-字节流</title>
<url>/2021/10/13/Article11/</url>
<content><![CDATA[<p>File类 【掌握】<br> 什么是File类<br> File类表示文件和目录(文件夹),在表示的时候不能只告诉程序文件或者目录的名称,<br> 应该要告诉程序文件或者目录的路径</p>
<pre><code>构造方法
告诉程序你要操作的文件/文件夹在哪里
File(String pathname)
File(String parent, String child)
File(File parent, String child)
* 构造方法中路径的特点:
1、可以是存在的路径也可以是不存在的
2、可以是文件路径也可以是文件夹路径
3、可以是相对路径也可以是绝对路径
路径的问题
绝对路径:以盘符开始的路径(完整的路径)
相对路径:不以盘符开始的路径(简化/省略路径)
当前路径相对的是项目根路径,以项目根路径为参照物
不是所有的绝对路径都可以省略写成相对路径的,只有这个
绝对路径中包含了项目根目录才可以写成相对路径
项目根路径:就是你项目存放的路径+项目名
D:\develop\JetBrains\IdeaProjects2020\javase2\a.txt -- 绝对路径
a.txt -- 相对路径
D:\develop\JetBrains\IdeaProjects2020\javase2\day11\dir\a.txt -- 绝对路径
day11\dir\a.txt -- 相对路径
E:\集合.xmind -- 绝对路径,该路径不能省略为相对路径
成员方法
对这个关联的文件/文件夹进行创建、删除、获取等操作
创建
文件
boolean createNewFile()
文件夹
boolean mkdir()
boolean mkdirs() :既可以创建单级也可以创建多级文件夹
删除
boolean delete()
既可以删除文件也可以删除文件夹,但是删除文件夹的时候只能删除空文件夹
删除不走回收站
判断和获取
public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
public boolean isFile() 测试此抽象路径名表示的File是否为文件
* 以上这个两个方法是互斥的,不是文件就是文件夹,不是文件夹就是文件
前提:要求文件或者文件夹是存在的,如果不存在,那么这两个方法都会返回false
boolean exists() 测试此抽象路径名表示的File是否存在
String getName() 返回由此抽象路径名表示的文件或目录的名称
long length():可以获取文件的大小(字节数),不能获取到文件夹的大小
long lastModified() :获取到文件的最后修改时间的毫秒值
String getAbsolutePath() :获取绝对路径
File[] listFiles()
</code></pre><p>IO流-字节流 【掌握】<br> 什么是IO流<br> 输入输出流,用于处理设备(内存/硬盘[文件])之间的数据传输</p>
<pre><code> 输入还是输出是以内存为参照物的,从外界到内存是输入,输入进行的读取操作
从内存到外界是输出,输出进行的写出的操作
IO流的分类
按流向分
输入流和输出流
按操作的数据类型分
字节流和字符流
非纯文本文件只能使用字节流进行操作
纯文本文件既可以使用字节流也可以使用字符流
IO流的作用
1、文件复制
2、文件上传
3、文件下载
IO流的注意事项
1、IO流核心的每行代码都有异常,而且是编译时异常
2、IO流相关的类,都是位于java.io包下
3、IO流会占用系统资源,操作完之后不要忘记释放资源(关流)
字节流
字节流的体系结构
字节输入流
InputStream(抽象类)
|-FileInputStream:基本流
|-FilterInputStream
|-BufferedInputStream:缓冲流
字节输出流
OutputStream(抽象类)
|-FileOutputStream:基本流
|-FilterOutputStream
|-BufferedOutputStream:缓冲流
字节流的基本流
FileOutputStream
构造方法
FileOutputStream(String name)
FileOutputStream(String name, boolean append)
FileOutputStream(File file)
FileOutputStream(File file, boolean append)
成员方法
void write(int b):写出字节数据 int->byte
void write(byte[] b)
* write("\r\n".getBytes()) 换行
void write(byte[] b, int off, int len)
void close()
//1.创建字节输出流的对象
//注意点:1、如果文件不存在,会帮我们自动创建出来,但是要求文件路径要存在
// 2、如果文件存在,会把文件清空,再写出到文件
3、可以写相对路径,也可以写绝对路径
4、关联的路径永远只能是文件路径,不能是文件夹路径
FileOutputStream fos = new FileOutputStream("C:\\itheima\\a.txt");
//2,写数据
// 传递一个整数时,那么实际上写到文件中的,是这个整数在码表中对应的那个字符.
fos.write(98);// 98(十进制)->00000000 00000000 00000000 01100010(int、二进制)
// ->01100010(byte、二进制) ->写入到文件中,但是文本编辑器是一个编解码
// 编解码
// 编码:将我们人类能看懂的文字(字符)转为计算机看得懂的(二进制)
// 解码:将计算机看得懂的(二进制)转为我们人类能看懂的文字(字符)
//3,释放资源
fos.close(); //告诉操作系统,我现在已经不要再用这个文件了
FileInputStream
构造方法
FileInputStream(String name)
FileInputStream(File file)
成员方法
int read():一次读取一个字节,并返回读取到的字节,如果读取到了文件末尾,返回-1
int read(byte[] b):一次读取多个字节,并返回读取到数组中的有效字节个数,如果读取到了文件末尾,返回-1
void close()
//如果文件存在,那么就不会报错.
//如果文件不存在,那么就直接报错.
FileInputStream fis = new FileInputStream("bytestream\\a.txt");
int read = fis.read();
//一次读取一个字节,返回值就是本次读到的那个字节数据.
//也就是字符在码表中对应的那个数字.
//如果我们想要看到的是字符数据,那么一定要强转成char
System.out.println((char)read);
//释放资源
fis.close();
</code></pre>]]></content>
</entry>
<entry>
<title>JAVA 字符流&&Properties集合</title>
<url>/2021/10/13/Article12/</url>
<content><![CDATA[<p>字符流 【重点】<br> 字节流的体系结构<br> 字符输入流<br> Reader<br> |-InputStreamReader 转换流<br> |-FileReader 基本流<br> |-BufferedReader 缓冲流</p>
<pre><code> 字符输出流
Writer
|-OutputStreamWriter 转换流
|-FileWriter 基本流
|-BufferedWriter 缓冲流
编码表
什么是编码表
是一张现实中的文字符号等(字符)与计算机能识别的二进制(字节)之间对应表
现实中的 计算机能识别的
a 01100001
我 11100001 10001010
! 01001000
什么编解码
编码:字符转换为字节 a->01100001
解码:字节转换为字符 01100001->a
编解码操作一定要查询编码表
编解码操作使用的编码表必须是同一张,否则会出现乱码问题
常见的编码表
ASCII:最早的编码表,美国人和英国人的文字符号等的对应关系,不包含中文
GBK:中文码表,一个中文占2个字节,英文、数字和符号占1个字节
UTF-8:包含中文,一个中文占3个字节,英文、数字和符号占1个字节
ISO-8859-1:西欧/拉丁编码,不包含中文
"我爱java"(字符串) ->'我','爱','j','a','v','a'(字符)
'我'(1个字符) -> {-19, -29, -89}(3个字节)
字符是由字节组成的,有些是一个字节组成,有些是多个字节组成
基本流
FileReader
构造方法
FileReader(String name)
FileReader(File file)
成员方法
int read():一次读取一个字符,并返回读取到的字符,如果读取到了文件末尾,返回-1
int read(char[] chs):一次读取多个字符,并返回读取到数组中的有效字符个数,如果读取到了文件末尾,返回-1
void close()
//如果文件存在,那么就不会报错.
//如果文件不存在,那么就直接报错.
FileReader fr = new FileReader("bytestream\\a.txt");
int read = fr.read();
//一次读取一个字符,返回值就是本次读到的那个字符数据.
//也就是字符在码表中对应的那个数字.
//如果我们想要看到的是字符数据,那么一定要强转成char
System.out.println((char)read);
//释放资源
fr.close();
FileWriter
构造方法
FileWriter(String name)
FileWriter(String name, boolean append)
FileWriter(File file)
FileWriter(File file, boolean append)
成员方法
void write(int b):写出字符数据 int->char
void write(char[] b)
void write(char[] b, int off, int len)
void write(String s)
* write("\r\n") 换行
void write(String s, int off, int len)
void close()
//1.创建字符输出流的对象
//注意点:1、如果文件不存在,会帮我们自动创建出来,但是要求文件路径要存在
// 2、如果文件存在,会把文件清空,再写出到文件
3、可以写相对路径,也可以写绝对路径
4、关联的路径永远只能是文件路径,不能是文件夹路径
FileWriter fw = new FileWriter("C:\\itheima\\a.txt");
//2,写数据
// 传递一个整数时,那么实际上写到文件中的,是这个整数在码表中对应的那个字符.
fw.write(98);// 98(十进制)->00000000 00000000 00000000 01100010(int、二进制)
// -> 00000000 01100010(char、二进制) ->写入到文件中,但是文本编辑器是一个编解码
//3,释放资源
fw.close(); //告诉操作系统,我现在已经不要再用这个文件了
flush和close方法的区别
close方法
1、是先刷新,在关闭流
2、close关闭流转换就不能继续使用流
flush方法
1、只有刷新功能
2、可以多次调用,可以继续使用流
缓冲流
底层提供了缓冲区(数组),提高读写的效率
BufferedWriter
构造方法:不能直接关联写出的文件
BufferedWriter(Writer out)
写和释放资源的操作与字符基本流一模一样
BufferedReader
构造方法:不能直接关联读取的文件
BufferedReader(Reader in)
读和释放资源的操作与字符基本流一模一样
特有功能
BufferedWriter类
* void newLine():一个跨平台的换行
BufferedReader类
* String readLine():一次读取一行数据(不包含回车换行符),并返回,如果读取到文件末尾,返回null
字符缓冲流的好处:
1、提高读写的效率
2、提供了特有功能方便操作
转换流
InputStreamReader
字符流 = 字节流 + 编码表
FileReader = FileInputStream + 默认编码(UTF-8)
InputStreamReader = FileInputStream + 指定编码表
OutputStreamWriter
FileWriter = FileOutputStream + 默认编码(UTF-8)
OutputStreamWriter = FileOutputStream + 指定编码表
作用:
1、读写的过程中进行编码转换
2、将字节流转换为字符流
</code></pre><p>Properties集合 【重点】<br> 什么是Properties?<br> 是一个双列集合,间接实现了Map接口。<br> 没有泛型,存储的键和值都是字符串类型<br> 它提供和IO流相结合的方法</p>
<pre><code>Properties
共性功能 【了解】
特有功能 【重点】
Object setProperty(String key, String value) 相当于put方法
String getProperty(String key) 相当于get方法 【重点】
Set<String> stringPropertyNames() 相当于keySet方法
void load(字节/字符输入流 inStream) 读取 【重点】
void store(字节/字符输出流, String comments) 写出
java中的配置方式
1、配置文件方式
.properties文件方式
.xml文件方式
2、注解配置方式
Properties的使用步骤:
1、创建Properties集合对象
2、通过load方法读取.properties文件中的配置信息(格式:键=值)
3、通过getProperty方法根据键获取我们需要的值。
// 1、创建Properties集合对象
Properties prop = new Properties();
// 2、通过load方法读取.properties文件中的配置信息(格式:键=值)
InputStream in = PropertiesDemo3.class.getClassLoader().getResourceAsStream("info.properties");
prop.load(in);
// 3、通过getProperty方法根据键获取我们需要的值。
String username = prop.getProperty("username");// zhangsan
String password = prop.getProperty("password");
</code></pre><p>其他流 - 对象操作流 【了解】<br> 用于操作对象的流,可以将对象直接写出到文件,也可以将文件中的对象读取出来<br> ObjectOutputStream:对象输出流,将对象直接写出到文件<br> 构造方法<br> ObjectOutputStream(OutputStream out)</p>
<pre><code> * 构造方法中不能直接关联文件路径,要传递基本流对象,基本流关联文件路径
成员方法
void writeObject(Object obj)
* Object obj要写出的对象
ObjectInputStream:对象输入流,将文件中的对象读取出来
构造方法
ObjectInputStream(InputStream in)
* 构造方法中不能直接关联文件路径,要传递基本流对象,基本流关联文件路径
成员方法
Object readObject()
* 返回的是读取到的对象
序列化和反序列化的概念
序列化:将对象直接写出到文件
反序列化:将文件中的对象读取出来
Serializable:标识/标记型接口
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
会出现问题,InvalidClassException无效的类异常
该类的序列版本号与从流中读取的类描述符的版本号不匹配(写的时候的类与读取到时候看到的类不同了)
如果出问题了,如何解决呢?
在类中定义一个serialVersionUID常量
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
serialVersionUID常量:序列化版本号,你可以认为这是类的版本号,读和写的时候看类是否是相同的,
就是根据serialVersionUID常量的值来判断的
如果不显式定义serialVersionUID常量,那么系统会根据类的内容去生成一个默认的serialVersionUID
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
transient:瞬态关键字
</code></pre>]]></content>
</entry>
<entry>
<title>JAVA 多线程1</title>
<url>/2021/10/15/Article13/</url>
<content><![CDATA[<h3 id="方式1,继承Thread类"><a href="#方式1,继承Thread类" class="headerlink" title="方式1,继承Thread类"></a>方式1,继承Thread类</h3><ol>
<li><p>定义一类继承Thread,并重写run方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyThread</span> <span class="keyword">extends</span> <span class="title">Thread</span></span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> System.out.println(<span class="string">"线程开启了"</span>+i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建线程对象,并调用start方法开启线程</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> MyThread t1 = <span class="keyword">new</span> MyThread();</span><br><span class="line"> MyThread t2 = <span class="keyword">new</span> MyThread();</span><br><span class="line"> t1.start();</span><br><span class="line"> t2.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h3 id="方式2,实现Runnable接口"><a href="#方式2,实现Runnable接口" class="headerlink" title="方式2,实现Runnable接口"></a>方式2,实现Runnable接口</h3><ol>
<li><p>定义一类实现Runnable,并重写run方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyRunnable</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> System.out.println(<span class="string">"第二种方式实现多线程"</span>+i);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建Thread对象,并将Runnable对象作为构造方法的参数进行传递</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">MyRunnable mr = <span class="keyword">new</span> MyRunnable();</span><br><span class="line">Thread t1 = <span class="keyword">new</span> Thread(mr);</span><br><span class="line">t1.start();</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h3 id="方式3,实现Callable接口"><a href="#方式3,实现Callable接口" class="headerlink" title="方式3,实现Callable接口"></a>方式3,实现Callable接口</h3><ol>
<li><p>定义类实现Callable接口(指定泛型,重写call方法)</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyCallable</span> <span class="keyword">implements</span> <span class="title">Callable</span><<span class="title">String</span>></span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">call</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> System.out.println(<span class="string">"跟女孩表白"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"答应"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
<li><p>创建Callable实现类对象,并将其作为参数传递给FutureTask,最后将FutureTask对象作为参数传递给Thread类的构造</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Demo</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> ExecutionException, InterruptedException </span>{</span><br><span class="line"> MyCallable mc = <span class="keyword">new</span> MyCallable();</span><br><span class="line"></span><br><span class="line"> FutureTask<String> ft = <span class="keyword">new</span> FutureTask<>(mc);</span><br><span class="line"></span><br><span class="line"> Thread t1 = <span class="keyword">new</span> Thread(ft);</span><br><span class="line"></span><br><span class="line"> t1.start();</span><br><span class="line"> String result = ft.get(); <span class="comment">// get方法带有阻塞效果,如果获取不到,会一直等待,后续代码无法执行</span></span><br><span class="line"> System.out.println(result);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>注意:ft.get(); 必须在开启线程后调用</p>
<h3 id="三种实现方式的对比"><a href="#三种实现方式的对比" class="headerlink" title="三种实现方式的对比"></a>三种实现方式的对比</h3><ul>
<li><p>编码的复杂度</p>
<p>继承Thread最简单</p>
</li>
<li><p>程序的扩展性</p>
<p>实现接口的方式扩展性相对更好</p>
</li>
<li><p>是否可以使用Thread的方法</p>
<p>第一种方式可以直接使用Thread的方法</p>
</li>
</ul>
<h3 id="获取和设置线程的名称"><a href="#获取和设置线程的名称" class="headerlink" title="获取和设置线程的名称"></a>获取和设置线程的名称</h3><ol>
<li><p>线程的默认的名称</p>
<p>规则:Thread-编号(编号是从0开始的一个数字)</p>
</li>
<li><p>获取名字</p>
<p>Thread类提供了String getName()方法</p>
</li>
<li><p>设置名称</p>
<ul>
<li>setName(String name);</li>
</ul>
</li>
</ol>
<ul>
<li>通过有参构造传递名称,需要手动重写有参构造</li>
</ul>
<h3 id="获取当前线程对象"><a href="#获取当前线程对象" class="headerlink" title="获取当前线程对象"></a>获取当前线程对象</h3><ol>
<li><p>Thread提供的静态方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Thread对象 Thread.currentThread();</span><br></pre></td></tr></table></figure>
<p>哪个线程在执行这行代码,就返回哪个线程所对应的对象</p>
</li>
</ol>
<h3 id="线程的休眠"><a href="#线程的休眠" class="headerlink" title="线程的休眠"></a>线程的休眠</h3><ol>
<li><p>Thread类中提供sleep方法·</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Thread.sleep(<span class="keyword">long</span> 毫秒值);</span><br></pre></td></tr></table></figure>
<p>哪个线程在执行这行代码,哪个线程就休眠指定毫秒</p>
</li>
</ol>
<h3 id="线程的调度模型"><a href="#线程的调度模型" class="headerlink" title="线程的调度模型"></a>线程的调度模型</h3><blockquote>
<p>java中的多线程是一种抢占式的模型。多个线程争夺CPU的执行权。线程中存在一个优先级的概念,优先级越高的线程抢夺到CPU的执行权的概率理论上更高。</p>
</blockquote>
<ol>
<li><p>线程的默认优先级</p>
<p>默认是5(线程的优先级是通过数字来表示的,取值范围是1-10)</p>
</li>
<li><p>如何设置线程的优先级</p>
<p>setPriority(int priority);</p>
</li>
<li><p>如何获取线程的优先级</p>
<p>int getPriority();</p>
</li>
</ol>
<h3 id="守护线程"><a href="#守护线程" class="headerlink" title="守护线程"></a>守护线程</h3><blockquote>
<p>备胎线程:用于守护普通线程的,当普通线程直接结束,守护线程会自动结束执行。</p>
</blockquote>
<ol>
<li><p>如何将一个线程变成守护线程。</p>
<p>setDaemon(true);</p>
</li>
</ol>
<h3 id="多线程的安全问题"><a href="#多线程的安全问题" class="headerlink" title="多线程的安全问题"></a>多线程的安全问题</h3><p><strong>产生的本质原因:</strong></p>
<p>多个线程在同时操作共享数据</p>
<h3 id="同步"><a href="#同步" class="headerlink" title="同步"></a>同步</h3><p><strong>同步代码块解决</strong></p>
<p>语法:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(锁对象){</span><br><span class="line"> 操作共享数据的代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>基本原理:</p>
<ol>
<li>默认锁是打开的</li>
<li>当有线程进入synchronized代码块后锁会自动锁上</li>
<li>当synchronized中的操作共享数据的代码执行完毕后,锁会自动打开</li>
</ol>
<p><strong>同步方法解决</strong></p>
<ul>
<li><p>同步方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">访问修饰符 <span class="keyword">synchronized</span> 返回值 方法名(){</span><br><span class="line"> 操作共享数据的代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>锁:this</p>
</li>
<li><p>同步静态方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">访问修饰符 <span class="keyword">static</span> <span class="keyword">synchronized</span> 返回值 方法名(){</span><br><span class="line"> 操作共享数据的代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>锁:当前类.class(当前类字节码对象)</p>
</li>
</ul>
<h3 id="Lock锁"><a href="#Lock锁" class="headerlink" title="Lock锁"></a>Lock锁</h3><p>语法</p>
<ol>
<li><p>创建Lock锁对象ReentranctLock</p>
</li>
<li><p>操作共享数据之前获取锁</p>
<p>lock();</p>
</li>
<li><p>操作完共享数据要释放锁</p>
<p>unlock();</p>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span>{</span><br><span class="line"> <span class="comment">// 获取锁</span></span><br><span class="line"> <span class="comment">// 操作共享数据的代码</span></span><br><span class="line">}<span class="keyword">catch</span>(异常){</span><br><span class="line"> </span><br><span class="line">}<span class="keyword">finally</span>{</span><br><span class="line"> <span class="comment">// 释放锁</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>示例:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Ticket</span> <span class="keyword">implements</span> <span class="title">Runnable</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> ticket = <span class="number">100</span>;</span><br><span class="line"> <span class="comment">// 创建锁对象</span></span><br><span class="line"> ReentrantLock lock = <span class="keyword">new</span> ReentrantLock(); <span class="comment">// 可重入锁</span></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> lock.lock(); <span class="comment">// 获取锁</span></span><br><span class="line"> <span class="keyword">if</span> (ticket == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Thread.sleep(<span class="number">10</span>);</span><br><span class="line"> ticket--;</span><br><span class="line"> System.out.println(Thread.currentThread().getName() + <span class="string">"在买票,还剩下"</span> + ticket + <span class="string">"张票"</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">// 释放锁</span></span><br><span class="line"> lock.unlock();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="消费者生产者-等待唤醒机制"><a href="#消费者生产者-等待唤醒机制" class="headerlink" title="消费者生产者(等待唤醒机制)"></a>消费者生产者(等待唤醒机制)</h3><p>假设:消费者一共只能吃10个汉堡</p>
<ul>
<li><p>消费者线程</p>
<ol>
<li>判断桌子上是否有汉堡</li>
<li>如果桌子上没有就<strong>等待</strong></li>
<li>如果桌子上有就开吃<ul>
<li><strong>通知</strong>生产者生成汉堡</li>
<li>汉堡的数量减1</li>
</ul>
</li>
</ol>
</li>
<li><p>生产者线程</p>
<ol>
<li><p>判断桌子上是否有汉堡</p>
</li>
<li><p>如果有就<strong>等待</strong></p>
</li>
<li><p>如果没有就生产汉堡,将汉堡放在桌子上</p>
<p><strong>通知</strong>消费者线程吃汉堡</p>
</li>
</ol>
</li>
</ul>
<h3 id="阻塞队列"><a href="#阻塞队列" class="headerlink" title="阻塞队列"></a>阻塞队列</h3><p>队列:一种先进先出的数据结构</p>
<p>阻塞队列:在传统的队列的基础上,加入了两个附加操作</p>
<ul>
<li>当获取元素的线程,发现队列为空,会自动等待,等到队列不为空为止</li>
<li>当添加元素的线程,发现队列已满,会自动等待,等待队列可用为止</li>
</ul>
<p>对象:</p>
<ul>
<li><p>ArrayBlockingQueue</p>
<p>依赖数组实现,有界的</p>
</li>
<li><p>LinkedBlockingQueue</p>
<p>依赖链表实现的,无界的,(最大长度是int的最大值)</p>
</li>
</ul>
<p>方法:</p>
<ul>
<li><p>存</p>
<p>put(元素对象)</p>
</li>
<li><p>取</p>
<p>队列顶端的元素 take();</p>
</li>
</ul>
<h3 id="复习"><a href="#复习" class="headerlink" title="复习"></a>复习</h3><ol>
<li><p>能够知道什么是进程什么是线程</p>
<ul>
<li><p>进程</p>
<p>操作系统中正在执行的程序。每一个程序开始运行后,至少会有一个进程。</p>
</li>
<li><p>线程</p>
<p>线程是某一个进程中的执行逻辑。一个进程中可能包含多个线程。某一个线程只可能属于某一个进程。</p>
</li>
</ul>
</li>
<li><p>并发和并行的</p>
<ul>
<li><p>并行</p>
<p>在同一时刻,有多个指令在<strong>多个</strong>CPU上<strong>同时</strong>执行。</p>
</li>
<li><p>并发</p>
<p>在同一时刻,有多个指定在<strong>单个</strong>CPU上<strong>交替</strong>执行</p>
</li>
</ul>
</li>
<li><p>线程3种创建方式</p>
<ul>
<li>继承Thread类<ul>
<li>写一个类继承Thread,重写run方法</li>
</ul>
</li>
<li>实现Runnable接口<ul>
<li>写一个类实现Runnable接口,重写run方法</li>
<li>创建Runnable的对象,并将其作为参数传递给Thread的构造方法</li>
</ul>
</li>
<li>实现Callable接口<ul>
<li>写一个类实现Callable接口,重写call方法</li>
<li>创建Callable的对象,并将其作为参数传递给FutureTask的构造方法</li>
<li>将FutureTask对象作为参数传递给Thread的构造方法</li>
</ul>
</li>
</ul>
</li>
<li><p>线程中的常见的方法</p>
<ul>
<li><p>获取线程的名称</p>
<p>getName(); 默认名字:Thread-编号</p>
</li>
<li><p>设置线程的名称</p>
<p>setName(String name);</p>
<p>有参构造方法</p>
</li>
<li><p>获取当前线程对象</p>
<p>Thread Thread.currentThread();哪个线程执行到这行代码,就返回该线程对象</p>
</li>
<li><p>线程的睡眠</p>
<p>Thread.sleep(long 毫秒值),哪个线程执行到这行代码,哪个线程就睡眠</p>
</li>
<li><p>设置线程的优先级</p>
<p>setPriority(int priority)</p>
<p>参数的取值范围:1-10,值越大优先级越大</p>
</li>
<li><p>获取线程的优先级</p>
<p>int getPriority(); 默认值是5</p>
</li>
</ul>
</li>
<li><p>守护线程</p>
<p>可以理解为备胎线程,用于守护其它的普通线程而存在。如果普通线程都执行结束,守护线程也会自动结束执行。</p>
<p>怎么将线程设置为守护线程:</p>
<p>setDaemon(true);</p>
</li>
<li><p>线程的安全问题</p>
<ul>
<li><p>什么是安全问题:</p>
<p>多个线程在并发操作共享数据时,会出现数据错乱的问题。</p>
</li>
<li><p>产生的本质原因</p>
<p>多个线程在在同时操作共享数据</p>
</li>
<li><p>解决思想</p>
<p>在同一个时刻,只能让一个线程操作功效数据,当这个线程操作完毕之后,其它线程才可以操作。</p>
</li>
<li><p>解决方式</p>
<ul>
<li><p>同步</p>
<ul>
<li><p>同步代码块</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">synchronized</span>(锁对象){</span><br><span class="line"> 操作共享数据的代码</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>锁对象是任意的。多个线程需要使用同一个锁对象。</p>
</li>
<li><p>同步方法</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">访问修饰符 <span class="keyword">synchronized</span> 返回值 方法名(){</span><br><span class="line"> </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">访问修饰符 <span class="keyword">static</span> <span class="keyword">synchronized</span> 返回值 方法名(){</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>同步方法使用this作为锁对象,同步静态方法使用 当前类.class作为锁对象</p>
</li>
</ul>
</li>
<li><p>Lock锁</p>
<ol>
<li><p>创建锁对象</p>
<p>ReentrantLock</p>
</li>
<li><p>在操作之前获取锁</p>
<p>lock();</p>
</li>
<li><p>操作之后释放锁</p>
<p>unlock(); // 一般写在finally语句块中</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li><p>死锁</p>
<p>多个线程出现相互等待的情况,造成程序无法继续。</p>
<p>出现的原因:锁的嵌套</p>
</li>
<li><p>生产者和消费者</p>
<p>等待唤醒机制</p>
<ul>
<li><p>让当前线程等待</p>
<p>Object类提供了一个wait();</p>
</li>
<li><p>唤醒其它线程</p>
<p>Object类提供两个方法</p>
<ul>
<li><p>notify();</p>
<p>随机换当前锁对象绑定的处于等待状态的某一个线程。</p>
</li>
<li><p>notifyAll();</p>
<p>唤醒当前锁对象绑定的所有处于等待状态的线程。</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
]]></content>
</entry>
<entry>
<title>JAVA 多线程2</title>
<url>/2021/10/20/Article14/</url>
<content><![CDATA[<h3 id="线程的状态"><a href="#线程的状态" class="headerlink" title="线程的状态"></a>线程的状态</h3><ul>
<li><p>新建</p>
<p>new 线程对象</p>
</li>
<li><p>就绪状态</p>
<p>调用线程对象的start()方法</p>
</li>
<li><p>阻塞状态</p>
<p>获取不到锁对象</p>
</li>
<li><p>等待状态</p>
<p>调用锁对象wait();方法</p>
</li>
<li><p>计时等待</p>
<p>调用Thread的sleep方法</p>
</li>
<li><p>退出状态</p>
<p>线程中的所有代码执行完毕</p>
</li>
</ul>
<h3 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h3><p>概念</p>
<blockquote>
<p>一个用于存储线程对象的容器。传统的编程,需要通过线程执行任务的时候,都是临时创建线程对象,使用完毕线程自动会处于退出状态。比较浪费时间和资源。线程池的基本思想就是将创建的线程对象存入一个容器中,需要使用线程时候从容器中获取,使用完毕之后将线程归还到容器,可以让线程对象得到<strong>复用</strong></p>
</blockquote>
<p>步骤:</p>
<ol>
<li>创建池子</li>
<li>将需要执行的任务提交给线程池</li>
<li>关闭线程池</li>
</ol>
<h4 id="创建默认线程池"><a href="#创建默认线程池" class="headerlink" title="创建默认线程池"></a>创建默认线程池</h4><ol>
<li><p>创建线程池,返回一个操作线程池的对象</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ExecutorsService service = Executors.newCachedThreadPool();</span><br></pre></td></tr></table></figure>
</li>
<li><p>通过ExecutorsService提交需要执行的任务</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">service.submit(Runnable runnable);</span><br><span class="line">service.submit(Callable callable);</span><br></pre></td></tr></table></figure>
<p>示例:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ExecutorService executorService = Executors.newCachedThreadPool();</span><br><span class="line">executorService.submit(()->{</span><br><span class="line"> System.out.println(Thread.currentThread().getName()+<span class="string">"在执行了"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">Thread.sleep(<span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line">executorService.submit(()->{</span><br><span class="line"> System.out.println(Thread.currentThread().getName()+<span class="string">"在执行了"</span>);</span><br><span class="line">});</span><br></pre></td></tr></table></figure>
</li>
<li><p>如果所有线程都使用完毕,而且后续没有其他线程需要使用,则可以关闭整个线程池</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">executorService.shutDown();</span><br></pre></td></tr></table></figure>
</li>
</ol>
<h4 id="创建一个指定上限的线程池"><a href="#创建一个指定上限的线程池" class="headerlink" title="创建一个指定上限的线程池"></a>创建一个指定上限的线程池</h4><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ExecutorService executorService = Executors.newFixedThreadPool(<span class="number">10</span>);<span class="comment">// 参数10代表最大线程数量</span></span><br></pre></td></tr></table></figure>
<h4 id="基于ThreadPoolExecutor创建线程池"><a href="#基于ThreadPoolExecutor创建线程池" class="headerlink" title="基于ThreadPoolExecutor创建线程池"></a>基于ThreadPoolExecutor创建线程池</h4><ol>
<li><p>示例代码:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">ThreadPoolExecutor pool = <span class="keyword">new</span> ThreadPoolExecutor(</span><br><span class="line"> <span class="number">2</span>,</span><br><span class="line"> <span class="number">5</span>,</span><br><span class="line"> <span class="number">5</span>,</span><br><span class="line"> TimeUnit.SECONDS,</span><br><span class="line"> <span class="keyword">new</span> ArrayBlockingQueue<>(<span class="number">10</span>),</span><br><span class="line"> Executors.defaultThreadFactory(),</span><br><span class="line"> <span class="keyword">new</span> ThreadPoolExecutor.AbortPolicy()</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
</li>
<li><p>参数详解</p>
<p>| 参数 | 解释 |<br>| ——- | —————————————————————————————— |<br>| 参数1 | 核心线程数量(就算达到指定空闲时间,核心线程不会被自动回收) |<br>| 参数2 | 最大线程数量(当达到指定的空闲时间,(最大线程数-核心线程数)的空闲线程会被自动回收,减少资源浪费) |<br>| 参数3 | 空闲时间(达到多长时间,开始进行空闲线程的回收) |<br>| 参数4 | 时间单位,需要通过TimeUnit的枚举进行指定 |<br>| 参数5 | 阻塞队列,当提交的线程数量超过最大最大线程数时,会将其它任务放到阻塞队列中进行排队 |<br>| 参数6 | 创建线程的工厂,线程池内部创建线程是通过线程工厂进行创建 |<br>| 参数7 | 任务的拒绝策略,当提交的任务数量超过 (最大线程数量+阻塞队列的长度) 就会触发拒绝策略 |</p>
</li>
</ol>
<h3 id="volatile问题"><a href="#volatile问题" class="headerlink" title="volatile问题"></a>volatile问题</h3><ol>
<li><p>多线程共享数据的<strong>可见性</strong>的问题:</p>
<p>当一个线程修改了共享数据的值后,其它线程不一定能够及时获取到修改后的最新值</p>
</li>
<li><p>问题产生的原因</p>
<p>线程操作共享数据时,默认是先将共享数据中的值拷贝到线程本地的变量副本中,后续的操作操作的是变量副本的值。</p>
</li>
<li><p>解决方式</p>
<ul>
<li><p>volatile关键字</p>
<p>在共享数据前面通过volatile进行修饰。强制要求线程每次都重新获取共享数据中的最新值。</p>
</li>
<li><p>synchronized关键字</p>
<p>在每次执行时,先清空变量副本,重新从共享数据中拷贝最新值到变量副本中。</p>
</li>
</ul>
</li>
</ol>
<h3 id="原子性-Atomic"><a href="#原子性-Atomic" class="headerlink" title="原子性(Atomic)"></a>原子性(Atomic)</h3><p>概念:</p>
<blockquote>
<p>一个包含多个操作的逻辑单元,要么同时执行成功,要么同时执行失败。强调的是一个逻辑单元是一个整体,不可分割。</p>
</blockquote>
<ol>
<li><p>count++问题</p>
<p>count++ 包含如下三个步骤:</p>
<ul>
<li>拷贝共享数据中的值到变量副本中</li>
<li>修改变量副本中的值(+1)</li>
<li>将变量副本中的值重新赋值给共享数据</li>
</ul>
<p>在执行到任意步骤的时候都可能会被其它线程打断,最终造成数据有误。所以count++的操作不是原子性的。</p>
</li>
<li><p>原子性问题产生的本质原因:</p>
<p>一个线程中的某个操作可能包含多个步骤,在执行到某一步的时候被其它线程给打断。</p>
</li>
<li><p>为什么synchronized能够解决原子性的问题:</p>
<p>加锁之后,不会出现多个线程同时操作共享数据的问题。操作不可能被打断,所以肯定不会出现问题。</p>
</li>
</ol>
<h3 id="原子类AtomicInteger"><a href="#原子类AtomicInteger" class="headerlink" title="原子类AtomicInteger"></a>原子类AtomicInteger</h3><p>基本使用:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> <span class="comment">// 1. 创建AtomicInteger对象</span></span><br><span class="line"> <span class="comment">// AtomicInteger integer = new AtomicInteger(); // 初始值默认为0</span></span><br><span class="line"> AtomicInteger integer = <span class="keyword">new</span> AtomicInteger(<span class="number">10</span>);</span><br><span class="line"> System.out.println(integer);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. 获取值并自增的方法(返回的原始值)</span></span><br><span class="line"> <span class="keyword">int</span> value = integer.getAndIncrement();</span><br><span class="line"> System.out.println(value);</span><br><span class="line"> System.out.println(integer.get());</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3. 获取值并自增的方法(返回的自增后的值)</span></span><br><span class="line"> <span class="keyword">int</span> value2 = integer.incrementAndGet();</span><br><span class="line"> System.out.println(value2);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 4. 获取值并设置新的值(返回的是原来的值)</span></span><br><span class="line"> <span class="keyword">int</span> value3 = integer.getAndSet(<span class="number">20</span>);</span><br><span class="line"> System.out.println(value3);</span><br><span class="line"> System.out.println(integer.get());</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="CAS算法"><a href="#CAS算法" class="headerlink" title="CAS算法"></a>CAS算法</h4><blockquote>
<p>compare and swarp</p>
</blockquote>
<p>核心:在修改共享数据(内存值)的时候比较线程中记录的获取到旧值和共享数据中的内存值是否一致</p>
<ol>
<li>如果一致,证明没有其他线程操作过内存值,那么直接进行修改</li>
<li>如果不一致,证明已经有其它线程操作过内存值,那么需要重新将最新值获取到,重新进行操作。这个操作叫自旋。</li>
</ol>
<h3 id="ConcurrentHashMap-jdk1-7原理"><a href="#ConcurrentHashMap-jdk1-7原理" class="headerlink" title="ConcurrentHashMap jdk1.7原理"></a>ConcurrentHashMap jdk1.7原理</h3><ul>
<li><p>创建对象时</p>
<ol>
<li>创建固定长度为16的大数组,加载因子是0.75</li>
<li>创建了一个长度为2的小数组,并将其地址值赋值给大数组的0索引</li>
</ol>
</li>
<li><p>存入元素时</p>
<ol>
<li>根据键的hash值,计算出在大数组中的索引</li>
<li>判断大数组对应的索引值是否是null<ul>
<li>是 null<ul>
<li>以大数组0号索引为模板创建长度为2的小数组,将其地址值赋值大数组的当前索引</li>
<li>会根据键进行二次hash,计算出小数组中的索引,直接存入</li>
</ul>
</li>
<li>不是null<ul>
<li>根据地址值找到小数组</li>
<li>会根据键进行二次hash,计算出小数组中的索引,判断是否需要扩容,如果需要则扩容两倍</li>
<li>判断小数组的索引位置是否为null<ul>
<li>如果为null,直接存入</li>
<li>如果不为null,调用equals方法判断属性值是否一致<ul>
<li>如果一致,不会进行存入</li>
<li>如果不一致,将元素存入小数组的对应索引,将老的元素挂载到下面形成hash桶结构</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
<li><p>如何解决安全问题</p>
<p>当有一个线程在操作大数组中的某一个索引时,会将对应索引下的小数组锁住。其它索引的位置,还是可以被其它线程操作。</p>
</li>
</ul>
<h3 id="ConcurrentHashMap-jdk1-8原理"><a href="#ConcurrentHashMap-jdk1-8原理" class="headerlink" title="ConcurrentHashMap jdk1.8原理"></a>ConcurrentHashMap jdk1.8原理</h3><ol>
<li>如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。<br>在第一次添加元素的时候创建哈希表</li>
<li>计算当前元素应存入的索引。</li>
<li>如果该索引位置为null,则利用cas算法,将本结点添加到数组中。</li>
<li>如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。</li>
<li>当链表的长度大于等于8时,大数组的长度等于64,自动转换成红黑树</li>
<li>以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性</li>
</ol>
<h3 id="CountDownLatch"><a href="#CountDownLatch" class="headerlink" title="CountDownLatch"></a>CountDownLatch</h3><p>应用场景:某一个线程需要等待其它线程之后完毕之后再执行</p>
<p><img src="多线程day02.assets\image-20211018165524678.png" alt="image-20211018165524678"></p>
<h3 id="Semaphore"><a href="#Semaphore" class="headerlink" title="Semaphore"></a>Semaphore</h3><p>应用场景:控制同时执行的线程的数量</p>
<p>编码步骤:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 场景Semaphore对象,并且传入允许同时执行线程数量</span></span><br><span class="line">Semaphore semaphore = <span class="keyword">new</span> Semaphore(数量);</span><br><span class="line"></span><br><span class="line">run(){</span><br><span class="line"> semaphore.acquire(); <span class="comment">// 获取许可证(如果能够获取到,数量会减1,如果数量为0,则获取不到,那么代码会在这一行阻塞)</span></span><br><span class="line"> <span class="comment">//需要执行的核心代码</span></span><br><span class="line"> <span class="comment">//需要执行的核心代码</span></span><br><span class="line"> semaphore.release(); <span class="comment">// 归还许可证(数量会加1)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h3 id="复习"><a href="#复习" class="headerlink" title="复习"></a>复习</h3><ol>
<li><p>线程的状态,状态切换</p>
<ul>
<li><p>新建</p>
<p>创建线程对象</p>
</li>
<li><p>就绪</p>
<p>调用start方法</p>
</li>
<li><p>阻塞</p>
<p>获取不到锁</p>
</li>
<li><p>等待</p>
<p>wait()方法</p>
</li>
<li><p>计时等待</p>
<p>sleep()方法</p>
</li>
<li><p>退出</p>
<p>run方法中的代码执行完毕</p>
</li>
</ul>
</li>
<li><p>线程池</p>
<ul>
<li><p>概念</p>
<p>用于存储线程对象的容器。当我们需要执行任务的时候,只需要将任务提交给线程池,线程池会自动创建线程执行任务。当线程使用完毕后,会将线程归还到线程池中,方便复用。</p>
</li>