Java 并发编程笔记 第四章:共享模型_管程

网课链接: 黑马程序员java并发.

第四章:共享模型 管程

  • 第四章 共享模型_管程
    • 章节总结
    • 4.1 共享带来的问题
      • 1. 临界区 Critical Section
      • 2. 竞态条件 Race Condition
    • 4.2 synchronized解决方案
      • 1. 解决手段
    • 4.3 方法上的synchronized
    • 4.4 变量的线程安全分析
      • 成员变量和静态变量是否线程安全?
      • 局部变量是否线程安全?
      • 线程安全的情况
      • Private 或 final的重要性
      • 局部变量被public修饰符暴露时情况
    • 4.6 Monitor概念
      • Java对象头(32bit)
      • Monitor(锁) 原理
      • synchronized 优化原理
        • 1. Synchronized 用于同步代码块与同步方法原理
        • 2. 轻量级锁
        • 3. 锁膨胀
        • 4. 自旋优化
        • 5. 偏向锁
          • 撤销偏向
        • 6. 批量重偏向
        • 7. 批量撤销
    • 4.7, 4.8 wait notify
      • 1. 原理
      • 2. sleep() vs wait()
      • 3. 使用 wait/notify
      • 4. [设计模式] 同步模式之保护性暂停
        • **拓展- 多任务**
      • 5. 异步模式之生产者/消费者
        • 小结
    • 4.9 Park & Unpark
        • 基本使用
        • 特点
        • 原理
    • 4.10 java 线程状态和转换
    • 4.11 多把锁
    • 4.12 活跃性
      • 死锁
        • 定位死锁
      • 活锁
      • 饥饿
    • 4.13 ReentrantLock
      • 可重入
      • 可打断
      • 锁超时
      • 公平锁
      • 条件变量
        • 使用流程

第四章 共享模型_管程

章节总结

本章我们需要重点掌握的是

  • 分析多线程访问共享资源时,哪些代码片段属于临界区
  • 使用 synchronized 互斥解决临界区的线程安全问题
    • 掌握 synchronized 锁对象语法
    • 掌握 synchronzied 加载成员方法和静态方法语法
    • 掌握 wait/notify 同步方法
  • 使用 lock 互斥解决临界区的线程安全问题 掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
  • 学会分析变量的线程安全性、掌握常见线程安全类的使用
  • 了解线程活跃性问题:死锁、活锁、饥饿

应用方面

  • 互斥:使用 Synchronized 或 Lock 达到共享资源互斥效果,实现原子性效果,保证线程安全。
  • 同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果。

原理方面

  • monitor、synchronized 、wait/notify 原理
  • synchronized 进阶原理
  • park & unpark 原理

模式方面

  • 同步模式之保护性暂停
  • 异步模式之生产者消费者
  • 同步模式之顺序控制

4.1 共享带来的问题

线程出现问题的根本原因是因为线程上下文切换,上下文切换会导致线程里的指令没有执行完就切换执行其它线程了。

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

public 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);
}

问题

以上的结果可能是正数、负数、零。为什么呢?

因为 Java 中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析.

两个线程是交错进行对一个共享的资源进行更改, 并且没有上下文切换.

1. 临界区 Critical Section

  • 一个程序运行多个线程本身是没有问题的, 问题出在多个线程访问共享资源
  • 多个线程读共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

2. 竞态条件 Race Condition

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

4.2 synchronized解决方案

应用之互斥

1. 解决手段

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

synchronized 是阻塞式的解决方案,即俗称的【对象锁】:

它采用互斥的方式让同一 时刻至多只有一个线程能持有对象锁,其它线程再想获取这个对象锁时就会阻塞住。

这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换

注意
虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码

  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

加了Synchronize的标记后, 函数内临界区的代码就会变成串行的, 当一个线程执行完毕后下一个线程才会执行.

总结思考

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

语法

synchronized(对象{
   
  //临界区
}

4.3 方法上的synchronized

  • 加在成员方法上,锁住的是对象
public Test{
   
  // 在静态方法上加上 synchronized 关键字
  public synchronized void test(){
   
    // code
  }
}
/* equivalent to*/
class Test {
   
  public void test() {
   
    synchronized(this){
   
      // code
    }
  }
}
  • 加在静态方法上,锁住的是
public class Test {
   
	// 在静态方法上加上 synchronized 关键字
	

你可能感兴趣的:(java,并发编程)