4. 共享模式之管程
4.1 共享带来的问题 - 竞态条件
临界区 : 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件 : 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test19")
public class Test19 {
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count--;
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("count = {}", count);
}
}
结果一 :0
结果二 : 正数
结果三 : 负数
4.2 synchronized 解决竞态条件问题
synchronized - 对象锁 : 保证了临界区内代码的原子性
原子性 : 临界区内的代码运行不会因为线程上下文切换而被打断
// 格式
synchronized (对象){
临界区
}
// 方法上的 synchronized
//情况一 : 实例方法上的 synchronized
public synchronized void method(){
临界区
}
<= 等价于 =>
public void method(){
synchronized (this){
临界区
}
}
//情况二 : 静态方法上的 synchronized
public synchronized static void method(){
临界区
}
<= 等价于 =>
public void method(){
synchronized (类对象){
临界区
}
}
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test20")
public class Test20 {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
room.add();
}, "t1");
Thread t2 = new Thread(() -> {
room.sub();
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("count = {}", room.getCount());
}
}
class Room {
private int count = 0;
public synchronized void add() {
count++;
}
public synchronized void sub() {
count--;
}
public synchronized int getCount() {
return count;
}
}
【注意】synchronized(对象锁)仅保证了临界区内代码的原子性,这并不意味着线程将一直处于运行状态,待该线程的时间片用完,其仍旧会发生线程上下文切换转换为就绪状态,不过是其他线程在与之访问同一对象时会进入阻塞(BLOCKED )状态,从而保证了该线程临界区内代码的原子性。
4.3 线程八锁
4.3.1 情况 1
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test21")
public class Test21 {
public static void main(String[] args) {
Number1 n1 = new Number1();
new Thread(() -> {
n1.a();
}, "t1").start();
new Thread(() -> {
n1.b();
}, "t2").start();
Thread t1;
}
}
@Slf4j(topic = "c.Number1")
class Number1 {
public synchronized void a() {
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis---”代表时间
// t1 与 t2 串行
t1->t2 | t2->t1
【分析】
// t1、t2
synchronized (n1){
}
4.3.2 情况 2
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test22")
public class Test22 {
public static void main(String[] args) {
Number2 n1 = new Number2();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n1.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number2")
class Number2 {
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis--->”代表时间
// t1 与 t2 串行
---1s--->t1->t2 | t2---1s--->t1
【分析】
// t1、t2
synchronized (n1){
}
4.3.3 情况 3
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test23")
public class Test23 {
public static void main(String[] args) {
Number3 n1 = new Number3();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n1.b();
}, "t2").start();
new Thread(() -> {
n1.c();
}, "t3").start();
}
}
@Slf4j(topic = "c.Number3")
class Number3 {
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
public void c() {
log.debug("3");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis---”代表时间
// (t1、t2)与 t3 并行,t1 与 t2 串行
t3 ---1s--->t1->t2 | t3 t2---1s--->t1
【分析】
// t1、t2
synchronized (n1){
}
// t3
无 synchronized
4.3.4 情况 4
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test24")
public class Test24 {
public static void main(String[] args) {
Number4 n1 = new Number4();
Number4 n2 = new Number4();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n2.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number4")
class Number4 {
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis---”代表时间
// t1 与 t2 并行
t2 ---1s--->t1
【分析】
// t1
synchronized (n1){
}
// t1
synchronized (n2){
}
4.3.5 情况 5
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test25")
public class Test25 {
public static void main(String[] args) {
Number5 n1 = new Number5();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n1.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number5")
class Number5 {
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis---”代表时间
// t1 与 t2 并行
t2 ---1s--->t1
【分析】
// t1
synchronized (Number5 类对象){
}
// t1
synchronized (n1){
}
4.3.6 情况 6
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test26")
public class Test26 {
public static void main(String[] args) {
Number6 n1 = new Number6();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n1.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number")
class Number6 {
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis--->”代表时间
// t1 与 t2 串行
---1s--->t1->t2 | t2---1s--->t1
【分析】
// t1、t2
synchronized (Number6 类对象){
}
4.6.7 情况 7
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test27")
public class Test27 {
public static void main(String[] args) {
Number7 n1 = new Number7();
Number7 n2 = new Number7();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n2.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number7")
class Number7 {
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis---”代表时间
// t1 与 t2 并行
t2 ---1s--->t1
【分析】
// t1
synchronized (Number7 类对象){
}
// t1
synchronized (n2){
}
4.6.8 情况 8
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test28")
public class Test28 {
public static void main(String[] args) {
Number8 n1 = new Number8();
Number8 n2 = new Number8();
new Thread(() -> {
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(() -> {
n2.b();
}, "t2").start();
}
}
@Slf4j(topic = "c.Number8")
class Number8 {
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public static synchronized void b() {
log.debug("2");
}
}
【结果】
//“ ”[空格] 代表并行,“->”代表串行,“---millis--->”代表时间
// t1 与 t2 串行
---1s--->t1->t2 | t2---1s--->t1
【分析】
// t1、t2
synchronized (Number8 类对象){
}
4.4 变量的线程安全分析
【草稿纸】
草稿纸中的内容极易出现错误,该板块的出现代表博主没整明白,需通过草稿纸整理思路
变量 : 成员变量、静态变量、局部变量
成员变量、静态变量 局部变量 线程安全 只读操作 基本类型
引用类型 - 其作用域仅为其所处的代码块中
非线程安全 读写操作 引用类型 - 其作用域跳出了其所处的代码块 【举个栗子】外星方法
外星方法 :子类重写父类方法后为线程安全带来了不确定性
解决方法 : 使用 private | final 修饰方法
线程安全类 :
String
包装类(如 Integer)
StringBuffer
Vector
Hashtable
java.util.concurrent 包下的类
线程安全类的每个方法单独使用都是线程安全的,但组合使用却充满着不确定性。
String 和 包装类(如 Integer)是不可变类
【加强版草稿纸】
适用于线程共享条件下(线程私有条件下不存在线程安全问题,无需考虑)
变量有被修改吗?: 有 -> 非线程安全 | 没有 -> 线程安全
实例分析
在分析下列实例前,你必须清楚单例意味着多个线程调用同一个堆对象,存在线程共享
例 1
public class MyServlet extends HttpServlet {
// 是否安全?
Map<String, Object> map = new HashMap<>();
// 是否安全?
String S1 = "...";
// 是否安全?
final String S2 = "...";
// 是否安全?
Date D1 = new Date();
// 是否安全?
final Date D2 = new Date();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
// 使用上述变量
}
}
【分析】
HttpServlet -> Tomcat -> 单例
Map<String, Object> map = new HashMap<>(); //非线程安全
HashMap -> 非线程安全类
String S1 = "..."; //线程安全
String -> 线程安全类
final String S2 = "..."; //线程安全
String -> 线程安全类
Date D1 = new Date(); //非线程安全
Date -> 非线程安全类
final Date D2 = new Date(); //非线程安全
Date -> 非线程安全类
【999】final 关键字修饰引用类型变量时,内存地址(变量地址)不可更改,但变量值可以改变
例 2
public class MyServlet extends HttpServlet {
// 是否安全?
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 记录调用次数
private int count = 0;
public void update() {
// ...
count++;
}
}
【分析】
HttpServlet -> Tomcat -> 单例
private UserService userService = new UserServiceImpl(); //非线程安全
userService -> 成员变量
[
userService.update(...);
public void update() {
// ...
count++;
}] -> 读写操作
成员变量、读写操作 -> 非线程安全
【加强版分析】
HttpServlet -> Tomcat -> 单例
private UserService userService = new UserServiceImpl(); //非线程安全
UserService 中的变量有被修改吗?
UserService 中的变量有 count
[
userService.update(...);
public void update() {
// ...
count++;
}] -> 修改了 count 的值
-> 非线程安全
例 3
@Aspect
@Component
public class MyAspect {
// 是否安全?
private long start = 0L;
@Before("execution(* *(..))")
public void before() {
start = System.nanoTime();
}
@After("execution(* *(..))")
public void after() {
long end = System.nanoTime();
System.out.println("cost time:" + (end - start));
}
}
【解析】
private long start = 0L; // 非线程安全
start -> 成员变量
[
start = System.nanoTime();
System.out.println("cost time:" + (end - start));
] -> 读写操作
成员变量、读写操作 -> 非线程安全
【加强版解析】
private long start = 0L; // 非线程安全
start 有被修改吗?
start = System.nanoTime(); -> 修改了 start 的值
-> 非线程安全
例 4
public class MyServlet extends HttpServlet {
// 是否安全
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
// 是否安全
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
public void update() {
String sql = "update user set password = ? where username = ?";
// 是否安全
try (Connection conn = DriverManager.getConnection("", "", "")) {
// ...
} catch (Exception e) {
// ...
}
}
}
【解析】
HttpServlet -> Tomcat -> 单例
Connection conn = DriverManager.getConnection("", "", "") // 线程安全
conn -> 方法参数 -> (当做)局部变量
且其作用域仅为所处的代码块中 -> 线程安全
private UserDao userDao = new UserDaoImpl(); //线程安全
userDao -> 成员变量
且未发生读写操作 -> 线程安全
private UserService userService = new UserServiceImpl(); // 线程安全
userService -> 成员变量
且未发生读写操作 -> 线程安全
【???】也许你会有疑惑
例 2 中 userService.update(...); 发生了读写操作,例 4 中 userService.update(...); 未发生读写操作?
例 2
[userService.update(...);] 中执行了 [count++;] 修改了 UserServiceImpl.class 的成员变量 count 的值,修改操作即读写操作,若线程运行过程中发生线程上下文切换,会导致竞态条件的出现,故而该线程不安全
例 4
[userService.update(...);] 中执行了 [userDao.update();]
[userDao.update();] 中执行了
[
public void update() {
String sql = "update user set password = ? where username = ?";
try (Connection conn = DriverManager.getConnection("", "", "")) {
// ...
} catch (Exception e) {
// ...
}
}]
未做任何修改,故而线程安全
【加强版解析】
HttpServlet -> Tomcat -> 单例
private UserService userService = new UserServiceImpl(); // 线程安全
UserService 中的变量有被修改吗?
UserService 中的变量有 userDao
userDao有被修改吗?
没有 -> 线程安全
private UserDao userDao = new UserDaoImpl(); //线程安全
UserService 中的变量有被修改吗?
UserService 中的变量有 sql、conn
sql、conn有被修改吗?
没有 -> 线程安全
Connection conn = DriverManager.getConnection("", "", "") // 线程安全
conn 有被修改吗?
没有 -> 线程安全
例 5
public class MyServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
public void update() {
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全
private Connection conn = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("", "", "");
// ...
conn.close();
}
}
【解析】
HttpServlet -> Tomcat -> 单例
private Connection conn = null; // 非线程安全
conn -> 成员变量
conn = DriverManager.getConnection("", "", ""); -> 读写操作
成员变量、读写操作 -> 非线程安全
【加强版解析】
HttpServlet -> Tomcat -> 单例
private Connection conn = null; // 非线程安全
conn 有被修改吗?
conn = DriverManager.getConnection("", "", ""); -> 修改了 conn 的值
-> 非线程安全
例 6
public class MyServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
public void doGet(HttpServletRequest request, HttpServletResponse response) {
userService.update(...);
}
}
public class UserServiceImpl implements UserService {
public void update() {
UserDao userDao = new UserDaoImpl();
userDao.update();
}
}
public class UserDaoImpl implements UserDao {
// 是否安全
private Connection conn = null;
public void update() throws SQLException {
String sql = "update user set password = ? where username = ?";
conn = DriverManager.getConnection("", "", "");
// ...
conn.close();
}
}
【解析】
HttpServlet -> Tomcat -> 单例
private Connection conn = null; // 线程安全
【???】你可能又要问了
例 5 与 例 6 中 UserDaoImpl 类的代码可是一模一样的,为什么 例 5 中 conn 变量是非线程安全,到 例 6 这里就是线程安全的了呢?
关键点在于 例 6 中 UserServiceImpl 类 的 update 方法
[ UserDao userDao = new UserDaoImpl(); userDao.update(); ]
这两条代码就意味着 : 多个线程调用了不同的 UserDao 对象,不存在线程共享,也就不会发生线程安全问题。
【加强版解析】
HttpServlet -> Tomcat -> 单例
private UserService userService = new UserServiceImpl(); // 线程安全
UserService 中的变量有被修改吗?
UserService 中的变量有 userDao
userDao有被修改吗?
没有 -> 线程安全
private UserDao userDao = new UserDaoImpl(); //线程安全
Connection conn = DriverManager.getConnection("", "", "") // 线程安全
[ UserDao userDao = new UserDaoImpl(); userDao.update(); ]
这两条代码就意味着 : 多个线程调用了不同的 UserDao 对象,不存在线程共享,也就不会发生线程安全问题。
例 7
public abstract class Test {
public void bar() {
// 是否安全
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
foo(sdf);
}
public abstract foo(SimpleDateFormat sdf);
public static void main(String[] args) {
new Test().bar();
}
}
【解析】
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 非线程安全
public abstract foo(SimpleDateFormat sdf); -> 抽象方法会发生重写,充满了不确定性(外星方法)
// 例如
public void foo(SimpleDateFormat sdf) {
String dateStr = "1999-10-11 00:00:00";
for (int i = 0; i < 20; i++) {
new Thread(() -> {
try {
sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
}
卖票练习
测试下面代码是否存在线程安全问题,并尝试改正
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
log.debug("selled count:{}", sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
log.debug("remainder count:{}", ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
【爆破】
1.找出 ExerciseSell 类中的全部变量
ticketWindow、list、sellCount、i、t、count、random
2.区分多线程私有与多线程共享的变量,找出多线程共享的变量
[
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});]
ticketWindow、count、sellCount
3.判断是否线程安全
① sellCount - 线程安全
List<Integer> sellCount = new Vector<>();
Vector - 线程安全类
② count - 线程安全
count 有被修改吗?
没有 -> 线程安全
③ ticketWindow - 非线程安全
TicketWindow 中的变量有被修改吗?
TicketWindow 中的变量有 count、amount
amount 有被修改吗?
没有 -> 线程安全
count 有被修改吗?
[
public int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}] -> 修改了 count 的值
-> 非线程安全
4.非线程安全 -> 线程安全
synchronized - 对象锁
public synchronized int sell(int amount) {
}
【???】头大吗?博主挺头大的
或许你开始好奇如何判断变量是否被修改
暴力一些,若代码中对变量的赋值次数大于1就认为该变量被修改
当然,也会存在一点小小的 bug
比如一开始仅仅是声明了变量而没有为其赋值,那么代码中对变量的赋值次数大于0就要认为该变量被修改了
声明变量 i :int i; // 采用默认值
初始化变量 i :int i = 1; // 声明 + 赋值
package com.rui.test;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
public static void main(String[] args) {
TicketWindow ticketWindow = new TicketWindow(2000);
List<Thread> list = new ArrayList<>();
// 用来存储买出去多少张票
List<Integer> sellCount = new Vector<>();
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
// 分析这里的竞态条件
int count = ticketWindow.sell(randomAmount());
sellCount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 买出去的票求和
log.debug("selled count:{}", sellCount.stream().mapToInt(c -> c).sum());
// 剩余票数
log.debug("remainder count:{}", ticketWindow.getCount());
}
// Random 为线程安全
static Random random = new Random();
// 随机 1~5
public static int randomAmount() {
return random.nextInt(5) + 1;
}
}
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public synchronized int sell(int amount) {
if (this.count >= amount) {
this.count -= amount;
return amount;
} else {
return 0;
}
}
}
4.5 锁状态
synchronized 蛮影响性能的,若线程安全时建议使用无锁状态
偏向锁、轻量级锁、重量级锁
偏向锁适用于无竞争的条件下;轻量级锁适用于良性竞争的条件下;重量级锁适用于恶性竞争的条件下
4.5.1 查看锁状态
配置
第一步
java修改第三方jar包中的代码_如何修改jar包中的代码_糖果墙的博客-CSDN博客
【郑重声明】该链接来自大佬 糖果墙的博客-CSDN博客,侵删
第一步 :[ pom.xml ]
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
代码
ClassLayout.parseInstance(对象).toPrintableSimple())
4.5.2 偏向锁
偏向锁是默认开启的,但有延迟
// 禁用偏向锁
-XX:-UseBiasedLocking
// 解决偏向锁延迟问题
-XX:BiasedLockingStartupDelay=0
上述代码怎么设置?
第一步
第二步
Mark Word 二进制下后三位为 101
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
new Thread(() -> {
log.debug("{}", ClassLayout.parseInstance(new Dog()).toPrintableSimple());
}).start();
}
}
class Dog {
}
4.5.3 轻量级锁
对象的锁状态为 00(Mark Word 二进制下后两位为 00)
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
Dog d = new Dog();
ClassLayout cl = ClassLayout.parseInstance(d);
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug("{}", cl.toPrintableSimple());
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("{}", cl.toPrintableSimple());
}
}, "t2");
t1.start();
t2.start();
}
}
class Dog {
}
4.5.4 重量级锁
对象的锁状态为 10(Mark Word 二进制下后两位为 10)
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import org.openjdk.jol.info.ClassLayout;
@Slf4j(topic = "c.Test3")
public class Test3 {
public static void main(String[] args) {
Dog d = new Dog();
ClassLayout cl = ClassLayout.parseInstance(d);
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug("{}", cl.toPrintableSimple());
}
}, "t1");
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("{}", cl.toPrintableSimple());
}
}, "t2");
t1.start();
t2.start();
}
}
class Dog {
}
4.5.5 锁状态间转换
1. 在良性竞争条件下,若某一线程偏向锁中的锁对象被另一线程访问,偏向锁升级为轻量级锁
轻量级锁释放后转为无锁状态
2. 在良性竞争条件下,若某一线程轻量级锁中的锁对象被另一线程访问,仍是轻量级锁状态
3. 在恶性竞争条件下,若某一线程访问另一线程 偏向锁 | 轻量级锁 中的锁对象,该线程会先进行自旋优化,若自旋优化失败,偏向锁 | 轻量级锁 升级为重量级锁
自旋优化会占用 CPU 的时间
自旋优化
【文字】
【瞎写】
// Mark Word 是关键点,后面要考的
通过 Mark Word 二进制下后两位(锁状态)判断锁状态
01 - 无锁 | 偏向锁 00 - 轻量级锁 10 - 重量级锁
通过 biased_lock 判断是否为偏向锁
0 - 无锁 1 - 偏向锁
// 偏向锁 锁对象 对象头 Mark Word 中 ThreadID 和 二进制下后三位 101 是关键点
// 轻量级锁 锁记录 指向 锁对象的地址 和 锁记录地址和锁状态 00 是关键点
// 重量级锁 Monitor锁 Owner 和 EntryList 是关键点【暂时】
对象创建时默认开启偏向锁
// 若为无锁状态,用 ThreadID 替换 [ 锁对象 对象头 Mark Word ](简写 Mark Word)
若为偏向锁状态,通过 ThreadID 判断是否为本线程是 -> 无操作
不是(良性竞争) -> CAS(让 锁记录 中的 锁记录地址和锁状态 00 替换 锁对象 的 Mark Word),升级为轻量级锁
不是(恶性竞争) -> 自旋优化 -> 失败 -> 申请 Monitor 锁,Owner 设为 该线程,升级为重量级锁
【不靠谱】自旋优化成功应该是升级为轻量级锁吧
// 偏向锁在发生竞争时释放资源
若为轻量级锁状态,通过 Mark Word(锁记录地址和锁状态 00)判断是否为本线程
是(锁重入) -> 添加一条取值为 null 的锁记录用于计数
不是(锁膨胀) -> 申请 Monitor 锁,Owner 设为该线程,升级为重量级锁
若为重量级锁状态,通过 Monitor 锁中的 Owner 值是否为 null 判断是否为本线程
是 -> 无操作
不是 -> 进入 EntryList (BLOCEKED 阻塞状态)
线程 通过 锁对象 访问 其 Monitor 锁中的 Owner。若为空,将 Owner 设为该线程;若不为空,进入 EntryList.
待 Owner 中的线程释放时,将 Owner 设为 null,并唤醒 EntryList 中的线程.
【图 - 非图解】
轻量级锁
重量级锁
轻量级锁 升级为 重量级锁
4.5.6 撤销偏向
【俺的理解,十分不靠谱】
撤销偏向,即修改偏向锁状态
1. 调用 对象 hashcode 【转换为无锁状态】
偏向锁 -> 锁对象 -> 对象头 -> Mark Word 没有 hashcode
2. 其他线程使用对象 【转换为 轻量级锁|重量级锁 状态】
3. 调用 wait/notify【还没学,学完会回来补解释的】
4.5.7 撤销重偏向
当撤销偏向锁次数超过 20 次后,会修改锁对象对应偏向锁中的 ThreadID
4.5.8 批量撤销
接 【4.5.7 撤销重偏向】
当撤销偏向锁次数超过 40 次后,会禁用偏向锁
说些废话
本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!