java并发编程笔记(三)--管程(二)

习题:卖票

请改正:

public class ExerciseSell {
 public static void main(String[] args) {
 
 //2000张票
 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;
 }
 }
}

解决方法:

public synchronized int getCount() {
 return count; }
    
public int sell(int amount) {

if (this.count >= amount) {
 this.count -= amount;
 return amount;
}
 else {
 return 0;
 }
}

并发编程的测试脚本:

for /L %n in (1,1,10) do java -cp ".;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackclassic\1.2.3\logback-classic-1.2.3.jar;C:\Users\manyh\.m2\repository\ch\qos\logback\logbackcore\1.2.3\logback-core-1.2.3.jar;C:\Users\manyh\.m2\repository\org\slf4j\slf4japi\1.7.25\slf4j-api-1.7.25.jar" cn.itcast.n4.exercise.ExerciseSell

习题:转账问题

请改正:

public class ExerciseTransfer {
 public static void main(String[] args) throws InterruptedException {
     
 Account a = new Account(1000);
 Account b = new Account(1000);
 
 //a向b转账
 Thread t1 = new Thread(() -> {
 for (int i = 0; i < 1000; i++) {
 a.transfer(b, randomAmount());
 }
 }, "t1");
 //b向a转账
 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);
 }
 }
}

错误改正:

public synchronized void transfer(Account target, int amount) {
 if (this.money > amount) {
 this.setMoney(this.getMoney() - amount);
 target.setMoney(target.getMoney() + amount);
 }
}

正确改正:

public synchronized void transfer(Account target, int amount) {
     synchronized(Account.class) {
 if (this.money > amount) {
 this.setMoney(this.getMoney() - amount);
 target.setMoney(target.getMoney() + amount);
 }
     }        
}

因为这个例子是每个对象里都有一个共享变量,且互相有读写,如果只用对象加锁,仍然解决不了一个线程调用方法时,另一个线程仍能修改对方的问题,所以需要对类加锁。

4.6.Monitor概念

当我们使用sychronized关键字时,其实使用了底层的monitor对象,monitor是一种监控对象,他需要和一个java对象绑定,来监控临界区代码,同时分配所有权,得到所有权的线程可以使用临界区代码,无所有权的线程不能使用临界区代码。

QQ截图20220221155847

  • 当线程执行到临界区代码时,如果使用了synchronized,会先查询synchronized中所指定的对象(obj)是否绑定了Monitor

    • 如果没有绑定,则会先去去与Monitor绑定,并且将Owner设为当前线程。

    • 如果

      已经绑定

      ,则会去查询该Monitor是否已经有了Owner

      • 如果没有,则Owner与将当前线程绑定
      • 如果有,则放入EntryList,进入阻塞状态(blocked)
  • 当Monitor的Owner将临界区中代码执行完毕后,Owner便会被清空,此时EntryList中处于阻塞状态的线程会被叫醒并竞争,此时的竞争是非公平的

  • 注意

    • 对象在使用了synchronized后与Monitor绑定时,会将对象头中的Mark Word置为Monitor指针。
    • 每个对象都会绑定一个唯一的Monitor,如果synchronized中所指定的对象(obj)不同,则会绑定不同的Monitor

4.7.Synchronized原理进阶

4.7.1.对象头格式–重点聚焦 Mark Word

32位虚拟机:

QQ截图20220221171215

64位虚拟机:

QQ截图20220221171259

对于不同的锁状态,后两个位是不同的,系统会根据后两个位来判断当前对象的锁状态。

4.7.2.轻量级锁

轻量级锁使用场景:当一个对象被多个线程所访问,但访问的时间是错开的(不存在竞争),此时就可以使用轻量级锁来优化。

注意:自从jdk加入了锁优化,轻量级锁是使用sychronized关键字时就优先使用的。

使用过程

QQ截图20220221171635

QQ截图20220221172928

QQ截图20220221172933

null的情况代码如下:

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

4.7.3.锁膨胀

QQ截图20220221171737

4.7.4.自旋优化

QQ截图20220221172054

自旋会占用 CPU 时间,单核 CPU 自旋就是浪费(比之前多了自旋的时间浪费时间片),多核 CPU 自旋才能发挥优势。

在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会

高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

Java 7 之后不能控制是否开启自旋功能

4.7.5.偏向锁

轻量级锁在没有竞争时,每次重入(该线程执行的方法中再次锁住该对象)操作仍需要cas替换操作,这样是会使性能降低的。

所以引入了偏向锁对性能进行优化:在第一次cas时会将线程的ID写入对象的Mark Word中。此后发现这个线程ID就是自己的,就表示没有竞争,就不需要再次cas,以后只要不发生竞争,这个对象就归该线程所有。

减少了重复cas操作引起的效率问题。

偏向锁和轻量级锁对比

QQ截图20220222150737

偏向状态的判定

QQ截图20220222150858

偏向锁也是默认使用的,sychronized关键字的加锁后默认的启动顺序是:

偏向锁 -> 轻量级锁 -> 重量级锁 ->自旋优化

撤销偏向锁,取消偏向状态

以下几种情况会使对象的偏向锁失效

  • 调用对象的hashCode方法(放不下了)
  • 多个线程使用该对象
  • 调用了wait/notify方法(调用wait方法会导致锁膨胀而使用重量级锁
偏向锁的测试
  • 导入测试工具

QQ截图20220222085414

  • 测试一

QQ截图20220222085619

  • 测试二

QQ截图20220222152039

QQ截图20220222152101

偏向锁变为轻量级锁

QQ截图20220222152529

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101 //偏向给1线程
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000 //2线程执行,变为轻量级锁
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001//无锁状态
批量重偏向

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

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

测试

QQ截图20220222153312

结果:

[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] - ===============> 
[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] - 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 //锁解除
//从20次开始,偏向锁指定变了,变成了t2
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 //t1的偏向锁
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 //从这变成了t2的偏向锁
[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 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

要注意,偏向锁操作一般是对一个了类的所有对象起作用的,重偏向发生后,以后创建的对象也是重偏向。

下面要介绍的批量撤销也是这样。

批量撤销

当撤销偏向锁被更改超过 40 次后,再次被更改时,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

测试

static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
 Vector<Dog> list = new Vector<>();
 
    int loopNumber = 39;
//先让偏向锁偏向线程1
  t1 = new Thread(() -> {
 for (int i = 0; i < loopNumber; i++) {
 Dog d = new Dog();
 list.add(d);
 synchronized (d) {
 log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
 }
 }
 LockSupport.unpark(t2);
 }, "t1");
    

    t1.start();
//线程二改变偏向锁指向40次 
 t2 = new Thread(() -> {
 LockSupport.park();
 log.debug("===============> ");
 for (int i = 0; i < loopNumber; 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));
 }
 LockSupport.unpark(t3);
 }, "t2");
 
    t2.start();
 //之后线程三再去改变偏向锁,此时就会变成轻量级锁,无论再改变几次
 t3 = new Thread(() -> {
 LockSupport.park();
 log.debug("===============> ");
 for (int i = 0; i < loopNumber; 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));
 }
 }, "t3");
 
    t3.start();
 
    t3.join();
 
    log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}
锁消除

QQ截图20220222154947

QQ截图20220222154952

指定就是 当 Obeject对象o是一个不共享的局部变量,为了简洁,jvm会自动解除他在方法里以他为锁对象的锁。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值