JUC学习笔记——共享模型之管程

乎语百科 222 0

JUC学习笔记——共享模型之管程

在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的管程部分

我们会分为以下几部分进行介绍:

  • 共享问题
  • 共享问题解决方案
  • 线程安全分析
  • Monitor
  • synchronized锁
  • Wait/notify
  • 模式之保护性暂停
  • 模式之生产者消费者
  • park
  • 线程状态转换详解
  • 多锁操作
  • 活跃性
  • ReentrantLock
  • 同步模式之顺序控制

共享问题

这小节我们将会介绍共享问题

共享问题概述

我们首先来简单介绍一下贡献问题的产生原因:

  • 操作系统目前只操纵一个CPU单位(单核CPU)
  • 但是有两个线程都需要CPU来运行程序,所以操作系统采用时间片分配CPU
  • 假设一个线程负责i++,一个线程负责i--,但我们需要注意共享数据的存放不是在线程中而是在内存里
  • 假设一个线程取到数据,并进行i++操作之后,但并未将数据放入时,发生了上下文转换,这时另一个线程完成了i--操作
  • 这时另一个线程的操作结果为0-1:-1,结果这个线程继续操作,将计算后的数据直接放入,结果变为了1,结果错误引发共享问题

实际代码体现

我们采用实际代码给出示例:

// 针对counter,我们一个线程++,一个线程--各运行5000次
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter++;
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            counter--;
        }
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("{}",counter);
}

// 但结果却不是0,经常为-5000~5000之间的数

我们可以从底层代码分析问题:

/*i++底层代码*/ 

getstatic i // 获取静态变量i的值
iconst_1 	// 准备常量1
iadd 		// 自增
putstatic i // 将修改后的值存入静态变量i

/*i--底层代码*/ 

getstatic i // 获取静态变量i的值
iconst_1 	// 准备常量1
isub 		// 自减
putstatic i // 将修改后的值存入静态变量i

我们会发现他们的底层代码并不是一步实现,而是多步操作一同实现

在单线程下,按照正常顺序实现自然不会出错:

JUC学习笔记——共享模型之管程

但是如果是多线程,就会因为上下文切换的缘由导致部分步骤出现交杂(我们给出正数示例):

JUC学习笔记——共享模型之管程

临界区和竞态条件

首先我们来简单介绍一下临界区:

  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
  • 例如我们上述共享问题中的i就是共享资源,而对i操作的i++和i--操作都可以被称为临界区

针对临界区我们需要注意以下内容:

  • 一个程序运行多个线程本身是没有问题的
  • 多个线程读共享资源其实也没有问题
  • 但是在多个线程对共享资源读写操作时发生指令交错,就会出现问题

然后我们再来介绍一下竞态条件:

  • 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

共享问题解决方案

这小节我们将会介绍共享问题解决方案

共享问题解决方案总述

我们的共享问题主要采用以下两种方案解决:

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

synchronized简述

首先我们简单介绍一下synchronized:

  • 俗称的【对象锁】,采用互斥的方式使目前至多只有一个线程能持有【对象锁】其它线程再想获取这个【对象锁】时就会阻塞住。
  • 这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

我们先来介绍synchronized的语法:

// 线程1, 线程2 都使用同一对象作为锁,这样一个运行,另一个处于blocked阻塞
synchronized(对象)
{
    临界区
}

我们再给出相关代码示例:

// 我们创建一个room对象来作为锁,注意处理共享问题的线程需要绑定同一个锁

static int counter = 0;
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            synchronized (room) {
                counter++;
            }
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            synchronized (room) {
                counter--;
            }
        }
    }, "t2");
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    log.debug("{}",counter);
}

我们做简单解释:

  • synchronized相当于一个方法用来设置一个房间
  • room对象相当于一个锁,这个锁控制着房间,而房间中放着所有对应的synchronized里面的代码
  • 多个线程谁先进入room就可以获得钥匙,然后如果想要进入这个房间操作,只有有钥匙的线程才可以
  • 当该线程操作结束后,就会主动将钥匙让出来,其他线程就可以进行抢夺钥匙,哪个线程获得钥匙就可以继续操作
  • 同时我们需要注意时间片结束并不意味着解开锁,就算轮到其他线程的时间片,他们也不能进入到房间里去执行他们的代码

synchronized思考

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

我们简单给出三个思考问题:

// - 如果把 synchronized(obj) 放在 for 循环的外面,如何理解?-- 原子性
会将for循环也作为原子性的一部分,会连续执行5000次之后才释放锁给另一个线程使用

// - 如果 t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎样运作?-- 锁对象
两个线程使用不同的锁,自然就对应不同的房间,不具有安全性

// -如果 t1 synchronized(obj) 而 t2 没有加会怎么样?如何理解?-- 锁对象
一个线程使用锁,另一个不使用,自然不具有互斥关系,不具有安全性

对象解决共享问题

我们同样也可以直接采用一个类和synchronized搭配来解决共享问题

// 我们自定义一个类,里面装有数据,我们采用synchronized解决共享问题
class Room {

    // value值是属于room对象的
    int value = 0;

    // 这里的 synchronized 里面的 this 指的是创建的类的实际对象

    public void increment() {
        synchronized (this) {
            value++;
        }
    }

    public void decrement() {
        synchronized (this) {
            value--;
        }
    }

    public int get() {
        synchronized (this) {
            return value;
        }
    }
}

@Slf4j
public class Test1 {

    public static void main(String[] args) throws InterruptedException {
        // 注意:这里线程1,2采用的是一个roon对象,所以他们的value值是共享的
        Room room = new Room();

        Thread t1 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                room.increment();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int j = 0; j < 5000; j++) {
                room.decrement();
            }
        }, "t2");

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

        log.debug("count: {}" , room.get());
    }
}

synchronized方法使用

我们的synchronized有时也会用于类的方法中,具有不同的意义:

// 首先是将synchronized放在普通的方法上:下面两个class是等价的,这里的this指的是对象本身

class Test{
    public synchronized void test() {

    }
}

class Test{
    public void test() {
        synchronized(this) {

        }
    }
}

// 再者就是将synchronized放在静态方法上:下面两个class是等价的,这里的Test.class指的是类本身

class Test{
    public synchronized static void test() {
    }
}

class Test{
    public static void test() {
        synchronized(Test.class) {

        }
    }
}

"线程八锁"思考题

我们来给出面试常用的线程八锁思考题来进行自身检测:

/*第1题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() {
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.b(); }).start();
}

// 结果:1,2或2,1
// 解析:两者都绑定n1对象锁,互斥关系

/*第2题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.b(); }).start();
}

// 结果:1s后1,2或2,1s后1
// 解析:两者都绑定n1对象锁,互斥关系;sleep不具有任何关系

/*第3题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
    public void c() {
        log.debug("3");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.b(); }).start();
    new Thread(()->{ n1.c(); }).start();
}

// 结果:3 1s 12 或 23 1s 1 或 32 1s 1
// 解析:a,b都是n1对象锁,c不具有锁

/*第4题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n2.b(); }).start();
}

// 结果:2 1s 后 1
// 解析:a是n1的对象锁,b是n2的对象锁,不具有互斥关系

/*第5题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public static synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.b(); }).start();
}

// 结果:2 1s 后 1
// 解析:a采用类锁,b采用n1对象锁,不具有互斥关系

/*第6题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public static synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public static synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n1.b(); }).start();
}

// 结果:1s 后12, 或 2 1s后 1
// 解析:两者都是类锁,具有互斥关系

/*第7题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public static synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n2.b(); }).start();
}

// 结果:2 1s 后 1
// 解析:a采用类锁,b采用n2对象锁,不具有互斥关系

/*第8题*/ 

@Slf4j(topic = "c.Number")
class Number{
    public static synchronized void a() {
        sleep(1);
        log.debug("1");
    }
    public static synchronized void b() {
        log.debug("2");
    }
}
public static void main(String[] args) {
    Number n1 = new Number();
    Number n2 = new Number();
    new Thread(()->{ n1.a(); }).start();
    new Thread(()->{ n2.b(); }).start();
}

// 结果:1s 后12, 或 2 1s后 1
// 解析:两者都是类锁,具有互斥关系

线程安全分析

这小节我们将会介绍线程安全分析

变量线程安全问题

首先我们来思考成员变量和静态变量的安全性:

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了:如果只有读操作,则线程安全
  • 如果它们被共享了:如果有读写操作,则这段代码是临界区,需要考虑线程安全

我们再来思索一下局部变量的安全性:

  • 局部变量是线程安全的
  • 局部变量引用的对象:如果该对象没有逃离方法的作用访问,它是线程安全的
  • 局部变量引用的对象:如果该对象逃离方法的作用范围,需要考虑线程安全

我们通过简单代码进行测试:

/*局部变量*/ 

// 源代码展示:
public static void test1() {
    int i = 10;
    i++;
}

// 我们查看底层代码:每个线程调用 test1() 方法时局部变量 i,会在每个线程的栈帧内存中被创建多份,因此不存在共享
public static void test1();
 descriptor: ()V
 flags: ACC_PUBLIC, ACC_STATIC
 Code:
 stack=1, locals=1, args_size=0
 0: bipush 10
 2: istore_0
 3: iinc 0, 1
 6: return
 LineNumberTable:
 line 10: 0
 line 11: 3
 line 12: 6
 LocalVariableTable:
 Start Length Slot Name Signature
 3        4     0   i      I

/*成员变量*/

// 源代码展示:
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
    // 一个test对象
    ThreadUnsafe test = new ThreadUnsafe();
    // 两个线程去操纵
    for (int i = 0; i < THREAD_NUMBER; i++) {
        // 均执行method1方法
        new Thread(() -> {
            test.method1(LOOP_NUMBER);
        }, "Thread" + i).start();
    }
}     

class ThreadUnsafe {
    // 这里的list是属于对象的,创建在堆中,属于线程共同操纵对象
    ArrayList<String> list = new ArrayList<>();
    // 不断调用method2,3方法200次
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            // { 临界区, 会产生竞态条件
            method2();
            method3();
            // } 临界区
        }
    }
    private void method2() {
        list.add("1");
    }
    private void method3() {
        list.remove(0);
    }
}

// 运行结果的其中一种:如果线程2 还未 add,线程1 remove 就会报错
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
 at java.util.ArrayList.rangeCheck(ArrayList.java:657)
 at java.util.ArrayList.remove(ArrayList.java:496)
 at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35)
 at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26)
 at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14)
 at java.lang.Thread.run(Thread.java:748) 

/*成员变量局部优化*/

// 源代码展示
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
    // 一个test对象
    ThreadUnsafe test = new ThreadUnsafe();
    // 两个线程去操纵
    for (int i = 0; i < THREAD_NUMBER; i++) {
        // 均执行method1方法
        new Thread(() -> {
            test.method1(LOOP_NUMBER);
        }, "Thread" + i).start();
    }
}    

class ThreadSafe {
    public final void method1(int loopNumber) {
        // 这里将list变为局部变量,每个线程独自在自己的栈中创建,就不会产生安全问题
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    private void method2(ArrayList<String> list) {
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

/*方法public化缺陷*/

// 源代码展示:
class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    public void method2(ArrayList<String> list) {
        list.add("1");
    }
    public void method3(ArrayList<String> list) {
        list.remove(0);
    }
}
class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

// 如果我们将method2,3改为public,就可能会导致其他线程直接调用method2,3导致安全性问题
// 同时甚至可能出现其他子类继承父类导致修改原方法,同时创建一个线程导致多线程问题出现

常见线程安全类

我们在下面介绍一下我们常用的线程安全类:

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的类

同时我们需要知道无法改变的类型也是线程安全的:

  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的
  • 这里需要注意:String的各类修改方法都是直接创建一个新的String类型而不是在String本体进行增删

我们再进行简单解释:

  • 线程安全类的每个方法是原子的
  • 但注意它们多个方法的组合不是原子的!

我们采用代码进行简单解释:

/*下述的单个方法都是原子且安全性的*/ 

Hashtable table = new Hashtable();

new Thread(()->{
    table.put("key", "value1");
}).start();

new Thread(()->{
    table.put("key", "value2");
}).start();

/*但当这些方法组合起来,就无法保证其线程安全性*/ 

Hashtable table = new Hashtable();

// 线程1,线程2都执行时,可能出现下述情况
// 线程1get==null,线程2get==null,线程2put,线程1put;导致线程安全性错误出现
if( table.get("key") == null) {
	table.put("key", value);
}

常见题型分析

我们直接给出代码来进行题型分析:

/*题目1*/

// MyServlet是Servlet类,应用于Tomcat的多线程上
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) {
        // 使用上述变量
    }
}

/*题目2*/

public class MyServlet extends HttpServlet {
    // 是否安全?不是,底层使用Impl,里面包含了count这个共享数据,且没有使用锁
    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++;
    }
}

/*题目3*/

// 这里是Spring的@Aspect,属于单例,也是共享
@Aspect
@Component
public class MyAspect {
    // 是否安全? 不是,start属于共享数据
    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));
    }
}

/*题目4*/

public class MyServlet extends HttpServlet {
    // 是否安全 是,调用Dao,不具有可变参数
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // 是否安全 是,调用Dao,不具有可变参数
    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) {
            // ...
        }
    }
}

/*题目5*/

public class MyServlet extends HttpServlet {
    // 是否安全 不是,具有conn可变参数
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // 是否安全 不是,具有conn可变参数
    private UserDao userDao = new UserDaoImpl();

    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // 是否安全 不是,具有conn可变参数
    private Connection conn = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}

/*题目6*/

public class MyServlet extends HttpServlet {
    // 是否安全 但是这里是安全的
    // 虽然底层将conn创建在堆里,但是impl层创建了多个userDao对象,导致产生了多个conn不产生安全问题
    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 {
    // 是否安全 不是,具有conn可变参数
    private Connection = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}

/*题目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();
    }
}

经典习题分析

我们给出两个经典习题分析:

/*卖票问题*/

// 源码展示:
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;
        }
    }
}    

// 问题分析:
TicketWindow卖票窗口的票属于共享数据,对共享数据的修改需要进行上锁处理,所以我们需要对sell使用synchronized

// 修改展示:
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());
    }
    static Random random = new Random();
    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;
    }
    //在方法上加一个synchronized即可
    public synchronized int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}
/*转账问题*/

// 源码展示
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}",(a.getMoney() + b.getMoney()));
    }
    // Random 为线程安全
    static Random random = new Random();
    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) +1;
    }
}
class Account {
    private int money;
    public Account(int money) {
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    public void transfer(Account target, int amount) {
        if (this.money > amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
}

// 问题分析:
我们的transfer方法中存在两个对象,一个自身对象,一个被转账用户对象,所以无法使用synchronized方法
但是我们可以暂时将他设置为 static synchronized 直接对账户整体进行上锁来处理问题~

// 修改后代码:
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        log.debug("total:{}",(a.getMoney() + b.getMoney()));
    }

    static Random random = new Random();

    public static int randomAmount() {
        return random.nextInt(100) +1;
    }
}
class Account {
    private int money;
    public Account(int money) {
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    public static synchronized void transfer(Account target, int amount) {
        if (this.money > amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
}

Monitor

这小节我们将会介绍Monitor

Java对象头

在正式开始Monitor介绍之前,我们先来介绍一下Java对象头的定义:

# 以下内容均以32位虚拟机为例

# 普通对象
|--------------------------------------------------------------|
|                    Object Header (64 bits)                   |
|------------------------------------|-------------------------|
|       Mark Word (32 bits)          |   Klass Word (32 bits)  |
|------------------------------------|-------------------------|

# 数组对象
|---------------------------------------------------------------------------------|
|                             Object Header (96 bits)                             |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |   Klass Word(32bits)  |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|

# 其中 Mark Word 结构为
|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |        State       |
|-------------------------------------------------------|--------------------|
|    hashcode:25  | age:4 |   biased_lock:0   |   01    |       Normal       |
|-------------------------------------------------------|--------------------|
|thread:23|epoch:2| age:4 |   biased_lock:1   |   01    |       Biased       |
|-------------------------------------------------------|--------------------|
|          ptr_to_lock_record:30              |   00    | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|          ptr_to_heavyweight_monitor:30      |   10    | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                             |   11    |    Marked for GC   |
|-------------------------------------------------------|--------------------|

# 其中Klass Word主要存储对象类型名称

# 64位虚拟机的 Mark Word 结构为
|--------------------------------------------------------------------|--------------------|
|                          Mark Word (64 bits)                       |        State       |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 |  01   |        Normal      |
|--------------------------------------------------------------------|--------------------|
| thread:54 |   epoch:2   | unused:1 | age:4 | biased_lock:1 |  01   |        Biased      |
|--------------------------------------------------------------------|--------------------|
|                    ptr_to_lock_record:62                   |  00   | Lightweight Locked |
|--------------------------------------------------------------------|--------------------|
|                 ptr_to_heavyweight_monitor:62              |  10   | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
|                                                            |  11   |    Marked for GC   |
|--------------------------------------------------------------------|--------------------|

Monitor概述

我们来简单介绍一下Monitor:

  • Monitor 被翻译为监视器或管程
  • 每个 Java 对象都可以关联一个 Monitor 对象,就是我们之前创建的obj对象
  • 如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

我们给出简单示例图:

JUC学习笔记——共享模型之管程

我们来做简单解释:

  • 每个obj都由MarkWord来绑定一个Monitor

  • 每个线程都需要经过synchronized(obj)方法进入Monitor

  • 首先Monitor主要分为三个部分:WaitSet,EntryList,Owner

  • Owner:属于当前Monitor的正常运行区间,例如Thread-2就是目前运行线程

  • EntryList:属于当前Monitor的等待运行区间,需要等到Thread-2结束线程释放锁资源,Thread3等才可以抢夺锁

  • WaitSet:属于之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲 wait-notify 时会分析

此外我们还需要注意:

  • synchronized 必须是进入同一个对象的 monitor 才有上述的效果
  • 不加 synchronized 的对象不会关联监视器,不遵从以上规则

synchronized锁

这小节我们将会介绍synchronized底层原理和相关锁的内容

synchronized原理

我们会从底层代码来讲解synchronized的原理:

/*源码*/

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
    synchronized (lock) {
        counter++;
    }
}

/*底层字节码*/

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
              stack=2, locals=3, args_size=1

              // 下面是正式字节码过程

              // 正常运行阶段
              0: getstatic #2 // <- lock引用 (synchronized开始)
              3: dup
              4: astore_1 // lock引用 -> slot 1(这里提前存储一份锁对象,用于后续的解锁)
              5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针(这里进行了上锁)
              6: getstatic #3 // <- i
              9: iconst_1 // 准备常数 1
              10: iadd // +1
              11: putstatic #3 // -> i
              14: aload_1 // <- lock引用
              15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList(这里进行了解锁)
              16: goto 24

              // 报错补救阶段(去除锁)
              19: astore_2 // e -> slot 2
              20: aload_1 // <- lock引用
              21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
              22: aload_2 // <- slot 2 (e)
              23: athrow // throw e

              // 代码结束
              24: return

              // 这里是异常检测器:当出现异常时,移动到异常处理底层代码区域进行解锁操作
              Exception table:
              from to target type
              6    16  19    any
              19   22  19    any
              LineNumberTable:
              line 8: 0
              line 9: 6
              line 10: 14
              line 11: 24
              LocalVariableTable:
              Start Length Slot Name Signature
              0     25     0    args [Ljava/lang/String;
              StackMapTable: number_of_entries = 2
              frame_type = 255 /* full_frame */
              offset_delta = 19
              locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
              frame_type = 250 /* chop */
              offset_delta = 4

轻量级锁

我们首先来简单介绍一下轻量级锁:

  • 如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。

我们用一个简单的代码实现轻量级锁:

// 下面两个代码都调用了锁,但是他们归根结底属于一个流程,时间错开,这时系统就会避免直接使用Monitor而是用轻量级锁进行优化

static final Object obj = new Object();
public static void method1() {
    synchronized( obj ) {
        // 同步块 A
        method2();
    }
}
public static void method2() {
    synchronized( obj ) {
        // 同步块 B
    }
}

我们来展示一下实现流程(00轻量级锁,01无锁):

  1. 创建锁记录(Lock Record)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word

JUC学习笔记——共享模型之管程

  1. 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存入锁记录

JUC学习笔记——共享模型之管程

  1. 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁,这时图示如下

JUC学习笔记——共享模型之管程

  1. 当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一

JUC学习笔记——共享模型之管程

  1. 当退出 synchronized 代码块(解锁时)锁记录的值不为 null,这时使用 cas 将 Mark Word 的值恢复给对象头

除此之外我们需要注意cas切换不是每次都成功的:

  • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程
  • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

此外最后的cas恢复操作也不是都成功的:

  • 成功,则解锁成功
  • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

我们给出锁重入时的展示图:

JUC学习笔记——共享模型之管程

锁膨胀

我们首先简单介绍一下锁膨胀:

  • 如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争)
  • 这时需要进行锁膨胀,将轻量级锁变为重量级锁。

我们给出部分内容展示:

  • 当 Thread-1 进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁

JUC学习笔记——共享模型之管程

  • 这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
  • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址 ,然后自己进入 Monitor 的 EntryList BLOCKED

JUC学习笔记——共享模型之管程

  • 当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。
  • 这时会进入重量级解锁 流程,即按照 Monitor 地址找到 Monitor 对象,设置 Owner 为 null,唤醒 EntryList 中 BLOCKED 线程

自旋优化

我们同样先来介绍一下自旋优化:

  • 重量级锁竞争的时候,还可以使用自旋来进行优化
  • 如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

我们给出自旋成功的案例:

线程1 ( core 1上) 对象Mark 线程2 ( core 2上)
- 10(重量锁) -
访问同步块,获取monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取 monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行完毕 10(重量锁)重量锁指针 自旋重试
成功(解锁) 01(无锁) 自旋重试
- 10(重量锁)重量锁指针 成功(加锁)
- 10(重量锁)重量锁指针 执行同步块
- ... ...

我们也给出自旋失败的案例:

线程1 ( core 1上) 对象Mark 线程2( core 2上)
- 10(重量锁) -
访问同步块,获取monitor 10(重量锁)重量锁指针 -
成功(加锁) 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 -
执行同步块 10(重量锁)重量锁指针 访问同步块,获取monitor
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 自旋重试
执行同步块 10(重量锁)重量锁指针 阻塞
- ... ...

我们针对自旋添加一些注意信息:

  • Java 7 之后不能控制是否开启自旋功能
  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作失败,那么就认为这次自旋成功的可能性会低,少自旋甚至不自旋

偏向锁

我们再来介绍一下偏向锁:

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

  • 偏向锁:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程ID是自己的表示没有竞争,不用重新 CAS。

  • 以后只要不发生竞争,这个对象就归该线程所有

我们给出一个简单实例:

// 下述三个操作完全不具有时间冲突,相当于全由m1来管辖,这时m1使用会进行cas更换,后续m2,m3均不会进行cas切换

static final Object obj = new Object();
public static void m1() {
    synchronized( obj ) {
        // 同步块 A
        m2();
    }
}
public static void m2() {
    synchronized( obj ) {
        // 同步块 B
        m3();
    }
}
public static void m3() {
    synchronized( obj ) {
        // 同步块 C
    }
}

我们给出简单示例图:

graph LR subgraph 偏向锁 t5("m1内调用synchronized(obj)") t6("m2内调用synchronized(obj)") t7("m2内调用synchronized(obj)") t8(对象) t5 -.用ThreadID替换MarkWord.-> t8 t6 -.检查ThreadID是否是自己.-> t8 t7 -.检查ThreadID是否是自己.-> t8 end subgraph 轻量级锁 t1("m1内调用synchronized(obj)") t2("m2内调用synchronized(obj)") t3("m2内调用synchronized(obj)") t1 -.生成锁记录.-> t1 t2 -.生成锁记录.-> t2 t3 -.生成锁记录.-> t3 t4(对象) t1 -.用锁记录替换markword.-> t4 t2 -.用锁记录替换markword.-> t4 t3 -.用锁记录替换markword.-> t4 end

偏向状态

首先我们回忆一下对象头格式:

|--------------------------------------------------------------------|--------------------|
|                          Mark Word (64 bits)                       |        State       |
|--------------------------------------------------------------------|--------------------|
| unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 |  01   |        Normal      |
|--------------------------------------------------------------------|--------------------|
| thread:54 |   epoch:2   | unused:1 | age:4 | biased_lock:1 |  01   |        Biased      |
|--------------------------------------------------------------------|--------------------|
|                    ptr_to_lock_record:62                   |  00   | Lightweight Locked |
|--------------------------------------------------------------------|--------------------|
|                 ptr_to_heavyweight_monitor:62              |  10   | Heavyweight Locked |
|--------------------------------------------------------------------|--------------------|
|                                                            |  11   |    Marked for GC   |
|--------------------------------------------------------------------|--------------------|

我们进行简单解释:

  • Normal:正常锁
  • Biased:正常偏向锁
  • Lightweight Locked :轻量级锁
  • Heavyweight Locked:重量级锁
  • biased_lock : 控制偏向锁的开启的码位

我们针对偏向锁进行简单解释:

  • 如果开启了偏向锁(默认开启)那么对象创建后,markword值为0x05即最后3位为101,这时它的 thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效
  • 如果想避免延迟,可以加 VM 参数- XX:BiasedLockingStartupDelay=0来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001
  • 这时它的 hashcode、 age 都为 0,第一次用到 hashcode 时才会赋值
  • 但是如果调用了hashcode方法就会导致覆盖掉biased_lock关闭偏向锁

撤销偏向状态

我们这里总结了三种撤销偏向状态的方法

撤销 - 调用对象 hashCode

我们给出简单解释:

  • hashcode与biased_lock的字节码位置冲突,若调用hashcode方法得到hashcode就会覆盖掉biased_lock位置,导致偏向锁失效
  • 调用了对象的hashCode,但偏向锁的对象MarkWord中存储的是线程 id,如果调用hashCode会导致偏向锁被 撤销
  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

撤销 - 其它线程使用对象

我们给出简单解释:

  • 当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

我们给出简单示例:

/*主代码*/

private static void test2() throws InterruptedException {
    Dog d = new Dog();
    Thread t1 = new Thread(() -> {
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        synchronized (TestBiased.class) {
            TestBiased.class.notify();
        }
        // 如果不用 wait/notify 使用 join 必须打开下面的注释
        // 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
        /*try {
         	System.in.read();
         } catch (IOException e) {
         	e.printStackTrace();
         }*/
    }, "t1");
    t1.start();
    Thread t2 = new Thread(() -> {
        synchronized (TestBiased.class) {
            try {
                TestBiased.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
    }, "t2");
    t2.start();
}

/*结果*/
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

撤销 - 调用 wait/notify

我们进行简单解释:

  • wait和notify方法都是重量级锁的专属方法,如果调用就会导致升级为重量级锁

我们给出简单代码示例:

/*主代码*/

public static void main(String[] args) throws InterruptedException {
    Dog d = new Dog();
    Thread t1 = new Thread(() -> {
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            try {
                d.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t1");
    t1.start();
    new Thread(() -> {
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (d) {
            log.debug("notify");
            d.notify();
        }
    }, "t2").start();
}

/*结果*/
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

批量重偏向

我们先来介绍一下批量重偏向:

  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象 的 Thread ID

  • 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得偏向对象错误,于是会在给这些对象加锁时重新偏向至加锁线程

我们给出简单代码示例:

/*主代码*/

private static void test3() throws InterruptedException {
    Vector<Dog> list = new Vector<>();
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 30; i++) {
            Dog d = new Dog();
            list.add(d);
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
        }
        synchronized (list) {
            list.notify();
        }
    }, "t1");
    t1.start();

    Thread t2 = new Thread(() -> {
        synchronized (list) {
            try {
                list.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("===============> ");
        for (int i = 0; i < 30; i++) {
            Dog d = list.get(i);
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t2");
    t2.start();
}

/*结果*/

// 我们会发现,最开始都是101,这里设置了偏向锁
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
 // 我们会发现这里从101偏向锁撤销,然后变为000无锁,然后变为001轻量级锁
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
// 在这里超过阈值之后,直接将该锁的偏向对象设置为t2线程,所有锁都设置为了101偏向锁
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 000001

标签:

留言评论

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~