前言

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 依旧不能保证线程安全。虽然上述是线程安全的经验总结,但是还是需要通过严格的测试进行验证,实践是检验真理的唯一标准。

最后

我这边整理了一份多线程相关资料文档,还有:Spring系列全家桶、Java的系统化资料:(包括Java核心知识点、面试专题和21年最新的互联网真题、电子书等)有需要的朋友可以关注公众号【程序媛小琬】即可获取。

如何只用5招实现多线程场景下的线程安全?相关推荐

  1. 5招教你实现多线程场景下的线程安全!

    摘要:多线程(并发)场景下,如何编写线程安全(Thread-Safety)的程序,对于程序的正确和稳定运行有重要的意义.下面将结合示例,谈谈如何在Java语言中,实现线程安全的程序. 本文分享自华为云 ...

  2. 谈谈有什么方法可以快捷实现多场景下的线程安全

    本文分享自华为云社区<如何只用5招实现多线程场景下的线程安全?>,作者: Java小叮当. 1.引言 当前随着计算机硬件的快速发展,个人电脑上的 CPU 也是多核的,现在普遍的 CUP 核 ...

  3. 多线程环境下的线程不安全问题(1)

    在不考虑多线程的情况下,很多类代码都是完全正确的,但是如果放在多线程环境下,这些代码就很容易出错,我们称这些类为 线程不安全类 .多线程环境下使用线程安全类 才是安全的. 下面是一个线程不安全类的例子 ...

  4. 多线程场景下利用ThreadLocal是线程安全?

    文章目录 背景 多线程场景测试代码 结论 背景 ThreadLocal原理以及基本概念这里我就不介绍了,这里我们主要关注ThreadLocal是否是线程安全吗?其实如果我们知道ThreadLocal原 ...

  5. 多线程场景下使用 ArrayList,这几点一定要注意!

    作者:雪山上的蒲公英 www.cnblogs.com/zjfjava/p/10217720.html ArrayList 不是线程安全的,这点很多人都知道,但是线程不安全的原因及表现,怎么在多线程情况 ...

  6. Linux下查看多线程进程下的线程

    Linux内核在执行程序时,将所有运行时信息写到了/proc/{pid} 目录下. 下面我们以以下python的2线程程序为例,详细说一下Linux系统下如何查看 1,某个进程有几个线程 2,某个进程 ...

  7. 线程间定制化调用通信—— 1 高内聚低耦合的前提下,线程操作资源类 2 判断/干活/通知 3 多线程交互中,必须要防止多线程的虚假唤醒,也即(判断只用while,不能用if)

    生产者与消费者模式 一个生产者与一个消费者 题目:现在有两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,另一个线程对该变量减1,这两个线程的操作加一.减一交替,进行10轮,变量的初始 ...

  8. 「高并发」亿级流量场景下如何实现分布式限流?

    分布式限流的关键就是需要将限流服务做成全局的,统一的.可以采用Redis+Lua技术实现,通过这种技术可以实现高并发和高性能的限流. Lua是一种轻量小巧的脚本编程语言,用标准的C语言编写的开源脚本, ...

  9. 【高并发】亿级流量场景下如何实现分布式限流?看完我彻底懂了!!(文末有福利)

    写在前面 在互联网应用中,高并发系统会面临一个重大的挑战,那就是大量流高并发访问,比如:天猫的双十一.京东618.秒杀.抢购促销等,这些都是典型的大流量高并发场景.关于秒杀,小伙伴们可以参见我的另一篇 ...

最新文章

  1. ASP.NET中WebForm组件CheckBoxList编程
  2. 网络编程学习笔记(shutdown函数)
  3. php 写一个大富翁游戏,C++大富翁代码 现在要设计一个类似“大富翁”的游戏:有一条由20个格子组成的 联合开发网 - pudn.com...
  4. Dockerfile 文件结构、docker镜像构建过程详细介绍
  5. 安卓手机能用吗_手机才用两年卡的不行,是手机问题吗,想问手机最长能用几年?...
  6. Android开发之软键盘遮盖EditText
  7. xp系统internet信息服务器地址,XP系统下Internet信息服务IIS的安装方法
  8. nbu mysql_mysql数据备份之NBU
  9. 如何用计算机计算微积分,高数从此不用怕?一键计算微积分的神App
  10. DOS窗口执行Jmeter测试脚本生成html报告
  11. php实现金币提现,PHP调用支付宝转账接口实现支付宝提现
  12. cmd批处理文件格式
  13. 失去池子的笑果文化越来越不好笑了
  14. js 系统教程-15-js 语法之命令行-console.log,console.info,console.error,console.warn,debugger
  15. SpringCloud快速上手
  16. 计算机开机显示器不亮,电脑显示屏不亮但是主机已开机怎么办 电脑显示屏不亮解决方法【图文】...
  17. 零件图的尺寸标注---机械制图
  18. 项目成本管理__重点内容_成本管理技术_挣值管理
  19. 睿智计算机科技,VR跨文化交际虚拟仿真实训教学系统 - 中科睿智教育科技
  20. 陌生人社交,止步于陌生人?

热门文章

  1. 利用链式哈希表解决哈希碰撞问题
  2. java无法下载更新文件,java – 在更新JProgressBar的同时下载文件
  3. WPF控件样式、模板
  4. 阿里巴巴公司 DBA笔试题及参考答案
  5. 计算机控制系统稳定性分析实验报告,自动控制实验报告一-控制系统的稳定性分析...
  6. 爬虫-携程酒店信息抓取降妖除魔(下)
  7. 人工智能原理复习 | 可分解产生式系统的搜索策略
  8. 工作第三周 : 程序猿的出世与入世
  9. Ijk播放器无法播放带空格或者中文的网络视频的解决方法
  10. 力扣每日一题:792. 匹配子序列的单词数【真没想到是一个二分】