【记录】并发编程 - 学习日志(二)

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 次后,会禁用偏向锁

说些废话

本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值