线程基础核心 08 线程安全

admin / 开发 / ... / Reads: 5

1.案例

1.1.困惑的i++操作

简述:

1.在我们的日常开发中,经常会写:i++这样的操作

2.问题:那么它到底是不是线程安全的呢?

3.关键点:问题的关键在于i++是不是原子性操作。即i++对于操作系统,或者说对于jvm执行子系统,是一条指令,还是多条指令?

1.1.1.案例代码

package com.anan.thread.threadsafe;

/**
 * 让人困惑的i++操作
 */
public class ThreadSafeIAddOper {

    // 定义自增操作变量:i
    public  static int i_add = 0;

    // 在方法中,进行i_add的自增操作
    public  static void addI(){
        i_add++;
    }

    public static void main(String[] args) {
        // 创建20个线程,并行执行i_add自增操作
        Runnable r1 = new MyRunnable();

        // for循环,创建20个线程
        for (int i = 0; i < 20; i++) {
            new Thread(r1).start();
        }

        // 等待20个子线程执行结束后,主线程main输出i_add的值
        while(Thread.activeCount() > 2){
            ;
        }
        System.out.println("i_add变量最终值:" +i_add);
    }

    /**
     * 实现Runnable接口,创建线程
     */
    static class MyRunnable implements Runnable{
        public void run() {
            // for循环,执行i_add自增操作:10000次
            for (int i = 0; i < 10000; i++) {
                addI();
            }
        }
    }
}

1.1.2.执行结果

1

1.1.3.ThreadSafeAddOper字节码文件内容

简述:

1.彩蛋:通过javap工具,查看字节码文件结构

2.说明i++操作,对于jvm执行子系统,不是原子性(是由多条指令组成)

3.以下是类:ThreadSafeIAddOper,对应的class文件内容

D:\03other\02study\coding\mypro\thread-pro\target\classes>javap -v com.anan.thread.threadsafe.ThreadSafeIAddOper
Classfile /D:/03other/02study/coding/mypro/thread-pro/target/classes/com/anan/thread/threadsafe/ThreadSafeIAddOper.class
  Last modified 2020-2-15; size 1259 bytes
  MD5 checksum 6b289d7c5da1749f03e41da116a3b9a6
  Compiled from "ThreadSafeIAddOper.java"
public class com.anan.thread.threadsafe.ThreadSafeIAddOper
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
 ......内容省略......

  public static void addI();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #10                 // Field i_add:I
         3: iconst_1
         4: iadd
         5: putstatic     #10                 // Field i_add:I
         8: return
      LineNumberTable:
        line 13: 0
        line 14: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public static void main(java.lang.String[]);
    .......内容省略......

D:\03other\02study\coding\mypro\thread-pro\target\classes>

1.1.4.i++对应的字节码指令说明

简述:

1.通过截图,可以看到一个i++操作,在字节码层面,对应了四条jvm字节码指令:

getstatic、iconst_1、iadd、putstatic

2.说明对于jvm来说,i++不是原子性操作

2

1.2.线程安全基本手段:锁

简述:

1.改造3.1案例代码,通过加锁实现:多条指令操作的原子性。从而实现线程安全。

2.给addI方法,增加synchronized同步锁

3

执行结果:

41

1.3.关键字volatile错误使用案例

简述:

改造3.1.案例代码,通过volatile关键字修饰:

1.说明volatile关键字,只能保障线程的可见性(即一个线程修改了volatile关键字修改的变量后,会立即刷新到主内存,让其它线程可见)。

2.但volatile关键字,不能保障原子性,对于i++操作,它还是不能保障线程安全

3.关于volatile关键字的正确使用方式,请看讨论分享中内容说明。

53

执行结果:

1117

关于作者

王硕,网名信平,十多年软件开发经验,业余架构师,精通Java/Python/Go等,喜欢研究技术,著有《PyQt 5 快速开发与实战》《Python 3.* 全栈开发》,多个业余开源项目托管在GitHub上,欢迎微博交流。

Comments

Make a comment

Author: admin

Publish at: ...

关于作者

王硕,网名信平,十多年软件开发经验,业余架构师,精通 C/Java/Python/Go 等,喜欢读书,音乐和宅在家里。
Email: xujieiata@163.com

www.ultrapower.com ultrapower 王硕的博客,专注于研究互联网产品和技术,提供中文精品教程。