文章目录

  • 线程安全性文章索引
  • 脑图
  • 概述
  • 原子性synchronized 修饰的4种对象
    • 修饰代码块
      • 作用范围及作用对象
      • Demo
        • 多线程下 同一对象的调用
        • 多线程下不同对象的调用
    • 修饰方法
      • 作用范围及作用对象
      • Demo
        • 多线程下同一个对象的调用
        • 多线程下不同对象的调用
    • 修饰静态方法
      • 作用范围及作用对象
      • Demo
        • 多线程同一个对象的调用
        • 多线程下不同对象的调用
    • 修饰类
      • 作用范围及作用对象
      • Demo
        • 多线程下同一对象的调用
        • 多线程下不同对象的调用
  • 使用Synchronized来保证线程安全
    • 方法一
    • 方法二
  • 原子性的实现方式小结
  • 代码

线程安全性文章索引

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

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

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

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

并发编程-07线程安全性之有序性


脑图


概述

举个例子:
【多线程场景】假设有个变量a在主内存中的初始值为1,线程A和线程B同时从主内存中获取到了a的值,线程A更新a+1,线程B也更新a+1,经过线程AB更新之后可能a不等于3,而是等于2。因为A和B线程在更新变量a的时候从主内存中拿到的a都是1,而不是等A更新完刷新到主内存后,线程B再从主内存中取a的值去更新a,所以这就是线程不安全的更新操作.

解决办法

  • 使用锁 1. 使用synchronized关键字synchronized会保证同一时刻只有一个线程去更新变量. 2、Lock接口 【篇幅原因先不讨论lock,另开篇介绍】。
  • 使用JDK1.5开始提供的java.util.concurrent.atomic包,见 并发编程-04线程安全性之原子性Atomic包详解

先简单说下synchronized和lock

  • synchronized 依赖jvm

  • lock 依赖特殊的cpu指令,代码实现,比如ReentranLock

这里我们重点来看下synchronized关键字是如何确保线程安全的原子性的。


原子性synchronized 修饰的4种对象

  • 修饰代码块
  • 修饰方法
  • 修饰静态方法
  • 修饰类

修饰代码块

作用范围及作用对象

被修饰的代码被称为同步语句块,作用范围为大括号括起来的代码,作用于调用的对象, 如果是不同的对象,则互不影响

Demo

多线程下 同一对象的调用

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedDemo {public void test() {// 修饰代码块 ,谁调用该方法synchronized就对谁起作用   即作用于调用的对象 。 如果是不同的对象,则互不影响synchronized (this) {for (int i = 0; i < 10; i++) {log.info("修饰代码块 i = {} ",i);}}}public static void main(String[] args) {// 同一个调用对象SynchronizedDemo synchronizedDemo = new SynchronizedDemo();ExecutorService executorService = Executors.newCachedThreadPool();// 启动两个线程去 【使用同一个对象synchronizedDemo】调用test方法 for (int i = 0; i < 2; i++) {executorService.execute(() ->{synchronizedDemo.test();});}//     使用Thread  可以按照下面的方式写
//      for (int i = 0; i < 2; i++) {//          new Thread(()-> {//              synchronizedDemo.test2();
//          }).start();
//      }// 最后 关闭线程池executorService.shutdown();}
}

我们先思考下执行的结果是什么样子的?

上述代码,我们通过线程池,通过循环开启了2个线程去调用含有同步代码块的test方法 , 我们知道 使用synchronized关键字修饰的代码块作用的对象是调用的对象(同一个对象)。 因此这里的两个线程都拥有同一个对象synchronizedDemo的引用,两个线程,我们命名为线程A 线程B。 当线程A调用到了test方法,因为有synchronized关键字的存在,线程B只能等待线程A执行完。 因此A会输出0~9,线程A执行完之后,A释放锁,线程Bh获取到锁后,继续执行。

实际执行结果:

符合我们分析和预测。

如果我们把 test 方法的synchronized关键字去掉会怎样呢? 来看下


执行结果

可知,synchronized关键字修饰的代码块,确保了同一调用对象在多线程的情况下的执行顺序


多线程下不同对象的调用

为了更好地区分,我们给调用方法加个参数

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedDemo {public void test(String flag) {// 修饰代码块 ,谁调用该方法synchronized就对谁起作用 即作用于调用的对象 。 如果是不同的对象,则互不影响synchronized (this) {for (int i = 0; i < 10; i++) {log.info("{} 调用 修饰代码块 i = {} ",flag ,i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();// 对象 synchronizedDemoSynchronizedDemo synchronizedDemo = new SynchronizedDemo();// 对象 synchronizedDemo2SynchronizedDemo synchronizedDemo2 = new SynchronizedDemo();// synchronizedDemo 调用 testexecutorService.execute(()->{synchronizedDemo.test("synchronizedDemo");});// synchronizedDemo2 调用 testexecutorService.execute(()->{synchronizedDemo2.test("synchronizedDemo2");});// 最后 关闭线程池executorService.shutdown();}
}

先来猜测下执行结果呢?
两个不同的对象,调用test方法,应该是互不影响的,所以执行顺序是交替执行的。

运行结果:


修饰方法

被修饰的方法称为同步方法,作用的范围是整个方法,作用于调用的对象, 如果是不同的对象,则互不影响

作用范围及作用对象

同 修饰代码块

Demo

增加个方法 test2

 // 修饰方法 谁调用该方法synchronized就对谁起作用 即作用于调用的对象 。 如果是不同的对象,则互不影响public synchronized void test2() {// 修饰代码块 ,for (int i = 0; i < 10; i++) {log.info("调用 修饰代码块 i = {} ",  i);}}

多线程下同一个对象的调用

同 修饰代码块


结果:


多线程下不同对象的调用

同 修饰代码块


结果:


通过上面的测试结论可以知道 修饰代码块和修饰方法
如果一个方法内部是一个完整的synchronized代码块,那么效果和synchronized修饰的方法效果是等同的 。

还有一点需要注意的是,如果父类的某个方法是synchronized修饰的,子类再调用该方法时,是不包含synchronized. 因为synchronized不属于方法声明的一部分。 如果子类想使用synchronized的话,需要在方法上显示的声明其方法为synchronized


修饰静态方法

作用范围及作用对象

整个静态方法, 作用于所有对象


Demo

多线程同一个对象的调用

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedStaticMethodDemo {// 修饰静态方法public synchronized static void test() {for (int i = 0; i < 10; i++) {log.info("调用 修饰方法 i = {} ", i);}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 2; i++) {executorService.execute(() ->{test();});}// 最后 关闭线程池executorService.shutdown();}
}

结果:


多线程下不同对象的调用

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedStaticMethodDemo {// 修饰静态方法public synchronized static void test() {for (int i = 0; i < 10; i++) {log.info("调用 修饰方法 i = {} ", i);}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticMethodDemo demo1 = new SynchronizedStaticMethodDemo();SynchronizedStaticMethodDemo demo2 = new SynchronizedStaticMethodDemo();// demo1调用executorService.execute(() ->{// 其实直接调用test方法即可,这里仅仅是为了演示不同对象调用 静态同步方法demo1.test();});// demo2调用executorService.execute(() ->{// 其实直接调用test方法即可,这里仅仅是为了演示不同对象调用 静态同步方法demo2.test();});// 最后 关闭线程池executorService.shutdown();}
}

结果:


修饰类

作用范围及作用对象

修饰范围是synchronized括号括起来的部分,作用于所有对象


Demo

多线程下同一对象的调用

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedStaticClassDemo2 {// 修饰一个类public  void test() {synchronized (SynchronizedStaticClassDemo2.class) {for (int i = 0; i < 10; i++) {log.info("调用 修饰方法 i = {} ", i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticClassDemo2 demo = new SynchronizedStaticClassDemo2();// demo调用executorService.execute(() ->{demo.test();});// demo调用executorService.execute(() ->{demo.test();});// 最后 关闭线程池executorService.shutdown();}
}

结果


多线程下不同对象的调用

package com.artisan.example.sync;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import lombok.extern.slf4j.Slf4j;@Slf4j
public class SynchronizedStaticClassDemo2 {// 修饰一个类public  void test() {synchronized (SynchronizedStaticClassDemo2.class) {for (int i = 0; i < 10; i++) {log.info("调用 修饰方法 i = {} ", i);}}}public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();SynchronizedStaticClassDemo2 demo1 = new SynchronizedStaticClassDemo2();SynchronizedStaticClassDemo2 demo2 = new SynchronizedStaticClassDemo2();// demo1调用executorService.execute(() ->{demo1.test();});// demo2调用executorService.execute(() ->{demo2.test();});// 最后 关闭线程池executorService.shutdown();}
}

结果


使用Synchronized来保证线程安全

先回顾下 线程不安全的写法

方法一

下面用Synchronized来改造下

我们知道synchronized修饰静态方法,作用的对象是所有对象 , 因此 仅需要将 静态add方法 修改为同步静态方法即可。

多次运算


方法二

假设 add方法不是静态方法呢? 我们知道 当synchronized修饰普通方法,只要是同一个对象,也能保证其原子性

假设 add方法为普通方法

改造如下:

多次运行,结果总是10000


原子性的实现方式小结

  • synchronized
    不可中断锁,适合不激烈的竞争,可读性较好

  • atomic包
    竞争激烈时能维持常态,比Lock性能好,但只能同步一个值

  • lock
    可中断锁,多样化同步,竞争激烈时能维持常态。后面针对lock单独展开。


代码

https://github.com/yangshangwei/ConcurrencyMaster

并发编程-05线程安全性之原子性【锁之synchronized】相关推荐

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

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

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

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

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

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

  4. java 线程安全list_JAVA并发编程实战-线程安全性

    线程安全性: 对象的状态是指存储在状态变量(例如实例和静态域)中的数据. 对象的状态可能包括其他依赖对象的域. 例如:某个HashMap的状态不仅存储在HashMap对象本身,还存储在许多Map.En ...

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

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

  6. 并发编程 (三) 线程安全性之避免线程不安全

    文章目录 一.如何保证线程安全 1.1.并发原子类 1.2.加锁机制 1.3.信号量同步共享数据 1.4.封闭线程,不共享数据 1.5.发布与逸出 二.设计线程安全的类 2.1.前提要素 2.2.无限 ...

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

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

  8. JAVA并发编程3_线程同步之synchronized关键字

    在上一篇博客里讲解了JAVA的线程的内存模型,见:JAVA并发编程2_线程安全&内存模型,接着上一篇提到的问题解决多线程共享资源的情况下的线程安全问题. 不安全线程分析 public clas ...

  9. python并发编程之semaphore(信号量)_python 之 并发编程(守护进程、互斥锁、IPC通信机制)...

    9.5 守护进程 主进程创建守护进程 其一:守护进程会在主进程代码执行结束后就立即终止 其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic process ...

最新文章

  1. 定义ComboBox(下拉列表)组件垂直滚动条的样式。
  2. vscode编辑器,自己喜欢的颜色
  3. PANEL中显示窗体
  4. FreeBSD 10 将使用 Clang 编译器替换 GCC
  5. weblogic hibernate HqlToken
  6. 单选框加了css后显示不出来,layui radio 单选框 效果 显示不来 解决方法
  7. mysql分页limit (currentPage-1)*pageSize,pageSize
  8. Elasticsearch 父子关系
  9. npm warn config global `--global`, `--local` are deprecated. use `--location=global` instead.
  10. FlashFXP导出导入站点
  11. Designing Data-Intensive Applications
  12. 神舟电脑董事长吴海军作客CNET中国,笑谈国货08怎夺江山
  13. Web3 全栈开发指南
  14. 怎么把一个网址链接转换成文字的形式, 就是点一下那段文字就进入该网站
  15. 开发微领地小蜜系统APP平台
  16. JSON-LIB 的使用指南
  17. 用正则表达式来判断手机号、地址、身份证号、邮箱等格式是否正确
  18. Trister‘s Lend借贷协议如何解决流动性困境?
  19. 突破路缘石建设桎梏中利用沥青拦水带成型机实现
  20. 手机android.sys木马,使用kali生成木马入侵安卓手机

热门文章

  1. C++中的RAII机制
  2. 二叉堆的优先队列基本原理及实现
  3. 快速排序的基本原理及实现
  4. 强化学习(三)---马尔科夫决策过程
  5. statsmodels 笔记:自回归模型 AutoReg
  6. SVM -支持向量机原理详解与实践之五
  7. 各种排序算法的讲解与代码实现
  8. Python入门100题 | 第002题
  9. Python编程基础:第二十四节 作用域Scope
  10. 时空快照模型snapshots