22种常用设计模式示例代码

22种常用设计模式示例代码

仓库地址https://github.com/Xiamu-ssr/DesignPatternsPractice
参考教程 refactoringguru设计模式-目录

创建型模式

软件包 复杂度 流行度
工厂方法 factorymethod ❄️ ⭐️⭐️⭐️
抽象工厂 abstractfactory ❄️❄️ ⭐️⭐️⭐️
生成器 builder ❄️❄️ ⭐️⭐️⭐️
原型 prototype ❄️ ⭐️⭐️
单例 singleton ❄️ ⭐️⭐️⭐️

结构型模式

软件包 复杂度 流行度
适配器 adapter ❄️ ⭐️⭐️⭐️
桥接 bridge ❄️❄️❄️ ⭐️
组合 composite ❄️❄️ ⭐️⭐️
装饰 decorator ❄️❄️ ⭐️⭐️
外观 facade ❄️ ⭐️⭐️
享元 flyweight ❄️❄️❄️ ⭐️
代理 proxy ❄️❄️ ⭐️

行为模式

软件包 复杂度 流行度
责任链 chainofresponsibility ❄️❄️ ⭐️
命令 command ❄️ ⭐️⭐️⭐️
迭代器 iterator ❄️❄️ ⭐️⭐️⭐️
中介者 mediator ❄️❄️ ⭐️⭐️
备忘录 memento ❄️❄️❄️ ⭐️
观察者 observer ❄️❄️ ⭐️⭐️⭐️
状态 state ❄️ ⭐️⭐️
策略 strategy ❄️ ⭐️⭐️⭐️
模版方法 templatemethod ❄️ ⭐️⭐️
访问者 visitor ❄️❄️❄️ ⭐️
深入探讨5种单例模式

深入探讨5种单例模式

一、对比总览

以下是不同单例模式实现方式的特性对比表格。表格从线程安全性、延迟加载、实现复杂度、反序列化安全性、防反射攻击性等多个方面进行考量。

特性 饿汉式 饱汉式 饱汉式-双检锁 静态内部类 枚举单例(推荐)
线程安全性 ×
延迟加载 × ×
实现复杂度 ×
反序列化安全性 × × × ×
防反射攻击性 × × × ×

详细解释

  1. 线程安全性

    • 饿汉式:类加载时即创建实例(因为instance是static),线程安全。
    • 饱汉式:未使用同步机制,线程不安全。
    • 饱汉式-双检锁:使用同步块和双重检查锁,线程安全。
    • 静态内部类:通过类加载机制,线程安全。
    • 枚举单例:JVM确保线程安全。
  2. 延迟加载

    • 饿汉式:类加载时即创建实例,不具备延迟加载。
    • 饱汉式:实例在首次使用时创建,具备延迟加载。
    • 饱汉式-双检锁:实例在首次使用时创建,具备延迟加载。
    • 静态内部类:实例在首次使用时创建(因为静态内部类只有在使用时才会加载),具备延迟加载。
    • 枚举单例:类加载时即创建实例,不具备延迟加载。
  3. 实现复杂度

    • 饿汉式:实现简单。
    • 饱汉式:实现简单。
    • 饱汉式-双检锁:实现相对复杂。
    • 静态内部类:实现简单。
    • 枚举单例:实现简单。
  4. 反序列化安全性

    • 饿汉式饱汉式饱汉式-双检锁静态内部类 :需要实现 readResolve 方法以防止反序列化创建新实例。
    • 枚举单例:天然防止反序列化创建新实例,JVM保证。
  5. 防反射攻击性

    • 饿汉式饱汉式饱汉式-双检锁静态内部类:可能通过反射创建新实例。
    • 枚举单例 :防止反射攻击,创建新实例时抛出 IllegalArgumentException

二、代码

1. 饿汉式

language-java
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
public class OrderManager1 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

@Getter
private static final OrderManager1 instance = new OrderManager1();

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

2. 饱汉式

language-java
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
public class OrderManager2 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static OrderManager2 instance;

public static OrderManager2 getInstance(){

if (instance == null){

instance = new OrderManager2();
}
return instance;
}

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

3. 饱汉式-双检锁

language-java
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
public class OrderManager3 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static OrderManager3 instance;

public static OrderManager3 getInstance(){

if (instance == null){

synchronized (OrderManager3.class){

if (instance == null){

instance = new OrderManager3();
}
}
instance = new OrderManager3();
}
return instance;
}

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

4. 静态内部类

language-java
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
public class OrderManager4 {

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

private static class SingletonHelper{

private static final OrderManager4 INSTANCE = new OrderManager4();
}

public static OrderManager4 getInstance(){

return SingletonHelper.INSTANCE;
}


/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

5. 枚举单例

language-java
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
public enum OrderManager5 {

INSTANCE;

@Getter
@Setter
private Map<String, TradeOrder> orders = new HashMap<>();

/**
* 添加订单
*
* @param order 顺序
*/
public void addOrder(TradeOrder order) {

orders.put(order.getId(), order);
}

/**
* 获取订单
*
* @param orderId 订单id
* @return {@link TradeOrder}
*/
public TradeOrder getOrder(String orderId) {

return orders.get(orderId);
}
}

三、性能对比

language-java
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
package org.dragon.singleton;

import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Slf4j
public class SingletonPerformanceTest {

static long timeout = 20; // 超时时间,单位为秒
static int testIterations = 10_000_000; // 测试次数
static int threadCount = 1000; // 并发线程数
static Map<String, HashMap<String, Long>> result = new HashMap<>();

public static void main(String[] args) {

/*
* 多次调用,结果是最后一次调用存入。为什么多次调用,因为单次test不准确,总是靠前的OrderManager跑的快,可能是因为Java某些机制导致的
* 所以多次调用,逐渐平稳。
* */
firstCreationTest();
mulAccessTest();
mulAccessTest();
mulAccessTest();
ConcurrentAccessTest();
ConcurrentAccessTest();
printRes();
ConcurrentAccessTest();
printRes();
ConcurrentAccessTest();
printRes();
}

/**
* 打印结果
*/
private static void printRes(){

ArrayList<String> names = new ArrayList<>();
names.add(OrderManager1.class.getSimpleName());
names.add(OrderManager2.class.getSimpleName());
names.add(OrderManager3.class.getSimpleName());
names.add(OrderManager4.class.getSimpleName());
names.add(OrderManager5.class.getSimpleName());
// 表头
System.out.printf("%-20s%-20s%-25s%-25s%-20s%n", "Singleton Type", "First Creation (ms)", "Multiple Access (ms)", "Concurrent Access (ms)", "Memory Used (MB)");
System.out.println("---------------------------------------------------------------------------------------------------------------");

for (String name : names) {

// 打印结果,转换时间为毫秒
System.out.printf("%-20s%-20.3f%-25.3f%-25.3f%-20.3f%n", name, result.get(name).get("firstCreation") / 1_000_000.0, result.get(name).get("mulAccess") / 1_000_000.0, result.get(name).get("ConcurrentAccess") / 1_000_000.0, 0 / (1024.0 * 1024.0));
}
}

/**
* 首次创建测试
*/
private static void firstCreationTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->firstCreation(OrderManager1::getInstance));
tests.add(()->firstCreation(OrderManager2::getInstance));
tests.add(()->firstCreation(OrderManager3::getInstance));
tests.add(()->firstCreation(OrderManager4::getInstance));
tests.add(()->firstCreation(() -> OrderManager5.INSTANCE));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 多次访问测试
*/
private static void mulAccessTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->mulAccess(OrderManager1::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager2::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager3::getInstance, testIterations));
tests.add(()->mulAccess(OrderManager4::getInstance, testIterations));
tests.add(()->mulAccess(() -> OrderManager5.INSTANCE, testIterations));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 多线程访问测试
*/
private static void ConcurrentAccessTest(){

List<Runnable> tests = new ArrayList<>();
tests.add(()->ConcurrentAccess(OrderManager1::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager2::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager3::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(OrderManager4::getInstance, testIterations, threadCount));
tests.add(()->ConcurrentAccess(() -> OrderManager5.INSTANCE, testIterations, threadCount));
// 随机化测试顺序
Collections.shuffle(tests);
//run
for (Runnable test : tests) {

test.run();
log.info("Complete one test");
try {

Thread.sleep(200);
} catch (InterruptedException e) {

throw new RuntimeException(e);
}
}
}

/**
* 首次创建
*
* @param singletonSupplier 单一供应商
* @return long ns
*/
private static <T> long firstCreation(Supplier<T> singletonSupplier){

// 测试首次创建时间
long startTime = System.nanoTime();
T instance = singletonSupplier.get();
long endTime = System.nanoTime();
long resTime = endTime - startTime;
//save res
String simpleName = instance.getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("firstCreation", resTime);
return resTime;
}

/**
* 多次访问
*
* @param singletonSupplier 单一供应商
* @param iterations 迭代
* @return long ns
*/
private static <T> long mulAccess(Supplier<T> singletonSupplier, int iterations){

//预热
for (int i = 0; i < 100_000; i++) {

T instance = singletonSupplier.get();
}
//计算
long startTime = System.nanoTime();
for (int i = 0; i < iterations; i++) {

T instance = singletonSupplier.get();
}
long endTime = System.nanoTime();
long resTime = endTime - startTime;
//save res
String simpleName = singletonSupplier.get().getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("mulAccess", resTime);
return resTime;
}

/**
* 并发访问
*
* @param singletonSupplier 单一供应商
* @param iterations 迭代
* @param threadCount 线程数
* @return long ns
*/
private static <T> long ConcurrentAccess(Supplier<T> singletonSupplier, int iterations, int threadCount){

ExecutorService executorService = Executors.newFixedThreadPool(100);
//预热
CountDownLatch latch1 = new CountDownLatch(100);
for (int i = 0; i < threadCount; i++) {

executorService.submit(() -> {

for (int j = 0; j < 100_000; j++) {

T instance = singletonSupplier.get();
}
latch1.countDown();
});
}
try {

boolean completed = latch1.await(timeout, TimeUnit.SECONDS);
if (!completed) {

System.out.println("Concurrent access test for 预热" + singletonSupplier.get().getClass().getSimpleName() + " timed out!");
}
} catch (InterruptedException e) {

e.printStackTrace();
}
//计算
CountDownLatch latch2 = new CountDownLatch(threadCount);
long startTime = System.nanoTime();
for (int i = 0; i < threadCount; i++) {

executorService.submit(() -> {

for (int j = 0; j < iterations; j++) {

T instance = singletonSupplier.get();
}
latch2.countDown();
});
}
try {

boolean completed = latch2.await(timeout, TimeUnit.SECONDS);
if (!completed) {

System.out.println("Concurrent access test for " + singletonSupplier.getClass().getSimpleName() + " timed out!");
}
} catch (InterruptedException e) {

e.printStackTrace();
}
long endTime = System.nanoTime();
long concurrentAccessTime = endTime - startTime;

executorService.shutdown();
//save res
String simpleName = singletonSupplier.get().getClass().getSimpleName();
HashMap<String, Long> resMap = result.computeIfAbsent(simpleName, k->new HashMap<>());
resMap.put("ConcurrentAccess", concurrentAccessTime);
return concurrentAccessTime;
}
}

结果输出如下

language-txt
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
[17:15:54.519] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:54.730] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:54.936] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.141] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.347] [INFO ] org.dragon.singleton.SingletonPerformanceTest 73 firstCreationTest - Complete one test
[17:15:55.554] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:55.782] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.007] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.227] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.445] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.669] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:56.906] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.146] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.376] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.598] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:57.818] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.054] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.276] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.495] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:58.716] [INFO ] org.dragon.singleton.SingletonPerformanceTest 97 mulAccessTest - Complete one test
[17:15:59.430] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:15:59.658] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:02.737] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:08.533] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:13.700] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:19.432] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:24.632] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:30.366] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:35.516] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:40.431] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4928.236 0.000
OrderManager2 0.010 18.592 5504.781 0.000
OrderManager3 0.009 19.127 5513.309 0.000
OrderManager4 0.241 18.772 4920.940 0.000
OrderManager5 0.117 16.637 4704.835 0.000
[17:16:45.002] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:48.982] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:52.778] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:16:57.834] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:02.298] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4217.333 0.000
OrderManager2 0.010 18.592 4340.869 0.000
OrderManager3 0.009 19.127 4824.581 0.000
OrderManager4 0.241 18.772 3743.032 0.000
OrderManager5 0.117 16.637 3558.995 0.000
[17:17:06.778] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:11.231] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:15.733] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:20.812] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
[17:17:25.837] [INFO ] org.dragon.singleton.SingletonPerformanceTest 121 ConcurrentAccessTest - Complete one test
Singleton Type First Creation (ms) Multiple Access (ms) Concurrent Access (ms) Memory Used (MB)
---------------------------------------------------------------------------------------------------------------
OrderManager1 0.010 16.848 4281.649 0.000
OrderManager2 0.010 18.592 4849.279 0.000
OrderManager3 0.009 19.127 4782.224 0.000
OrderManager4 0.241 18.772 4267.228 0.000
OrderManager5 0.117 16.637 4233.907 0.000

进程已结束,退出代码为 0

可以去多跑几次,基本上最后一种枚举单例,性能属于最好的一批。并且也最安全。

Java - JVM

Java - JVM

一、JVM

1. JVM的作用

Java代码编译成java字节码后,运行在JVM中,只要针对不同的系统都开发JVM后,java就实现了跨平台。

2. JVM、JRE、JDK的关系

3. JVM的组成

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

4. JVM工作流程

ClassLoader负责加载字节码文件,至于是否可以运行,由Execution Engine决定。
Execution Engine把指令和数据信息加载到内存中,并且负责把命令解释到操作系统,将JVM指令集翻译成操作系统指令集。
Execution Engine执行指令时,可能会调用一些本地的接口,这时就需要用到Native Interface,主要负责调用本地的接口给java程序使用,会在本地方法栈中记录对应的本地方法。

5. 运行时方法区Runtime Data Area

是JVM最重要的部分,运行时数据区的组成主要包含以下

  1. PC Register(程序计数器)
    程序计数器是程序控制流的指示器,循环,跳转,异常处理,线程的恢复等工作都需要依赖程序计数器去完成。程序计数器是线程私有的,它的生命周期是和线程保持一致的,我们知道,N 个核数的 CPU 在同一时刻,最多有 N个线程同时运行,在我们真实的使用过程中可能会创建很多线程,JVM 的多线程其实是通过线程轮流切换,分配处理器执行时间来实现的。既然涉及的线程切换,所以每条线程必须有一个独立的程序计数器。
  2. Stack (Java虚拟机栈)
    虚拟机栈,其描述的就是线程内存模型,也可以称作线程栈,也是每个线程私有的,生命周期与线程保持一致。在每个方法执行的时候,jvm 都会同步创建一个栈帧去存储局部变量表,操作数栈,动态连接,方法出口等信息。一个方法的生命周期就贯彻了一个栈帧从入栈到出栈的全部过程。
  3. Native Method Stack (本地方法栈)
    本地方法栈的概念很好理解,我们知道,java底层用了很多c的代码去实现,而其调用c端的方法上都会有native,代表本地方法服务,而本地方法栈就是为其服务的。
  4. Heap(堆)
    可以说是 JVM 中最大的一块儿内存区域了,它是所有线程共享的,不管你是初学者还是资深开发,多少都会听说过堆,毕竟几乎所有的对象都会在堆中分配。
  5. Method Area(方法区)
    方法区也是所有线程共享的区域,它存储了被 jvm 加载的类型信息、常量、静态变量等数据。运行时常量池就是方法区的一部分,编译期生成的各种字面量与符号引用就存储在其中。

💡 随着Java 7及以后版本的发布,虽然字符串常量池被移至堆内存,运行时常量池仍然是方法区(或Java 8中的元空间)的一部分。
💡 在Java 8之前,方法区的实现被称为永久代(PermGen),并且是堆的一部分。所以,当我们说”方法区”时,从概念上讲,它是JVM的一个独立逻辑部分,但在HotSpot JVM的具体实现中,直到Java 7为止,它是作为堆内存结构的一个部分(即永久代)来实现的,永久代是堆的一个物理部分。
💡从Java 8开始,HotSpot JVM去除了永久代的概念,引入了元空间(Metaspace),并且元空间是在本地内存中,而不是在堆内存中。因此,在Java 8及以后的版本中,方法区的实现从永久代变为了元空间,方法区(现在通常指的是元空间)与堆内存是完全分开的。

💡 元空间在本地内存中,只要内存足够,就不会出现OOM(Out of Memory)。元空间的概念仍然在JVM内存模型中。

二、深入JVM内存模型(JMM)

。。。待续

JavaSE-多线程

JavaSE-多线程

1.创建线程

创建线程有三种方式,都需要子类,然后在主程序中使用它。

其一

继承Thread类,重写run方法,这种方式简单,但是没法继承其他类。

language-java
1
2
3
4
5
6
7
8
9
10
11
public class Test01_01 extends Thread{

@Override
public void run() {

for (int i = 0; i < 5; i++) {

logger.info("thread sout:"+i);
}
}
}

运行方式如下

language-java
1
2
Thread t = new Test01_01();
t.start();

其二

实现Runnable接口,重写run方法,这种方式避免了无法继承别的类的缺陷。

language-java
1
2
3
4
5
6
7
8
9
10
11
12
public class Test02_01 implements Runnable{

@Override
public void run() {

Thread cut = Thread.currentThread();
for (int i = 0; i < 5; i++) {

System.out.println("thread ["+cut.getName()+"]"+i);
}
}
}

运行方式如下

language-java
1
2
Runnable target = new Test02_01();
new Thread(target).start();

或者使用lambda表达式

language-java
1
2
3
4
5
6
7
8
new Thread(()->{

Thread cut = Thread.currentThread();
for (int i = 0; i < 5; i++) {

System.out.println("thread ["+cut.getName()+"]"+i);
}
}).start();

其三

实现Callable接口,重写call方法,这种方式可以取得线程的返回值。

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private static class Test04_01 implements Callable<Long>{

@Override
public Long call() throws Exception {

Thread cut = Thread.currentThread();
System.out.println("当前线程:"+cut.getName());
long res = 0;
for (int i = 0; i < 100; i++) {

for (int j = 0; j < 100; j++) {

res += j;
}
}
return res;
}
};

运行方式如下

language-java
1
2
3
4
5
Callable<Long> call = new Test04_01();
FutureTask<Long> task = new FutureTask<>(call);
new Thread(task).start();
Long l = task.get();
System.out.println(l);

2.线程常用方式

3.线程同步

三种办法

其一

同步代码块,在类中使用

language-java
1
2
3
4
synchronized (LockObject){

//your code
}

对于示例方法,使用this作为LockObject,
对于静态方法,使用类名.class作为LockObject

其二

同步方法,在方法前加上synchronized 关键字
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

其三

Lock锁,定义一把锁,需要加锁时lock,结束时unlock,

language-java
1
2
3
4
5
6
7
8
9
10
11
12
private final Lock lk = new ReentrantLock();
try {

lk.lock();
money -= 1;
} catch (Exception e) {

throw new RuntimeException(e);
} finally {

lk.unlock();
}

4.线程池

创建线程的开销是很大的。使用线程池可以复用线程,迭代任务不迭代线程,这样意味着,我们只能只用Runnable和Callable的方式开多线程。

创建线程池

language-java
1
2
3
4
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());

七个参数的意思分别是:

  1. 核心线程数(一直存活的线程数)
  2. 最大线程数(最大-核心=允许存在的临时线程数)
  3. 临时线程存活时间(如果临时线程超过这个时间没有被分配任务就消亡)
  4. 存活时间单位
  5. 等待队列(ArrayBlockingQueue对象,需指定长度,被线程拿走的任务不算在等待队列)
  6. 线程工厂(使用Executors工具类即可)
  7. 拒绝策略(线程都在忙,等待队列满了,新的任务怎么处理)

临时线程的开启条件为,一个新任务来了,核心线程都在忙,同时等待队列已满,同时线程数还没达到最大线程数时,临时线程被创建,拿走新任务。

线程池常用方法

执行Runnable任务如下

language-java
1
2
Runnable target1 = new MyRunnable();
threadPool.execute(target1);// core 1

执行Callable任务如下

language-java
1
2
Callable<Integer> callableTask = new MyCallable();
Future<Integer> future = threadPool.submit(callableTask);

拒绝策略

线程池工具类

这样就不用自己去设计ThreadPoolExecutor了,Executors底层是封装ThreadPoolExecutor的。

一个经验之谈
计算密集型任务:核心线程数量=CPU核数 + 1
IO密集型任务:核心线程数量=CPU核数 * 2

5.并行并发

并发 并行
CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮巡为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。 在同一个时刻上,同时有多个线程在被CPU调度执行。

多线程是并发和并行同时进行的!

6.悲观乐观锁

悲观锁 乐观锁
一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差! 一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。

上述三种线程同步默认都是悲观锁。以下是一个悲观锁和乐观锁比较代码

language-java
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
public class PostLock01 {

private static class MyRunnable01 implements Runnable{

private int number;
@Override
public void run() {

for (int i = 0; i < 1000; i++) {

synchronized (this){

++number;
}
}
}
};
private static class MyRunnable02 implements Runnable{

// private int number;
private AtomicInteger number = new AtomicInteger();
@Override
public void run() {

for (int i = 0; i < 1000; i++) {

number.incrementAndGet();
}
}
};

public static void main(String[] args) {

long timeStart, timeEnd;
timeStart = System.currentTimeMillis();
pessLock();
timeEnd = System.currentTimeMillis();
System.out.println("pessimism : "+(timeEnd - timeStart)+" ms");

timeStart = System.currentTimeMillis();
optiLock();
timeEnd = System.currentTimeMillis();
System.out.println("optimism : "+(timeEnd - timeStart)+" ms");
}

public static void pessLock(){

Runnable target = new MyRunnable01();
for (int i = 0; i < 32; i++) {

new Thread(target).start();
}
}

public static void optiLock(){

Runnable target = new MyRunnable02();
for (int i = 0; i < 32; i++) {

new Thread(target).start();
}
}

}
Java - 锁

Java - 锁

乐观锁

分为三个阶段:数据读取、写入校验、数据写入。

假设数据一般情况下不会造成冲突,只有在数据进行提交更新时,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回错误信息,让用户决定如何去做。fail-fast机制。

悲观锁

正如其名,它指对数据被外界(可能是本机的其他事务,也可能是来自其它服务器的事务处理)的修改持保守态度。在整个数据处理过程中,将数据处于锁定状态。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响程序的并发访问性,同时这样对数据库性能开销影响也很大,特别是长事务而言,这样的开销往往无法承受。

分布式锁

分布式集群中,对锁接口QPS性能要求很高,单台服务器满足不了要求,可以考虑将锁服务部署在独立的分布式系统中,比如借助分布式缓存来实现。

可重入锁

可重入锁,也叫做递归锁,是指在同一个线程在调外层方法获取锁的时候,再进入内层方法会自动获取锁。ReentrantLocksynchronized 都是 可重入锁。可重入锁的一个好处是可一定程度避免死锁。

这对于设计复杂的程序或库来说是非常重要的。考虑下面这种情况:

language-java
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
class SomeClass {

private final Lock lock = new ReentrantLock();

public void methodA() {

lock.lock();
try {

// 一些代码
methodB(); // 在 methodA 中调用 methodB
// 更多代码
} finally {

lock.unlock();
}
}

public void methodB() {

lock.lock();
try {

// 一些代码
} finally {

lock.unlock();
}
}
}

在这个例子中,methodA()methodB() 都使用了同一把锁。如果 ReentrantLock 不是可重入的,那么当线程在 methodA() 中已经获取了锁后,再次尝试在 methodB() 中获取锁时,就会导致死锁,因为锁已经被同一个线程持有,而不是其他线程。

因此,”可重入”锁允许在同一线程中多次获取同一把锁,这样就能避免死锁,并且简化了程序设计,因为无需担心方法之间的调用顺序是否会导致死锁。

尽管在您提出的例子中看起来似乎并没有太大意义,但在实际的程序设计中,”可重入”锁确实是一个非常重要的概念。

自旋锁

自旋锁是一种基于忙等待(busy-waiting)的锁,它在获取锁时会不断地循环尝试获取锁,而不是让线程进入睡眠状态。自旋锁的主要特点是在锁被其他线程占用时,当前线程会不断地尝试获取锁,直到获取到锁为止,而不会释放 CPU 控制权。

自旋锁适用于以下情况:

  1. 锁被持有的时间短:如果锁被持有的时间很短,那么等待锁的线程不需要进入睡眠状态,使用自旋锁可以避免线程切换的开销

  2. 多核处理器:在多核处理器上,一个线程在自旋等待锁的同时,其他线程可以继续执行,因此自旋锁在多核处理器上能够充分利用 CPU 时间,提高并发性能。

  3. 高并发场景:在高并发的情况下,锁的竞争可能会很激烈,自旋锁可以减少线程的阻塞时间,提高系统的响应速度。

然而,自旋锁也有一些缺点:

  1. 等待时间过长可能会浪费 CPU 资源:如果锁的竞争很激烈,导致线程不断自旋等待锁的释放,可能会浪费大量的 CPU 时间。

  2. 不适用于长时间持有锁的情况:如果锁被持有的时间较长,自旋锁会导致其他线程长时间等待,影响系统的响应性能。

因此,在使用自旋锁时需要权衡利弊,根据具体的场景来决定是否使用自旋锁。通常情况下,自旋锁适用于锁被持有时间短、锁的竞争不激烈的情况下,能够有效提高并发性能。

当然可以。以下是一个简单的示例,演示了如何使用自旋锁来保护一个共享资源:

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.concurrent.atomic.AtomicBoolean;

public class SpinLock {

private AtomicBoolean locked = new AtomicBoolean(false);

public void lock() {

// 不断尝试获取锁,直到成功为止
while (!locked.compareAndSet(false, true)) {

// 自旋等待,不做其他事情,持续尝试获取锁
}
}

public void unlock() {

// 释放锁
locked.set(false);
}
}

在这个示例中,SpinLock 类实现了一个自旋锁。AtomicBoolean 类被用作锁状态的标记,初始时为 false 表示未锁定状态。lock() 方法使用了自旋等待的方式来尝试获取锁,它会不断地尝试将 locked 的值从 false 设置为 true,直到成功获取到锁为止。在 unlock() 方法中,锁会被释放,将 locked 的值重新设置为 false

以下是一个使用 SpinLock 的示例:

language-java
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
public class SpinLockExample {

private static int counter = 0;
private static SpinLock spinLock = new SpinLock();

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

for (int i = 0; i < 1000; i++) {

spinLock.lock();
counter++;
spinLock.unlock();
}
});

Thread t2 = new Thread(() -> {

for (int i = 0; i < 1000; i++) {

spinLock.lock();
counter++;
spinLock.unlock();
}
});

t1.start();
t2.start();
t1.join();
t2.join();

System.out.println("Counter: " + counter);
}
}

在这个示例中,两个线程 t1t2 分别对 counter 执行了 1000 次增加操作,每次增加操作都在获取自旋锁后执行,并在执行完毕后释放自旋锁。最后输出 counter 的值,由于自旋锁的保护,counter 的增加操作是线程安全的。

独享锁

独享锁是指该锁一次只能被一个线程所持有。

共享锁

共享锁是指该锁可被多个线程所持有。ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。读锁的共享锁可保证并发读是非常高效的,读写、写读、写写的过程是互斥的。独享锁与共享锁也是通过AQSAbstractQueuedSynchronizer)来实现的,通过实现不同的方法,来实现独享或者共享。

互斥锁

独享锁/共享锁就是一种广义的说法,互斥锁/读写锁指具体的实现。

读写锁

读写锁在Java中的具体实现就是ReentrantReadWriteLock

阻塞锁

阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争进入运行状态。

JAVA中,能够进入\退出、阻塞状态或包含阻塞锁的方法有 ,synchronized 关键字(其中的重量锁),ReentrantLock,Object.wait()/notify(),LockSupport.park()/unpark()

公平锁

公平锁是指多个线程按照申请锁的顺序来获取锁

非公平锁

非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。

可能造成优先级反转或者饥饿现象。对于Java ReentrantLock而言,通过构造函数 ReentrantLock(boolean fair) 指定该锁是否是公平锁,默认是非公平锁。

非公平锁的优点在于吞吐量比公平锁大。对于Synchronized而言,也是一种非公平锁。

分段锁

分段锁其实是一种锁的设计,目的是细化锁的粒度,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMapJDK7HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLockSegment继承了ReentrantLock)

当需要put元素的时候,并不是对整个HashMap加锁,而是先通过hashcode知道要放在哪一个分段中,然后对这个分段加锁,所以当多线程put时,只要不是放在同一个分段中,可支持并行插入。

对象锁

一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

synchronized修饰非静态方法、同步代码块的synchronized (this)synchronized (非this对象),锁的是对象,线程想要执行对应同步代码,需要获得对象锁。

使用 synchronized 加锁 this 时,只有同一个对象会使用同一把锁,不同对象之间的锁是不同的。 ​

当需要同步访问对象的实例方法或实例变量时,应该使用 this 作为 synchronized 的参数。
例如,在一个多线程环境中,多个线程需要同时访问对象的实例方法或实例变量时,可以使用 synchronized(this) 来确保线程安全。

language-java
1
2
3
4
5
6
7
public void synchronizedBlock() {

synchronized (this) {

// 同步代码块
}
}

类锁

synchronized修饰静态方法或者同步代码块的synchronized (类.class),线程想要执行对应同步代码,需要获得类锁。
使用 synchronized 加锁 class 时,无论共享一个对象还是创建多个对象,它们用的都是同一把锁
当需要同步访问类的静态方法或静态变量时,应该使用 MyClass.class 作为 synchronized 的参数。
例如,在一个多线程环境中,多个线程需要同时访问类的静态方法或静态变量时,可以使用 synchronized(MyClass.class) 来确保线程安全。

language-java
1
2
3
4
5
6
7
public static void synchronizedStaticBlock() {

synchronized (MyClass.class) {

// 同步代码块
}
}

锁升级

锁升级是Java虚拟机中的一种优化策略,它是针对 synchronized 关键字进行的优化,并不是像 ReentrantLock 这样的锁类库,可以在代码逻辑中直接使用的锁。

(无锁、偏向锁、轻量级锁、重量级锁)是指在Java虚拟机中对锁的状态进行优化和调整的过程。这些状态反映了对象的锁定状态以及锁的竞争情况。这种锁升级的目的是为了在不同情况下提供不同的锁定机制,以减少锁的竞争、提高性能。

下面是对这几种锁状态的简要介绍:

  1. 无锁状态

    • 当线程尝试获取锁时,对象的锁状态为无锁状态,表示该对象没有被任何线程锁定。
    • 在无锁状态下,线程会通过CAS(Compare and Swap)等原子操作尝试直接修改对象的指针或标记位,来尝试获取锁。
  2. 偏向锁

    • 当只有一个线程访问同步块时,对象的锁状态会升级为偏向锁状态。
    • 偏向锁会将线程的ID记录在对象头中,表示该线程拥有对象的偏向锁。当其他线程尝试获取锁时,会检查偏向锁的线程ID是否与当前线程ID相同,如果相同则表示获取成功。
  3. 轻量级锁

    • 当有多个线程竞争同步块时,对象的锁状态会升级为轻量级锁状态。
    • 轻量级锁使用CAS操作来避免传统的互斥量操作,尝试在用户态下通过自旋来获取锁。如果自旋获取锁失败,则升级为重量级锁。
  4. 重量级锁

    • 当轻量级锁竞争失败时,对象的锁状态会升级为重量级锁状态。
    • 重量级锁会使得竞争失败的线程进入阻塞状态,从而让出CPU资源,减少竞争。

锁升级是Java虚拟机对锁状态的动态调整过程,旨在根据实际的锁竞争情况和线程行为来选择最适合的锁策略,以提高程序的并发性能。

JavaSE-网络通信

JavaSE-网络通信

一、概述

基本的通信架构有2种形式:

  • CS架构( Client客户端/Server服务端)
  • BS架构(Browser浏览器/Server服务端) IPV4共32bit,4字节,形如192.168.000.001
    IPV6共128bit,16字节,形如2001:0db8:0000:0023:0008:0800:200c:417a
    IPV4形为4位十进制数,每个数字基于8bit,范围0~255
    IPV6形为8位十六进制数,每个数基于16bit,范围0000~FFFF(0 ~ 65535)
    使用Java自带的java.net.InetAddress 可以互动IP 前两个为构造器,后三个为方法

二、端口和协议

UDP

不事先建立连接,发送方直接发送,不管对面状态,是不可靠连接。但是通信效率高,适合诸如视频直播、语音通话等。单个数据包最大限制为64KB。

TCP

事先通过三次握手建立连接,可靠连接,四次挥手断开连接,在不可靠信道做到可靠连接

三次握手是要Client和Server知道对方可以’收’和’发’
第一次握手:S知道C可以发
第二次握手:C知道S可以收发
第三次握手:S知道C可以收发
之后传输数据时,C也不断向S发送确认消息,S需要回复确认,如果没有,那么C会采取重发等措施。

三、UDP编程

S和C都需要先创建数据包类,用来接收信息。
S代码如下

language-cpp
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 Server {

public static void main(String[] args) throws Exception {

//1. create server socket
DatagramSocket socket = new DatagramSocket(8889);
//2. create packet
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {

//3.receive
socket.receive(packet);

System.out.println("服务端收到了长度为 "+packet.getLength()+" bytes的数据");
System.out.println("数据为: "+new String(bytes,0, packet.getLength()));
System.out.println("消息来自:\nIP->"+packet.getAddress().getHostAddress());
System.out.println("Name->"+packet.getAddress().getHostName());
System.out.println("Port->"+packet.getPort());
System.out.println("-----------------------------");
}

// socket.close();
}
}

S会在socket.receive一直等待C的消息,同时通过packet也可以查看发送方的一些信息。
C代码如下

language-cpp
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
public class Client {

public static void main(String[] args) throws Exception {

//1. create client
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true){

System.out.println("请说:");
String s = sc.nextLine();
if ("exit".equals(s)){

System.out.println("欢迎下次使用~");
break;
}
//2. create data package
byte[] bytes = s.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(),8889);
//3.send
socket.send(packet);
System.out.println("客户端发送了长度为 "+bytes.length+" bytes的数据");
System.out.println("数据为: "+new String(bytes));
System.out.println("----------------------");
}
socket.close();
}
}

你可能会好奇为什么S可以在循环外创建packet重复使用而C每次循环都要创建新的?
因为C每次发送的信息长度是未知的,而packet创建需要给出length所以C每次发送都需要重新创建,同时S对C发送的消息长度也是未知的,但是由于UDP对数据包有64KB上限制约,所以我们可以直接创建个64KB大的packet来接收,然后截断字节就可以。

四、TCP编程

TCP中C由Socket类创建,而S由ServerSocket类创建,然后通过accept方法再次获得Socket类。

S代码如下

language-cpp
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
    public static void main(String[] args) throws Exception {

//1.create serversocket
ServerSocket serverSocket = new ServerSocket(8889);
//2.wait client link request
Socket socket = serverSocket.accept();
//3.get input is for receive message
InputStream is = socket.getInputStream();
//4.package low is to high is
DataInputStream dis = new DataInputStream(is);
while (true) {

try {

//5.receive message
System.out.println("消息来自: "+socket.getRemoteSocketAddress());
String rs = dis.readUTF();
System.out.println("消息为:"+rs);
} catch (IOException e) {

System.out.println("客户端离线了");
break;
}
}
//close
dis.close();
socket.close();
}
}

S会在accept等待C
C代码如下

language-cpp
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
public class Client {

public static void main(String[] args) throws Exception {

//1.create socket and send link request to server
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(), 8889);
//2.get socket output for write message to server
OutputStream os = socket.getOutputStream();
//3.package low os to high os
DataOutputStream dos = new DataOutputStream(os);

Scanner sc = new Scanner(System.in);
while (true) {

System.out.println("请说");
String s = sc.nextLine();
if (s.equals("exit")){

break;
}
//4.write message
dos.writeUTF(s);
dos.flush();//立刻发送
System.out.println("已发送~");
}

//close
dos.close();
socket.close();
}
}

通过高级流包装原始流,方便编程,这里用的是数据流,将字符串以UTF-8的编码形式传输。

五、QA

TCP怎么实现多个C对一个S?

一个S应对多个C的TCP通信,S需要线程池,每个通信都抛给子线程处理即可。在ServerSocket.accept到一个C的适合,就把这个socket抛到子线程。

TCP怎么实现群聊?

C除了需要发送信息,还需要接收信息,并且两个行为需要持续开启,所以C需要多线程,一个从用户键盘读取信息发送,一个从S接收其他C发送的信息。
S除了线程池外,还需要一个子线程都能访问共享数组,来保存所有socket,子线程收到对应的C的消息,就抄一份发给所有socket。

JavaSE-高级特性

JavaSE-高级特性

一、单元测试

使用Junit框架

二、反射

反射可以破坏封装性,得到一个类的所有成分,最重要的途径是用来参与Java框架制作。
获取Class对象的三种方式

  • Class c1 = 类名.class
  • 调用Class提供方法: public static Class forName(String package);
  • Object提供的方法: public Class getClass(); Class c3 = 对象.getClass();

反射案例

需求:对于任意对象,将对象的字段名和对应的值保存到文件中去
假设我们有Teacher类和Student类,那么代码如下

language-java
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
public class Demo01 {

private String dirPath = "src/main/resources/P13/";

@Test
public void test01() {

Student s1 = new Student("淇",22,'女',167.56,"swim");
Teacher t1 = new Teacher("柳",5433.2);

work(s1, dirPath+"s.txt");
work(t1, dirPath+"t.txt");
}

public void work(Object object, String filePath) {

//1.create fileIO
try (
// FileWriter fileWriter = new FileWriter(filePath);
PrintWriter printWriter = new PrintWriter(filePath);
) {

//2.get Object fields info
Class c = object.getClass();
Field[] declaredFields = c.getDeclaredFields();
printWriter.println("--------"+c.getSimpleName()+"--------");
for (Field f : declaredFields) {

f.setAccessible(true);
printWriter.println(f.getName()+" --> "+f.get(object));
}
} catch (Exception e) {

throw new RuntimeException(e);
}
}
}

三、注解

我的注解一如下

language-java
1
2
3
4
5
6
7
public @interface MyAnnotation {

String aaa();
boolean bbb() default true;
String[] ccc();
}

注解二如下

language-java
1
2
3
4
public @interface MyAnnotation2 {

String value();
}

注解使用如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.kmo.d08_Annotation.Test01;

import org.junit.Test;

@MyAnnotation(aaa="牛魔王", ccc={
"Python","Java"})
public class MyTest01 {

@MyAnnotation(aaa="铁山",bbb=false,ccc={
"C++","C"})
public void test1(){

}

@MyAnnotation2("孙悟空")
public void test2(){

}
}

解析注解案例

需求:
定义注解MyTest4。
定义一个Demo类,在类中定义一个test1方法,并在该类和其方法上使用MyTest4注解。
定义AnnotationTest3测试类,解析Demo类中的全部注解。

这是一个注解

language-java
1
2
3
4
5
6
7
8
9
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {

String value();
double aaa() default 100;
String[] bbb();
}

这是Demo类

language-java
1
2
3
4
5
6
7
8
9
10
11
12
@MyTest4(value = "vv",aaa = 99.9, bbb={
"b","bb","bbb"})
public class Demo {


@MyTest4(value = "我是至尊宝",aaa = 100.9, bbb = {
"小淇","爱你"})
public void test01(){


}
}

这是解析类,解析Demo类上的注解以及Demo类方法的注解

language-java
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
public class AnnotationTest3 {


@Test
public void test01(){

Class c = Demo.class;
if (c.isAnnotationPresent(MyTest4.class)){

MyTest4 myTest4 = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
@Test
public void test02() throws Exception {

Class c = Demo.class;
Method method = c.getDeclaredMethod("test01");
if (method.isAnnotationPresent(MyTest4.class)){

MyTest4 myTest4 = (MyTest4) method.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}

注解案例-简易Junit

这是一个注解

language-java
1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {


}

这是运行代码

language-java
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
public class Demo {

@MyTest()
public void test01(){

System.out.println("===Test01====");
}

public void test02(){

System.out.println("===Test02====");
}

@MyTest
public void test03(){

System.out.println("===Test03====");
}
public void test04(){

System.out.println("===Test04====");
}

public static void main(String[] args) throws Exception {

Demo demo = new Demo();
Class c = Demo.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {

if (method.isAnnotationPresent(MyTest.class)){

method.invoke(demo);
}
}
}
}

四、动态代理

相当于把相同的地方集合到一起了,只要写一遍,且灵活。

动态代理案例

需求:对一个类的所有方法前后都加上计时,当类的方法被调用时,输出时间。

一个UserService接口

language-java
1
2
3
4
5
6
public interface UserService {

void login(String loginName, String password) throws Exception;
void deleteUsers() throws Exception;
String[] selectUsers() throws Exception;
}

UserServicelmpl实现类

language-java
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
public class UserServiceImpl implements UserService {

@Override
public void login(String loginName, String passWord) throws Exception {

if ("admin".equals(loginName) && "123456".equals(passWord)) {

System.out.println("您登录成功,欢迎光临本系统~");
} else {

System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
}

@Override
public void deleteUsers() throws Exception {

System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
}

@Override
public String[] selectUsers() throws Exception {

System.out.println("查询出3个用户");
String[] names = {
"张全蛋","李二狗","牛爱花"};
Thread.sleep(500);
return names;
}
}

ProxyUtil代理类

language-java
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
public class ProxyUtil {

public static UserService createProxy(UserService userService){

UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{
UserService.class},
new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals("login") || method.getName().equals("deleteUsers") || method.getName().equals("selectUsers")){

long startTime = System.currentTimeMillis();
Object res = method.invoke(userService, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"方法执行耗时: " + (endTime - startTime) / 1000.0 + "s");
return res;
}else {

Object res = method.invoke(userService, args);
return res;
}
}
}
);

return userServiceProxy;
}
}

在这个代理中,被代理的类传入,Proxy.newProxyInstance中重写的invoke方法里,就是在重写被代理类的所有方法,可以理解为在调用(被代理的)类的方法时,其实是在调用这里的invoke。如果不想做更改,直接执行method.invoke就行。

运行代码

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Tests {

public static void main(String[] args) throws Exception {

// UserService userService = new UserServiceImpl();
UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

userService.login("admin","123456");
System.out.println("--------------------");
userService.deleteUsers();
System.out.println("--------------------");
String[] strings = userService.selectUsers();
System.out.println(Arrays.toString(strings));
System.out.println("--------------------");

}
}
JavaSE-日志文件

JavaSE-日志文件


日志接口有两类,

  • Commons Logging (JCL)
  • Simple Logging Facade for Java (SLF4J)

热门的Logback是基于SLF4J实现的。

​​

所以要使用Logback首先需要添加3个依赖,去maven仓库搜一下,用maven加载就好。

= = = = = = = = = = = = = = = = = = =
= = = = = = Maven仓库 = = = = = = =
= = = = = = = = = = = = = = = = = = =

然后我们可以通过一个logback.xml文件来配置logback一些东西,文件放在/src/main/resources下,这是一份样例。

language-xml
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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>

<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>C:/Users/kmo/IdeaProjects/JAVA-DataSt/DataSt/src/main/resources/Log/data.log</file>
<!-- 指定日志文件拆分和压缩规则-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>C:/Users/kmo/IdeaProjects/JAVA-DataSt/DataSt/src/main/resources/Log/log-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>

<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR(只有大于等于这个级别的才会被记录) | ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="ALL">
<!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
<appender-ref ref = "CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>

​接着这样在代码中引入logback日志输出器

language-java
1
2
3
4
5
6
7
8
9
10
11
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test{

public static void main(String[] args) {

Logger logger = LoggerFactory.getLogger("");
logger.info("This a info message");
logger.warn("This a warning message");
}
}
JavaSE-IO

JavaSE-IO

IO流总体来看有四大类

  • 字节输入流
  • 字节输出流
  • 字符输入流
  • 字符输出流

IO的体系如下图

需要注意的是字节流适合完整的复制文件之类的操作,而字符流尤其适合纯文本文件。

第一行实现类,都是原始流,性能并不高。

第二行是Buffer缓冲流,自带8KB缓冲池,相当于缓存之于cpu和硬盘的感觉。

第三行是打印流,符合平常的println习惯。

第四行是数据流,适合保存读取基本类型数据,保存后的文件打开是乱码很正常。

第五行是序列流,适合保存读取对象,对象需实现Serializable接口,transient关键字可以设置某变量不参加序列化。

除了原始流,其他的都是高级流,高级流都封装了原始流,并且性能都比较好。

IO框架

Commons-IOhttps://www.w3schools.cn/apache_commons_io/同时Java自己后来也提供了IO框架库。

JavaSE-Stream流

JavaSE-Stream流

从流的出生,经过过滤,排序,去重等各种方法,变成最后的结果,就像水流一样,在代码层面也类似地简单明了,最大的优点是不用自己去保存中间状态,而是可以一直往后操作,直到遇到Stream的终结方法,也像水流一样,Stream流使用后就会改变,一般只用一遍。

1.获取Stream流

一般集合都提供了stream方法,只需要.stream()就可以获取。比如

1
2
Set<String> set = new HashSet<>();
Stream<String> stream = set.stream();

但Map除外,Map用以下方法获取流:

  1. keySet()获取Key的Set后stream或者values()获取Value的Set后stream
  2. entrySet()转化为包含Map.Entry的Set后stream

一般建议用第二种,毕竟Key+Value才是Map的精髓,下面代码里的Entry可以理解为Pair(一对)。

1
2
Map<String, Double> map = new HashMap<>();
Stream<Map.Entry<String, Double>> stream = map.entrySet().stream();

Arrays数组可以通过以下方式获得Stream

  1. Arrays.stream(YourArrays)
  2. Stream.of(YourArrays)

2.常用的Stream的中间方法

filter使用lambda式判断条件作为过滤,例如:

1
2
3
4
5
6
7
8
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 108.8, 60.0, 99.0, 9.5, 99.6, 25.0);
//需求1:找出成绩人于等于60分的数据,并升序后,可输出。
List<Double> list1 = scores.stream()
.filter(s -> s > 60)
.sorted()
.collect(Collectors.toList());
System.out.println(list1);

sorted排序,limit限制数量,skip跳过,distinct去重,都比较简单。其中sorted需要重写排序规则,在集合体系已经讲过,distinct需要重写hashCode和equals,如果是Java自带的基础集合,都不用管这些。

map操作接收一个函数作为参数,支持lambda,比如

1
2
3
4
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5); 
List<String> strList = numList.stream()
.map(n -> n.toString())
.collect(Collectors.toList());

concat合并流,后者接在前者后面,比如

1
2
3
4
Stream<String> stream1 = Stream.of("张三", "李四");
Stream<String> stream2 = Stream.of("张三2", "李四2", "王五");
Stream<String> streamAll = Stream.concat(stream1, stream2);
streamAll.forEach(s -> System.out.println(s));

3.常用的终结方法

collect里面填诸如Collectors.toList(),Collectors.toSet(),to什么就用什么接收结果。

1
2
.collect(Collectors.toList());
.collect(Collectors.toMap(Student::getName, Student::getHeight))