本文分享自华为云社区《如何只用5招实现多线程场景下的线程安全?》,作者: Java小叮当。

1、引言

当前随着计算机硬件的快速发展,个人电脑上的 CPU 也是多核的,现在普遍的 CUP 核数都是 4 核或者 8 核的。因此,在编写程序时,需要为了提高效率,充分发挥硬件的能力,则需要编写并行的程序。Java 语言作为互联网应用的主要语言,广泛应用于企业应用程序的开发中,它也是支持多线程Multithreading)的,但多线程虽好,却对程序的编写有较高的要求。

单线程可以正确运行的程序不代表在多线程场景下能够正确运行,这里的正确性往往不容易被发现,它会在并发数达到一定量的时候才可能出现。这也是在测试环节不容易重现的原因。因此,多线程(并发)场景下,如何编写线程安全(Thread-Safety)的程序,对于程序的正确和稳定运行有重要的意义。下面将结合示例,谈谈如何在 Java 语言中,实现线程安全的程序。

为了给出感性的认识,下面给出一个线程不安全的示例,具体如下:

package com.example.learn;
public class Counter {private static int counter = 0;public static int getCount(){return counter;}public static  void add(){counter = counter + 1;}
}1.2.3.4.5.6.7.8.9.10.

这个类有一个静态的属性 counter,用于计数。其中可以通过静态方法 add()对 counter 进行加 1 操作,也可以通过 getCount()方法获取到当前的计数 counter 值。如果是单线程情况下,这个程序是没有问题的,比如循环 10 次,那么最后获取的计数 counter 值为 10。但多线程情况下,那么这个结果就不一定能够正确获取,可能等于 10,也可能小于 10,比如 9。下面给出一个多线程测试的示例:

package com.example.learn;
public class MyThread extends Thread{private String name ;public MyThread(String name){this.name = name ;}public void run(){Counter.add();System.out.println("Thead["+this.name+"] Count is "+  Counter.getCount());}
}
///
package com.example.learn;
public class Test01 {public static void main(String[] args) {for(int i=0;i<5000;i++){MyThread mt1 = new MyThread("TCount"+i);mt1.start();}}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

这里为了重现计数的问题,线程数调至比较大,这里是 5000。运行此示例,则输出可能结果如下:

Thead[TCount5] Count is 4
Thead[TCount2] Count is 9
Thead[TCount4] Count is 4
Thead[TCount14] Count is 10
..................................
Thead[TCount4911] Count is 4997
Thead[TCount4835] Count is 4998
Thead[TCount4962] Count is 49991.2.3.4.5.6.7.8.

注意:多线程场景下,线程不安全的程序输出结果具有不确定性。

2、synchronized 方法

基于上述的示例,让其变成线程安全的程序,最直接的就是在对应的方法上添加 synchronized 关键字,让其成为同步的方法。它可以修饰一个类,一个方法和一个代码块。对上述计数程序进行修改,代码如下:

package com.example.learn;
public class Counter {private static int counter = 0;public static int getCount(){return counter;}public static synchronized void add(){counter = counter + 1;}
}1.2.3.4.5.6.7.8.9.10.

再次运行程序,则输出结果如下:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

3、加锁机制

另外一种常见的同步方法就是加锁,比如 Java 中有一种重入锁 ReentrantLock,它是一种递归无阻塞的同步机制,相对于 synchronized 来说,它可以提供更加强大和灵活的锁机制,同时可以减少死锁发生的概率。示例代码如下:

package com.example.learn;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {private  static int counter = 0;private static final ReentrantLock lock = new ReentrantLock(true);public static int getCount(){return counter;}public static  void add(){lock.lock();try {counter = counter + 1;} finally {lock.unlock();}}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

再次运行程序,则输出结果如下:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

注意:Java 中还提供了读写锁 ReentrantReadWriteLock,这样可以进行读写分离,效率更高。

4、使用 Atomic 对象

由于锁机制会影响一定的性能,而有些场景下,可以通过无锁方式进行实现。Java 内置了 Atomic 相关原子操作类,比如 AtomicInteger,AtomicLong, AtomicBoolean 和 AtomicReference,可以根据不同的场景进行选择。下面给出示例代码:

package com.example.learn;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {private static final AtomicInteger counter = new AtomicInteger();public static int getCount(){return counter.get();}public static void add(){counter.incrementAndGet();}
}1.2.3.4.5.6.7.8.9.10.11.

再次运行程序,则输出结果如下:

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

5、无状态对象

前面提到,线程不安全的一个原因就是多个线程同时访问某个对象中的数据,数据存在共享的情况,因此,如果将数据变成独享的,即无状态(stateless)的话,那么自然就是线程安全的。而所谓的无状态的方法,就是给同样的输入,就能返回一致的结果。下面给出示例代码:

package com.example.learn;
public class Counter {public static int sum (int n) {int ret = 0;for (int i = 1; i <= n; i++) {ret += i;}return ret;}
}1.2.3.4.5.6.7.8.9.10.

6、不可变对象

前面提到,如果需要在多线程中共享一个数据,而这个数据给定值,就不能改变,那么也是线程安全的,相当于只读的属性。在 Java 中可以通过 final 关键字进行属性修饰。下面给出示例代码:

package com.example.learn;
public class Counter {public final int count ;public Counter (int n) {count = n;}
}1.2.3.4.5.6.7.

7、总结

前面提到了几种线程安全的方法,总体的思想要不就是通过锁机制实现同步,要不就是防止数据共享,防止在多个线程中对数据进行读写操作。另外,有些文章中说到,可以在变量前使用 volatile 修饰,来实现同步机制,但这个经过测试是不一定的,有些场景下,volatile 依旧不能保证线程安全。虽然上述是线程安全的经验总结,但是还是需要通过严格的测试进行验证,实践是检验真理的唯一标准。

点击关注,第一时间了解华为云新鲜技术~​

谈谈有什么方法可以快捷实现多场景下的线程安全相关推荐

  1. 基于模型预测人工势场的船舶运动规划方法,考虑复杂遭遇场景下的COLREG(Matlab代码实现)

  2. 谈谈String.intern方法

    谈谈String.intern方法 1. 首先明确什么是intern()方法? String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现. ...

  3. 网站推广方法之快捷网址推广方法

    今天姜成来讲解下快捷网址推广方法,因为其中涉及的推广方法非常丰富,所以可能无法面面俱到的讲解,只举其中最知名的几种方法介绍给大家. 快捷网址,听这个推广方式的名字就比较容易理解其中的含义,是利用一个比 ...

  4. word页眉横线怎么去掉,这3种方法简单快捷

    word页眉横线怎么去掉,这3种方法简单快捷 https://baijiahao.baidu.com/s?id=1631681505226955779&wfr=spider&for=p ...

  5. 最简单快捷的在win7下用pycharm安装Tensorflow的方法

    win7下用pycharm安装Tensorflow 最简单快捷的在win7下用pycharm安装Tensorflow的方法 这是一部关于在win7下用pycharm安装Tensorflow的血泪史!百 ...

  6. python使用imbalanced-learn的SMOTEENN方法同时进行上采样和下采样处理数据不平衡问题

    python使用imbalanced-learn的SMOTEENN方法同时进行上采样和下采样处理数据不平衡问题 机器学习中常常会遇到数据的类别不平衡(class imbalance),也叫数据偏斜(c ...

  7. python使用imbalanced-learn的SMOTETomek方法同时进行上采样和下采样处理数据不平衡问题

    python使用imbalanced-learn的SMOTETomek方法同时进行上采样和下采样处理数据不平衡问题 机器学习中常常会遇到数据的类别不平衡(class imbalance),也叫数据偏斜 ...

  8. CVPR 2019 | 旷视研究院提出Re-ID新方法VPM,优化局部成像下行人再识别

    全球计算机视觉三大顶级会议之一 CVPR 2019 将于当地时间 6 月 16-20 日在美国洛杉矶举办.届时,旷视研究院将带领团队远赴盛会,助力计算机视觉技术的交流与落地.在此之前,旷视每周会介绍一 ...

  9. 号和管道符号(|)在不同场景下的使用方法

    2019独角兽企业重金招聘Python工程师标准>>> 掌握连接各个命令之间的连接符号用法也是很重要的.实际上,命令的用法并不难,例如 mkdir.touch和 find 也分别可以 ...

最新文章

  1. 通讯接口:I2C和USART,SPI,CAN,USB2.0
  2. SGU - 507 启发式合并维护平衡树信息
  3. leetcode 4. 寻找两个有序数组的中位数,c语言
  4. Web开发入门疑问收集(不定期更新)
  5. 如何使用groff -me在Linux上格式化学术论文
  6. javascript该怎么学呢?值得收藏的学习Js之路
  7. JS魔法堂:LINK元素深入详解
  8. 完整记录一则Oracle 11.2.0.4单实例打PSU补丁的过程
  9. 黑龙江省谷歌高清卫星地图下载
  10. opencv python 常用方法
  11. Aspose.Pdf 转图片的正确姿势
  12. 云服务器功能检验方法
  13. PTGUI 全景图批量拼接
  14. Keil编译错误 error: L6050U: The code size of this image (99784 bytes) exceeds the maxim
  15. PHP页面间参数传递
  16. 云上OneNET智慧大棚
  17. 中国传媒大学计算机课程表,课 程 表 - 中国传媒大学
  18. 编译天嵌E8内核源码
  19. python jsonpath模块
  20. 用MyBatis来操作crud

热门文章

  1. cassandra 数据量_Cassandra和Spark的数据处理简介
  2. (23)HTTP 状态消息
  3. 前端:CSS/14/综合案例:传智首页
  4. 程序员秒懂的30个段子:看到第几个你笑了?
  5. HTML div元素
  6. 轨迹规划实现 tfaar_example2.7
  7. ROS笔记(20) Kinect仿真
  8. php绕过验证,PHP-Nuke绕过识别码验证漏洞
  9. oracle standby审计,监控oracle standby
  10. Golang 受欢迎的原因:大道至简