Java教程

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

本文主要是介绍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 之后不能控制是否开启自旋功能

这篇关于java并发编程笔记(三)--管程(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!