二、JMM与线程安全

JMM

概念

JMM 是一套规范,定义多线程环境下,Java 程序中的变量(特别是共享变量)如何被写入内存以及如何从内存中读取的规则,旨在解决由于多线程访问共享数据而可能引发的各种问题

注意:JMM 不是指 Java 程序运行时内存区域的划分(如堆、栈、方法区),那是 JVM 内存结构。这是两个不同的概念

三大特性

原子性

一次操作,要么所有操作全部执行并不受任何因素干扰,要么都不执行

可见性

当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值

这里线程对本地内存修改后,另一线程读取的共享变量位于主内存中,该值不是最新的

有序性

程序执行的顺序按照代码的先后顺序执行

JMM 允许某些指令重排序以提高性能,但会保证线程内的操作顺序不会被破坏

Happens-Before 原则

是一组规则,用于描述两个操作之间的内存可见性。如果操作 A Happens-Before 于操作 B,那么 A 操作所做的任何修改对 B 操作都是可见的。

1)程序次序规则: 在一个线程内,书写在前面的操作先行发生于书写在后面的操作

2)管程锁定规则: 一个 unlock 操作先行发生于后面对同一个锁的 lock 操作

3)volatile 变量规则: 对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作

4)线程启动规则: Thread 对象的start()方法先行发生于此线程的每一个动作

5)线程终止规则: 线程中的所有操作都先行发生于对此线程的终止检测

6)线程中断规则: 对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生

7)对象终结规则: 一个对象的初始化完成先行发生于它的 finalize() 方法的开始

8)传递性: 如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C

volatile

1.可见性:

确保变量的可见性。当一个线程修改 volatile 变量的值,新值会立即被刷新到主内存中,其他线程中的缓存无效,需要在主存中读取新值

2.禁止指令重排序:

1)通过内存屏障来禁止部分指令重排序

2)保证执行到 volatile 变量时,其前面的所有语句都必须执行完,后面所有得语句都未执行

示例:

两个线程分别依次调用writerreadervolatile修饰flag变量关键字保证 2 操作执行先于 3 操作

class ReorderExample {
  int a = 0;
  boolean volatile flag = false;
  public void writer() {
      a = 1;                   //1
      flag = true;             //2
  }
  public void reader() {
      if (flag) {                //3
          int i = a * a;         //4
          System.out.println(i);
      }
  }
}

3.不保证原子性:

private static volatile int count = 0;

public static void main(String[] args) {
  for (int i = 0; i < 1000; i++) {
      new Thread(() -> count++).start();
  }
  System.out.println(count); // 结果可能不为 1000
}

synchronized

Java 多线程的锁都是基于对象的,Java 中的每一个对象都可以作为一个锁(类对象锁、实例对象锁)

作用

实现代码同步,同一时刻只有一个线程在执行某个方法或代码块

保证可见性,对于共享数据的变化,能够直接被其他线程读取(可以代替volatile

同步方式

同步方法(实例对象锁)

在方法声明中加入synchronized关键字,保证在任意时刻,对于同一个对象,只有一个线程能执行该方法

正确示范:

这里istatic修饰的共享资源

两个线程由同一个instance对象创建

一个对象只有一把锁,当一个线程获取了该锁后,其他线程无法访问该对象的其他 synchronized 方法

这把锁只作用于 synchronized 方法,其他线程还是可以访问该对象的非 synchronized 方法

public class AccountingSync implements Runnable {
    //共享资源(临界资源)
    static int i = 0;
    // synchronized 同步方法
    public synchronized void increase() {
        i ++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String args[]) throws InterruptedException {
        AccountingSync instance = new AccountingSync();
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

错误示范:

这里两个线程由不同对象创建,具有两把锁。所以最终i的值无法保证

public class AccountingSyncBad implements Runnable {
    //共享资源(临界资源)
    static int i = 0;
    // synchronized 同步方法
    public synchronized void increase() {
        i ++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }

    public static void main(String args[]) throws InterruptedException {
        // new 两个AccountingSync新实例
        Thread t1 = new Thread(new AccountingSyncBad());
        Thread t2 = new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

同步静态方法(类对象锁)

在静态方法声明中添加synchronized关键字,保证同一时刻,只有一个线程能够访问该类的静态同步方法

示例:

这里两线程由同一个类的不同对象创建,但只能争夺同一个类锁

访问静态 synchronized 方法占用的锁是当前类的锁,当类的锁被占用时,不影响访问对象锁占用的资源

此处对象仍然可以通过调用increase4Obj()方法,修改i的值

public class AccountingSyncClass implements Runnable {
    static int i = 0;
    /**
     * 同步静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase() {
        i++;
    }
    // 非静态,访问时锁不一样不会发生互斥
    public synchronized void increase4Obj() {
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncClass());
        //new新实例
        Thread t2=new Thread(new AccountingSyncClass());
        //启动线程
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

同步代码块(实例对象锁/类对象锁)

当我们编写的方法代码量较多时,而需要同步的代码块只有一小部分,此时可以使用同步代码块的方式对需要同步的代码进行包裹

括号内可以填写对象实例或者类对象,锁分别加在对象和类上

//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
//Class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

synchronized是可重入锁

当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态

但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功

一个线程调用 synchronized 方法的同时,在其方法体内部调用该对象另一个 synchronized 方法是允许的

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇