翻译:https://www.journaldev.com/1061/thread-safety-in-java



我们知道线程安全在Java中是一个非常重要的主题,当多个线程操作共享数据时,如果没有任何控制,将会产生数据不一致的问题。数据不一致的重要原因是,当更新一个变量的值时,是分三步完成的,第一获取变量值,第二对变量值进行更新,第三将修改后的变量值刷新到内存。

下面我们用一个简单程序来验证一下这个问题,多线程去更新共享数据:

  1. package com.lkf.mulithread;
  2. public class ThreadSafety {
  3. public static void main(String[] args) throws InterruptedException {
  4. ProcessingThread pt = new ProcessingThread();
  5. Thread t1 = new Thread(pt, "线程1");
  6. t1.start();
  7. Thread t2 = new Thread(pt, "线程2");
  8. t2.start();
  9. //wait for threads to finish processing
  10. t1.join();
  11. t2.join();
  12. System.out.println("执行次数:" + pt.getCount());
  13. }
  14. static class ProcessingThread implements Runnable {
  15. private int count;
  16. @Override
  17. public void run() {
  18. for (int i = 1; i < 5; i++) {
  19. processSomething(i);
  20. count++;
  21. }
  22. }
  23. private void processSomething(int i) {
  24. // processing some job
  25. try {
  26. Thread.sleep(i * 1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. public int getCount() {
  32. return this.count;
  33. }
  34. }
  35. }

当多次执行之后,我们会发现,最终的结果是6或7或8,而不是我们的预期结果8,出现这种情况的原因就在于count++

保证线程安全

针对多线程环境下,线程安全问题,有以下几种方法来解决:

1.关键字Synchronization是使用最简单应用最广泛的确保线程安全的方式

2.使用原子包装类,位于java.util.concurrent.atomic包下,比如:AutomicInteger

3.使用锁,位于java.util.concurrent.locks包下

4.使用线程安全集合类,比如:ConcurrentHashMap

5.使用volatile关键字保证线程每次直接从内存中读取变量值,而不是从工作内存中读取

Synchronized

  • synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性.
  • synchronized有两种使用方式,一种是锁定方法,另一种是锁定代码块
  • 普通同步方法,锁的是当前实例对象,静态同步方法,锁的是当前类,因此最好的方法是,只锁定需要同步的代码块
  • synchronized(this)锁定是当前对象,因为使用synchronized来保证线程安全具有一定的性能成本,因此建议在必要的时候使用

下面是推荐的保证线程安全的使用方式

  1. //虚拟对象
  2. private Object mutex=new Object();
  3. ...
  4. //using synchronized block to read, increment and update count value synchronously
  5. synchronized (mutex) {
  6. count++;
  7. }

事例分析

下面我们一起分析一些保证线程安全的一些事例:

事例1

  1. public class MyObject {
  2. // 锁定对象的监视器
  3. public synchronized void doSomething() {
  4. // ...
  5. }
  6. }
  7. // Hackers code
  8. MyObject myObject = new MyObject();
  9. synchronized (myObject) {
  10. while (true) {
  11. // 无限期循环
  12. Thread.sleep(Integer.MAX_VALUE);
  13. }
  14. }

注意,同步代码试图锁定myObject实例,一旦它获得了锁,它就不会释放它导致doSomething()方法阻塞等待锁,这会导致系统陷入死锁并导致Denial of Service (DoS)

事例2

  1. public class MyObject {
  2. public Object lock = new Object();
  3. public void doSomething() {
  4. synchronized (lock) {
  5. // ...
  6. }
  7. }
  8. }
  9. //untrusted code
  10. MyObject myObject = new MyObject();
  11. //change the lock Object reference
  12. myObject.lock = new Object();

注意,锁对象是公共的,通过更改它的引用,我们可以在多个线程中执行同步块并行。这种方法是可行的,如果是私有锁对象,可以通过setter方法来修改它的引用

事例3

  1. package com.lkf.mulithread;
  2. import java.util.Arrays;
  3. public class SyncronizedMethod {
  4. public static void main(String[] args) throws InterruptedException {
  5. String[] arr = {"我是1号", "我是2号", "我是3号", "我是4号", "我是5号", "我是6号"};
  6. HashMapProcessor hmp = new HashMapProcessor(arr);
  7. Thread t1 = new Thread(hmp, "线程t1");
  8. Thread t2 = new Thread(hmp, "线程t2");
  9. Thread t3 = new Thread(hmp, "线程t3");
  10. long start = System.currentTimeMillis();
  11. //start all the threads
  12. t1.start();
  13. t2.start();
  14. t3.start();
  15. //wait for threads to finish
  16. t1.join();
  17. t2.join();
  18. t3.join();
  19. System.out.println("Time taken= " + (System.currentTimeMillis() - start));
  20. //check the shared variable value now
  21. System.out.println(Arrays.asList(hmp.getMap()));
  22. }
  23. static class HashMapProcessor implements Runnable {
  24. private String[] strArr = null;
  25. public HashMapProcessor(String[] m) {
  26. this.strArr = m;
  27. }
  28. public String[] getMap() {
  29. return strArr;
  30. }
  31. @Override
  32. public void run() {
  33. processArr(Thread.currentThread().getName());
  34. }
  35. private void processArr(String name) {
  36. for (int i = 0; i < strArr.length; i++) {
  37. //process data and append thread name
  38. processSomething(i);
  39. addThreadName(i, name);
  40. }
  41. }
  42. private void addThreadName(int i, String name) {
  43. strArr[i] = strArr[i] + ":" + name;
  44. }
  45. private void processSomething(int index) {
  46. // processing some job
  47. try {
  48. Thread.sleep(index * 1000);
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. }

输出结果:

  1. Time taken= 15015
  2. [我是1号:线程t1:线程t2:线程t3,
  3. 我是2号:线程t1:线程t2:线程t3,
  4. 我是3号:线程t1:线程t2:线程t3,
  5. 我是4号:线程t1,
  6. 我是5号:线程t2,
  7. 我是6号:线程t3:线程t2]

可以看到每个线程中数组数据不完整,是因为共享数据没有同步造成的

下面我们修改一下代码,来保证线程安全:

  1. private Object lock = new Object();
  2. private void addThreadName(int i, String name) {
  3. synchronized(lock){
  4. strArr[i] = strArr[i] +":"+name;
  5. }
  6. }

输出结果:

  1. Time taken= 15015
  2. [我是1号:线程t1:线程t2:线程t3,
  3. 我是2号:线程t1:线程t3:线程t2,
  4. 我是3号:线程t1:线程t3:线程t2,
  5. 我是4号:线程t1:线程t3:线程t2,
  6. 我是5号:线程t3:线程t2:线程t1,
  7. 我是6号:线程t3:线程t1:线程t2]

Java线程之synchronized的更多相关文章

  1. Java线程之Synchronized用法

    synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对 ...

  2. Java线程之 InterruptedException 异常

    Java线程之 InterruptedException 异常   当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法. 抛 ...

  3. Java多线程之synchronized(四)

    前面几章都是在说synchronized用于对象锁,无论是修饰方法也好修饰代码块也好,然而关键字synchronized还可以应用到static静态方法上,如果这样写,那就是对当前的*.java文件所 ...

  4. Java多线程之synchronized(三)

    在多线程访问同一个对象中的不同的synchronized方法或synchronized代码块的前提下,也就是“对象监控器”为同一个对象的时候,也就是synchronized的锁为同一把锁的时候,调用的 ...

  5. (二)java多线程之synchronized

    本人邮箱: kco1989@qq.com 欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco github: https://github.com/kco198 ...

  6. JAVA多线程之Synchronized关键字--对象锁的特点

    一,介绍 本文介绍JAVA多线程中的synchronized关键字作为对象锁的一些知识点. 所谓对象锁,就是就是synchronized 给某个对象 加锁.关于 对象锁 可参考:这篇文章 二,分析 s ...

  7. Java多线程之synchronized及其优化

    Synchronized和同步阻塞synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件.syn ...

  8. java基础---->多线程之synchronized(六)

    这里学习一下java多线程中的关于synchronized的用法.我来不及认真地年轻,待明白过来时,只能选择认真地老去. synchronized的简单实例 一. synchronized在方法上的使 ...

  9. JAVA多线程之Synchronized、wait、notify实例讲解

    一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...

随机推荐

  1. 快速上手小程序的mpvue框架

    一.什么是mpvue框架? mpvue 是一个使用 Vue.js 开发小程序的前端框架.框架基于 Vue.js 核心(所以建议熟练掌握vue再使用mpvue框架,否则还是建议去使用原生框架去写小程序) ...

  2. R语言学习笔记:读取前n行数据

    常规读取 一般我们读取文件时都会读取全部的文件然后再进行操作,因为R是基于内存进行计算的. data <- read.table("C:\\Users\\Hider\\Desktop\ ...

  3. 如何使用Jedis操作redis

    public class JredisTest { private static Jedis jedis = new Jedis("localhost", 6379); publi ...

  4. requests 抓取网站

    import requests from requests.exceptions import RequestException import re import json def get_one_p ...

  5. netty的断线重连问题

    手里的这个项目需要作为客户端,不断的接收服务端发来的数据,用的netty框架,但是一直存在一个问题,就是断线重连问题. 什么是断线重连呢? 就是我们这个客户端要保证一直与服务端保持连接,这样客户端才能 ...

  6. Docker镜像构建文件Dockerfile及相关命令介绍

    使用docker build命令或使用Docker Hub的自动构建功能构建Docker镜像时,都需要一个Dockerfile文件.Dockerfile文件是一个由一系列构建指令组成的文本文件,doc ...

  7. 【异常】Could not find artifact com.wm.****:

    1 详细异常 [ERROR] Failed to execute goal on project spark-etl: Could not resolve dependencies for proje ...

  8. linux tty终端个 pts伪终端 telnetd伪终端

    转:http://blog.sina.com.cn/s/blog_735da7ae0102v2p7.html 终端tty.虚拟控制台.FrameBuffer的切换过程详解 Framebuffer Dr ...

  9. 【wifi移植 1】 ap6210 wifi模块移植

    1. 编译wifi相关功能为模块,生成bcmdhd.ko:由bcmdhd.ko的模块信息可知,该模块依赖于cfg80211.ko和rfkill.ko. 2. 写脚本,开机自动加载wifi模块. 3. ...

  10. windows下用navicat链接虚拟机MySQL数据库的过程和问题解决

    navicat远程连接虚拟机中的MySQL数据库 1.在linux查看mysql服务器IP地址 ifconfig 记住此IP navicat设置 设置完毕 遇到问题 一直连不上,在网上搜索了一下,主要 ...