线程安全性:

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程安全的。

线程安全主要体现在以下三个方面:

原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作

可见性:一个线程对主内存的修改可以及时的被其他线程观察到

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

一.原子性:

1.atomic包

我们可以模拟用工具压测例如JMeter,AB等测试程序是否线程安全,也可以用代码来实现,这里我们实现有5000个请求,200个并发的情况来进行程序的压测。

压测代码如下:

@Slf4j
public class CountExample {// 请求总数public static int clientTotal = 5000;// 同时并发执行的线程数public static int threadTotal = 200;public static int count = 0;public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();//限制最多只有200个线程同时并发操作Semaphore semaphore = new Semaphore(threadTotal);//初始值为5000,调用countDownLatch.countDown()之后会自动减1,CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {semaphore.acquire();add();semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}countDownLatch.countDown();}});}//阻塞这句代码下面的代码的执行,知直到countdown为0countDownLatch.await();executorService.shutdown();log.info("count = {}",count);}public static void add(){count++;}
}

Semaphore这个类翻译过来是信号量的意思,表示只能有规定的线程去执行一段代码,这是设置的是200个线程。首先,线程池开启线程,每个线程执行时都会执行semaphore.acquire这句代码,最后是semaphore.release,中间是add()进行count+1的操作,线程在执行add()前都会去看一下当前是否已经有200个线程在操作这个方法,有的话就加上锁,当前线程阻塞,等有线程执行完了就再去执行,然后最后释放锁,这样可以保证每次同时只有最多200个线程操作add()方法。

CountDownLatch这里我们初始化传入了5000,在线程每次执行完add()方法之后就执行countDownLatch.countDown()方法进行自动减1操作,而且在线程池释放前加上了countDownLatch.await(),这句代码作用的当countDownLatch里面的值减为0时就能执行下面的代码,否则就阻塞。

当然上面的代码肯定是线程不安全的。

所以引出了我们的atomic包。

(1)AtomicInteger,AtomicLong

我们对add()方法进行改造:

    public static AtomicInteger count = new AtomicInteger(0);public static void atomicAdd(){count.incrementAndGet();//count++;}

执行多次,发现结果都是一样的:

说明这个类是线程安全的。

下面我们来看一下这个类里面的原理:

点进去:

,继续点:

这个就是这个类的核心实现了,其中compareAndSwapInt方法是底层实现的。

var1是我们的count对象,var2是当前的值,var4是要加多少,var5是根据getIntVolatitle方法从底层取出来的值,然后把值都传进compareAndSwapInt方法中,这个方法首先会判断传进来的var2的值和从底层var5的值进行对比,如果相同的话进行相加的操作,如果不相同的话就重新从底层取出var5再进行判断,以此循环。

(2)AtomicBoolean

这个类与上面的类大同小异,只不过针对的boolean类型的数据类型。

@Slf4j
public class AtomicExample2 {private static AtomicBoolean isHappened = new AtomicBoolean(false);// 请求总数public static int clientTotal = 5000;// 同时并发执行的线程数public static int threadTotal = 200;private static int count;public static void main(String[] args) throws InterruptedException {ExecutorService executorService = Executors.newCachedThreadPool();//限制最多只有200个线程同时并发操作Semaphore semaphore = new Semaphore(threadTotal);//初始值为5000,调用countDownLatch.countDown()之后会自动减1,CountDownLatch countDownLatch = new CountDownLatch(clientTotal);for (int i = 0; i < clientTotal; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {semaphore.acquire();test();semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}countDownLatch.countDown();}});}//阻塞这句代码下面的代码的执行,知直到countdown为0countDownLatch.await();executorService.shutdown();log.info("count = {}",count);}private static void test(){if (isHappened.compareAndSet(false,true)){log.info("执行了");}}
}

执行结果:

可以看到test方法只被执行了一次,因为我们的初始化的isHappened的值是false,而我们的compareAndSet方法意思是如果当前的值是false的话,就更新为true,而且是保证的是只有一个线程的执行,所以当第一个线程来到这个方法的时候发现值是false然后更新为true,之后的4999个线程拿到的值就都是true了,所以test方法里面的log指挥打印一次了,这个类我们可以保证当并发的时候我们的方法里面的逻辑只被执行一次的时候可以使用该类。

2. synchronized关键字

synchronized关键字也是原子性体现的一种,也是保证每次执行只有一个线程,而且注意一点的是synchronized关键字锁住的不是代码,而是对象。锁住的是对象这怎么理解尼?通过例子来看:

@Slf4j
public class SynchronizedExample {public static void main(String[] args) {SynchronizedExample synchronizedExample1 = new SynchronizedExample();SynchronizedExample synchronizedExample2 = new SynchronizedExample();ExecutorService executorService = Executors.newCachedThreadPool();executorService.execute(new Runnable() {@Overridepublic void run() {synchronizedExample1.test1(1);}});executorService.execute(new Runnable() {@Overridepublic void run() {synchronizedExample1.test1(3);}});executorService.execute(new Runnable() {@Overridepublic void run() {synchronizedExample2.test1(2);}});}//修饰代码块public void test1(int j){synchronized (this){for (int i = 0;i < 3 ;i++){log.info("test1 {}-> {}",j,i);}}}//修饰方法public synchronized void test2(){for (int i = 0;i < 3 ;i++){log.info("test2 -> {}",i);}}
}

结果:

我们可以发现除了最后三条log是顺序的之外,其余的log输出都是乱序的,这是为什么尼?这就是synchronized关键字锁住的是类的实例对象的问题导致的了。我们new了两个对象synchronizedExample1和synchronizedExample2,分别一个在两个线程中都调用了test方法,一个在一个线程中调用了一次test方法,由于synchronized关键字锁住的是类的实例对象,所以synchronizedExample1对象和synchronizedExample2对象调用的test方法都可以互相干扰(即test方法在同一时间并不是只有一个线程去执行,而是两个线程),而synchronizedExample1在两个线程中都调用了test方法,由于是同一个对象的调用,所以此时的synchronized关键字就起作用了,它能使者两个线程中只能有一个执行完test方法,另外一个阻塞知道上面的线程执行完。所以这就很明显了,synchronized关键字锁住的是同一个对象调用的代码段。

synchronized的修饰类型与被锁的对象:

(1)修饰代码块 -->类的实例对象

(2)修饰方法 --> 类的实例对象

(3)修饰类的静态方法 -->类的任意对象(全局锁)

(4)修饰类-->类的任意对象(全局锁)

上面举出了(1)和(2)的修饰类型的例子,(3)和(4)就和(1)(2)的不同了,不同在于对于类的任意的对象,synchronized关键字都能起作用,这就是全局锁的意思,还是上面的例子,如果我们换成的是(3)或者(4)的修饰类型的话,那么得到log日志打印结果应该是顺序的。

二.可见性------volatile关键字

volatile作为java中的关键词之一,用以声明变量的值可能随时会别的线程修改,使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效(非volatile变量不具备这样的特性,非volatile变量的值会被缓存,线程A更新了这个值,线程B读取这个变量的值时可能读到的并不是是线程A更新后的值)。volatile会禁止指令重排。

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

private volatile boolean init = flase;//线程1......
init = true;//线程2
while(!init){
if(init){
......
}}

使用volatile关键字我们可以用在两个线程之间作为共享变量条件,当线程1初始化完改变了init值之后,线程2会立马感知到init的值发生了变化,然后进行后续的操作,这就利用了线程安全的可见性。

三.有序性

Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有的操作都是有序的:如果在一个线程中观察另外一个线程,所有的线程操作都是无序的。前半句是指“线程内表现为串行的语义”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟”现象。Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这条规则决定了持有同一个锁的两个同步块只能串行的进入。

Java并发编程之线程安全性相关推荐

  1. Java并发编程之线程安全性分析之原子性、可见性、有序性

    一:线程的安全性分析 如何理解线程安全: 当多个线程访问某个共享对象时,不管运行环境采用何种调度方式,或者这些线程如何交替执行,并且主调代码中不需要任何的额外同步操作或者协同操作,这个类都能表现出正确 ...

  2. 并发编程-06线程安全性之可见性 (synchronized + volatile)

    文章目录 线程安全性文章索引 脑图 可见性定义 导致不可见的原因 可见性 -synchronized (既保证原子性又保证可见性) 可见性 - volatile(但不保证操作的原子性) volatil ...

  3. 并发编程-05线程安全性之原子性【锁之synchronized】

    文章目录 线程安全性文章索引 脑图 概述 原子性synchronized 修饰的4种对象 修饰代码块 作用范围及作用对象 Demo 多线程下 同一对象的调用 多线程下不同对象的调用 修饰方法 作用范围 ...

  4. 并发编程-04线程安全性之原子性Atomic包的4种类型详解

    文章目录 线程安全性文章索引 脑图 概述 原子更新基本类型 Demo AtomicBoolean 场景举例 原子更新数组 Demo 原子更新引用类型 Demo 原子更新字段类型 使用注意事项: Dem ...

  5. 并发编程-03线程安全性之原子性(Atomic包)及原理分析

    文章目录 线程安全性文章索引 脑图 线程安全性的定义 线程安全性的体现 原子性 使用AtomicInteger改造线程不安全的变量 incrementAndGet源码分析-UnSafe类 compar ...

  6. java并发编程与线程安全

    2019独角兽企业重金招聘Python工程师标准>>> 什么是线程安全 如果对象的状态变量(对象的实例域.静态域)具有可变性,那么当该对象被多个线程共享时就的考虑线程安全性的问题,否 ...

  7. 【Java 并发编程】线程池机制 ( ThreadPoolExecutor 线程池构造参数分析 | 核心线程数 | 最大线程数 | 非核心线程存活时间 | 任务阻塞队列 )

    文章目录 前言 一.ThreadPoolExecutor 构造参数 二.newCachedThreadPool 参数分析 三.newFixedThreadPool 参数分析 四.newSingleTh ...

  8. 【Java 并发编程】线程池机制 ( 线程池示例 | newCachedThreadPool | newFixedThreadPool | newSingleThreadExecutor )

    文章目录 前言 一.线程池示例 二.newCachedThreadPool 线程池示例 三.newFixedThreadPool 线程池示例 三.newSingleThreadExecutor 线程池 ...

  9. (转)Java并发编程:线程池的使用

    背景:线程池在面试时候经常遇到,反复出现的问题就是理解不深入,不能做到游刃有余.所以这篇博客是要深入总结线程池的使用. ThreadPoolExecutor的继承关系 线程池的原理 1.线程池状态(4 ...

  10. 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

最新文章

  1. 后台返回给前端数据拆分成三级菜单
  2. kubernetes(k8s)之yaml文件详解
  3. Firebug 1.7正式版发布,支持Firefox 4
  4. Codeforces 1110G Tree-Tac-Toe (博弈论)
  5. kotlin-unresolved reference daclaredFunctions
  6. 2010-04-25 搞定aftr
  7. Django之ORM对数据库操作
  8. 大数据之Elasticsearch教程
  9. 拓扑之homeomorphic
  10. Linux突然断电造成系统文件损坏而无法启动
  11. oracle sha2,Oracle11.2.0.1在AMD CPU 64位硬件,32位操作系统下的BUG 8670579
  12. PDF文件切成图片然后拼接成长图
  13. (VGG)VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION--Karen Simonyan
  14. All clients has disconnected from. You can graceful shutdown now., dubbo version: , current host
  15. 任正非创业期间得抑郁症:研发失败我就跳楼
  16. 浅论信息化环境下的印刷业发展
  17. 【Matlab学习手记】拟牛顿型信頼域方法求解函数极值
  18. 农业农村部回应长江刀鱼将正式禁捕:已严重过度捕捞
  19. c语言二叉树族谱管理系统,数据结构课程设计报告(用二叉树实现家谱管理系统).doc...
  20. Windows常见的几种提权方法

热门文章

  1. NumPy库---Axis理解
  2. git diff与git status
  3. 213.打家劫舍II
  4. matlab仿真之大尺度衰落因子的产生
  5. 中调用view_在 View 上使用挂起函数
  6. c#物联网_「物联网架构」Apache-Kafka:物联网数据平台的基石
  7. Numpy之文件存取
  8. 基础集合论 第一章 4 子集
  9. 逆向动态调试之Ollydbg的使用
  10. DS_Store文件泄漏