Java并发编程实战:第2章 线程安全性

1. 什么是线程安全性

下载资源 jar;https://repo1.maven.org/maven2/org/lucee/jcip-annotations/1.0.0/jcip-annotations-1.0.0.jar

先创建Java Web Module

File -> New -> Module...

Java -> Next -> Finish

选中创建的 Model -> 右键选择 Add Framework Support... -> 选择 Web Application -> ok

配置 Tomcat

Run -> Edit Configurations... -> Add New Configuration -> Tomcat Server -> Local

添加 Artifacts

刚刚添加的 Tomcat 配置 -> Deployment -> Deploy at the server starup -> Add -> Artifact... -> ok

添加 tomcat 相关的 jar

File -> Project Structure -> Modules -> 选中创建的 Model -> Dependencies -> Add -> Library... -> Application Server Libraries -> Tomcat *** -> Add Selected -> ok

1.1 一个无状态的 Servlet

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
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;

/**
* Created by osys on 2022/08/28 21:48.
*/
public class StatelessFactories extends GenericServlet implements Servlet {

@Override
public void init(ServletConfig servletConfig) throws ServletException { }

@Override
public ServletConfig getServletConfig() { return null; }

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
BigInteger[] factories = factor(i);
encodeIntoResponse(servletResponse, factories);

System.out.println("Input:" + servletRequest.getParameter("demo"));
System.out.println("i=" + i.toString());
System.out.println("factories=" + Arrays.toString(factories));
}

@Override
public String getServletInfo() { return null; }

@Override
public void destroy() { }

void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { }

BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}

BigInteger[] factor(BigInteger i) {
return new BigInteger[] { i };
}

}

web.xml 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 配置和映射servlet -->
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>demoServlet</servlet-name>
<!-- servlet的全类名-->
<servlet-class>StatelessFactories</servlet-class>
</servlet>

<servlet-mapping>
<!-- 需要和某一个servlet节点的servlet子节点的文本节点一致 -->
<servlet-name>demoServlet</servlet-name>
<!-- 映射具体 的访问路径: / 代表当前web应用的根目录 -->
<url-pattern>/Demo</url-pattern>
</servlet-mapping>
</web-app>

index.jsp 代码:

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
<%--
Created by IntelliJ IDEA.
User: osys
Date: 2022/3/25
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试</title>
</head>
<body>
<form name="f1" id="f1" action="${pageContext.request.contextPath}/Demo" method="post">
<table>
<tr>
<td>创建数组:</td>
<td><label for="demo"></label><input type="text" name="demo" id="demo"></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>
</body>
</html>

运行 Tomcat,访问 http://localhost:8080/

随意输入 1234 并提交,页面跳转到 http://localhost:8080/Demo

控制台 output:

1
2
3
Input:1234
i=7
factories=[7]

访问 StatelessFactories 的线程不会影响另一个访问同一个 StatelessFactories 的线程的计算结果,因为两个线程并没有共享状态。

由于线程访问无状态对象的行为并不会影响其他线程中操作的正确性,因此无状态对象是线程安全的。

说明:

  • 有状态对象(Stateful Bean):有数据存储功能。是有实例变量的对象,可以保存数据,是 非线程安全 的。在不同方法调用间不保留任何状态。
  • 无状态对象(Stateless Bean):一次操作,不能保存数据。是没有实例变量的对象,不能保存数据,是不变类,是 线程安全 的。

2. 原子性

原子性:原子性是指操作是不可分的。如 count++ 涉及 读取-修改-写入 的操作序列,因此它并不是一个不可分割的操作。

竞态条件(Race Condition) :计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。

2.1 常见竞态条件

  1. 先检测后执行

    执行依赖于检测的结果,而检测结果依赖于多个线程的执行时序,而多个线程的执行时序通常情况下是不固定不可判断的,从而导致执行结果出现各种问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    例如:如果不存在 test.txt 文件,创建一个 test.txt 文件

    ---- 不存在 test.txt 文件 --------


    A 进程发现文件 test.txt 不存在
    同时,B 程也发现 文件 test.txt 不存在


    于是 A进程,先创建 test.txt 文件
    不过 B 进程不知道 A 进程以及 创建了 test.txt 文件


    最后 B 进程创建 test.txt 文件
    将 A 进程创建的 test.txt 文件给替换掉了
  2. 延迟初始化

    延迟初始化,是将对象的初始化操作推迟到实际使用时才进行。同时确保只被初始化一次

2.2 在没有同步的情况下统计已处理请求数量的 Servlet

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
import net.jcip.annotations.NotThreadSafe;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;

/**
* Created by osys on 2022/08/28 21:48.
*/
@NotThreadSafe
public class UnsafeCountingFactories implements Servlet {

private long count = 0;

@Override
public void init(ServletConfig servletConfig) throws ServletException {

}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
BigInteger[] factories = factor(i);
++count;
encodeIntoResponse(servletResponse, factories);

System.out.println("Input:" + servletRequest.getParameter("demo"));
System.out.println("i=" + i.toString());
System.out.println("factories=" + Arrays.toString(factories));
System.out.println(count);
}

public long getCount() {
return count;
}

void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { }

public BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}

public BigInteger[] factor(BigInteger i) {
return new BigInteger[] { i };
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

index.jsp

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
<%--
Created by IntelliJ IDEA.
User: osys
Date: 2022/3/25
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试</title>
</head>
<body>
<form name="f1" id="f2" action="${pageContext.request.contextPath}/Demo2" method="post">
<table>
<tr>
<td>创建数组:</td>
<td><label for="demo2"></label><input type="text" name="demo2" id="demo2"></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>
</body>
</html>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<servlet>
<!-- servlet注册的名字 -->
<servlet-name>demoServlet2</servlet-name>
<!-- servlet的全类名-->
<servlet-class>UnsafeCountingFactories</servlet-class>
</servlet>

<servlet-mapping>
<!-- 需要和某一个servlet节点的servlet子节点的文本节点一致 -->
<servlet-name>demoServlet2</servlet-name>
<!-- 映射具体 的访问路径: / 代表当前web应用的根目录 -->
<url-pattern>/Demo2</url-pattern>
</servlet-mapping>
</web-app>

运行 Tomcat,访问 http://localhost:8080/

多个页面同时进行 —- 随意输入 1234 并提交,页面跳转到 http://localhost:8080/Demo

线程不安全。

2.3 延迟初始化中的竞态条件

延迟初始化 —- 先检查后执行

对于两个类

1
2
3
4
5
6
7
8
9
10
11
12
13
@NotThreadSafe
public class LazyInitRace {

private ExpensiveObject instance = null;

public ExpensiveObject getInstance() {
return instance;
}

public void setInstance(ExpensiveObject instance) {
instance = instance;
}
}
1
2
3
4
5
public class ExpensiveObject {
public ExpensiveObject() {
System.out.println(Thread.currentThread().getName() + "----------------------- 创建了一个 ExpensiveObject 对象");
}
}

实现 Runnable 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static class MyThread implements Runnable {

private LazyInitRace lazyInitRace;

public MyThread(LazyInitRace lazyInitRace) {
this.lazyInitRace = lazyInitRace;
}

@Override
public void run() {
// 如果 ExpensiveObject == null,那么创建 ExpensiveObject 对象
if (lazyInitRace.getInstance() == null) {
lazyInitRace.setInstance(new ExpensiveObject());
}
}
}

创建多线程

1
2
3
4
5
6
7
8
9
10
11
public class LazyInitRaceDemo {
private static final MyThread thread = new MyThread(new LazyInitRace());

public static void main(String[] args) {
// 启动两个线程
Thread myThread1 = new Thread(thread, "线程1");
Thread myThread2 = new Thread(thread, "线程2");
myThread1.start();
myThread2.start();
}
}

Debug:

执行 start()

1 2

对于两个不同的进程,指向的都是同一个 LazyInitRaceDemo$MyThread ,即对应的 lazyInitRace 为同一个,此时 lazyInitRace 里面的成员变量 instance = null

Output

1
2
线程1----------------------- 创建一个 ExpensiveObject 对象
线程2----------------------- 创建一个 ExpensiveObject 对象

说明:

start(),线程1发现 instance = null,于是进行 lazyInitRace.setInstance(new ExpensiveObject());

同时,start(),线程2也发现 instance = null,于是也进行 lazyInitRace.setInstance(new ExpensiveObject());

2.4 避免竞态条件问题

要避免这个问题,就必须在某个线程修改改变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态过程中。

使用 AtomicLong 类型的变量,来统计已处理请求的数量:

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
import net.jcip.annotations.ThreadSafe;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;

/**
* Created by osys on 2022/08/28 21:48.
*/
@ThreadSafe
public class CountingFactories implements Servlet {

private final AtomicLong count = new AtomicLong(0);

public long getCount() {
return count.get();
}

void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) { }

public BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}

public BigInteger[] factor(BigInteger i) {
return new BigInteger[] { i };
}

@Override
public void init(ServletConfig servletConfig) throws ServletException {
}

@Override
public ServletConfig getServletConfig() {
return null;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
BigInteger[] factories = factor(i);
count.incrementAndGet();
encodeIntoResponse(servletResponse, factories);

System.out.println("Input:" + servletRequest.getParameter("demo"));
System.out.println("i=" + i.toString());
System.out.println("factories=" + Arrays.toString(factories));
System.out.println(count);
}

@Override
public String getServletInfo() {
return null;
}

@Override
public void destroy() {

}
}

index.jsp

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
<%--
Created by IntelliJ IDEA.
User: osys
Date: 2022/3/25
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试</title>
</head>
<body>
<form name="f3" id="f3" action="${pageContext.request.contextPath}/Demo3" method="post">
<table>
<tr>
<td>创建数组:</td>
<td><label for="demo3"></label><input type="text" name="demo3" id="demo3"></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>
</body>
</html>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>demoServlet3</servlet-name>
<!-- servlet的全类名-->
<servlet-class>CountingFactories</servlet-class>
</servlet>
<servlet-mapping>
<!-- 需要和某一个servlet节点的servlet子节点的文本节点一致 -->
<servlet-name>demoServlet3</servlet-name>
<!-- 映射具体 的访问路径: / 代表当前web应用的根目录 -->
<url-pattern>/Demo3</url-pattern>
</servlet-mapping>
</web-app>

运行 Tomcat,访问 http://localhost:8080/

多个页面同时进行 —- 随意输入 1234 并提交,页面跳转到 http://localhost:8080/Demo

线程安全。

通过使用 AtomicLong 来代替 long 类型的计数器,能够确保所有对计数器状态的访问操作都是原子的。

3. 加锁机制

3.1 程序 2-5

在没有足够原子性保证的情况下对其最近计算结果进行缓存

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
import net.jcip.annotations.NotThreadSafe;

import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

/**
* Created by osys on 2022/08/28 21:48.
*/
@NotThreadSafe
public class UnsafeCachingFactories extends GenericServlet implements Servlet {

private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();

private final AtomicReference<BigInteger[]> lastFactor = new AtomicReference<>();

@Override
public void init(ServletConfig servletConfig) throws ServletException { }

@Override
public ServletConfig getServletConfig() {return null;}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
if (lastNumber.get().equals(i)) {
encodeIntoResponse(servletResponse, lastFactor.get());
} else {
BigInteger[] factors = factor(i);
lastNumber.set(i);
lastFactor.set(factors);
encodeIntoResponse(servletResponse, factors);
}

System.out.println(lastNumber.get() + "-------------------------");
System.out.println(Arrays.toString(lastFactor.get()) + "-------------------------");
}

void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
}

BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}

BigInteger[] factor(BigInteger i) {
return new BigInteger[]{i};
}

@Override
public String getServletInfo() {return null;}

@Override
public void destroy() {}
}

index.jsp

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
<%--
Created by IntelliJ IDEA.
User: osys
Date: 2022/3/25
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试</title>
</head>
<body>
<form name="f4" id="f4" action="${pageContext.request.contextPath}/Demo4" method="post">
<table>
<tr>
<td>创建数组:</td>
<td><label for="demo4"></label><input type="text" name="demo4" id="demo4"></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>
</body>
</html>

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>demoServlet4</servlet-name>
<!-- servlet的全类名-->
<servlet-class>UnsafeCachingFactories</servlet-class>
</servlet>
<servlet-mapping>
<!-- 需要和某一个servlet节点的servlet子节点的文本节点一致 -->
<servlet-name>demoServlet4</servlet-name>
<!-- 映射具体 的访问路径: / 代表当前web应用的根目录 -->
<url-pattern>/Demo4</url-pattern>
</servlet-mapping>
</web-app>

程序中,有这么一段代码:

1
2
3
4
5
6
7
8
private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();
private final AtomicReference<BigInteger[]> lastFactor = new AtomicReference<>();

......

BigInteger[] factors = factor(i);
lastNumber.set(i);
lastFactor.set(factors);

对于 lastNumberlastFactor ,这些原子引用本身是线程安全的。

不过 lastNumber 又做为 lastFactor 的基数。这是线程不安全的。

简单的理解:

lastNumber.set(i);lastFactor.set(factors); 两个无法同时进行。即是可拆分的。因此对于 UnsafeCachingFactories 是非原子的。线程不安全。

如果只对其中一个值修改,那么在两次修改操作之间,其他线程将发现不变性条件被破坏了。

同样,我们也不能保证会同时获取两个值:在线程A获取这两个值的过程中,线程B修改了它们,这样线程A也会发现不变性条件被破坏了。

3.2 内置锁(互斥锁)

Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)

同步代码块包含两个部分:

  • 锁的对象引用
  • 锁保护的代码块

线程进入同步代码块之前,会自动获得锁,并且在退出同步代码块时,自动释放锁。

同步代码块的锁是,方法调用所在的对象。静态同步代码块,则以Class对象作为锁。

能正确地缓存最新的计算记过,但并发性却非常糟糕:

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
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;

/**
* Created by osys on 2022/08/28 21:48.
*/
@ThreadSafe
public class SynchronizedFactories implements Servlet {

@GuardedBy("this")
private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();

@GuardedBy("this")
private final AtomicReference<BigInteger[]> lastFactor = new AtomicReference<>();

@Override
public void init(ServletConfig servletConfig) throws ServletException {}

@Override
public ServletConfig getServletConfig() {return null;}

@Override
public synchronized void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
if (!lastNumber.get().equals(i)) {
BigInteger[] factors = factor(i);
lastNumber.set(i);
lastFactor.set(factors);
encodeIntoResponse(servletResponse, factors);
} else {
encodeIntoResponse(servletResponse, lastFactor.get());
}

System.out.println(lastNumber.get() + "-----------------------");
System.out.println(Arrays.toString(lastFactor.get()) + "-----------------------");
}

void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
}

BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}

BigInteger[] factor(BigInteger i) {
return new BigInteger[]{i};
}

@Override
public String getServletInfo() {return null;}

@Override
public void destroy() {}
}

index.jsp:

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
<%--
Created by IntelliJ IDEA.
User: osys
Date: 2022/3/25
Time: 16:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试</title>
</head>
<body>
<form name="f5" id="f5" action="${pageContext.request.contextPath}/Demo5" method="post">
<table>
<tr>
<td>创建数组:</td>
<td><label for="demo5"></label><input type="text" name="demo5" id="demo5"></td>
</tr>
<tr>
<td colspan="2"><input type="submit"></td>
</tr>
</table>
</form>
</body>
</html>

web.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<!-- servlet注册的名字 -->
<servlet-name>demoServlet5</servlet-name>
<!-- servlet的全类名-->
<servlet-class>SynchronizedFactories</servlet-class>
</servlet>
<servlet-mapping>
<!-- 需要和某一个servlet节点的servlet子节点的文本节点一致 -->
<servlet-name>demoServlet5</servlet-name>
<!-- 映射具体 的访问路径: / 代表当前web应用的根目录 -->
<url-pattern>/Demo5</url-pattern>
</servlet-mapping>
</web-app>

3.3 重入

内置锁时可重入的。针对 某个线程,可以在 该线程 未释放 该锁 的同时,获取同 该锁 多次。

如果某个线程试图获得一个已经被自己持有的锁,那么这个请求就会成功。

重入的一种实现方式:

  1. 每个所关联一个 获取计数值 和 一个 所有者线程
  2. 当计数为 0 时,没有被任何线程持有。
  3. 当线程请求一个未被持有的锁时,JVM记录下锁的持有者,并且将获取计数值设置为1。
  4. 如果同一个线程再次获取这个锁,计数值将递增。
  5. 反之,当线程退出同步代码块时,计数器会相应的递减。
  6. 当计数值为 0 时,这个锁将被释放。

如果内置锁是不可重入的,那么这段代码将发生死锁

java

1
2
3
4
5
6
7
8
9
10
11
import net.jcip.annotations.ThreadSafe;

/**
* Created by osys on 2022/08/28 21:48.
*/
@ThreadSafe
public class Widget {
public synchronized void doSomething() {
System.out.println("父类,做某些事");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
import net.jcip.annotations.ThreadSafe;

/**
* Created by osys on 2022/08/28 21:48.
*/
@ThreadSafe
public class LoggingWidget extends Widget {
@Override
public synchronized void doSomething() {
System.out.println("子类,做某些事");
super.doSomething();
}
}
  1. 这里 子类 重写了 父类 的方法,然后又调用了 父类 的方法。
  2. 如果没有可重入的锁:
    • 每次执行 LoggingWidget.doSomething() 前,都会去获取 Widget 上的锁。
    • 然而(如果)锁不可重入,那么在调用 super.doSomething() 时,将无法获得 Widget 上的锁,因为这个锁已经被持有了
    • 因此,线程会永远停顿下去,等待一个永远也无法获得的锁。
  3. 重入避免了这周情况的发生。

Java并发编程实战:第2章 线程安全性

https://osys.github.io/posts/406e.html

作者

Osys

发布于

2022年08月29日

更新于

2022年08月29日

许可协议

评论