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



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

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

package com.lkf.mulithread;

public class ThreadSafety {
public static void main(String[] args) throws InterruptedException { ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "线程1");
t1.start();
Thread t2 = new Thread(pt, "线程2");
t2.start();
//wait for threads to finish processing
t1.join();
t2.join();
System.out.println("执行次数:" + pt.getCount());
} static class ProcessingThread implements Runnable {
private int count; @Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count++;
}
} private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} public int getCount() {
return this.count;
} }
}

当多次执行之后,我们会发现,最终的结果是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来保证线程安全具有一定的性能成本,因此建议在必要的时候使用

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

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

事例分析

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

事例1

public class MyObject {

  // 锁定对象的监视器
public synchronized void doSomething() {
// ...
}
} // Hackers code
MyObject myObject = new MyObject();
synchronized (myObject) {
while (true) {
// 无限期循环
Thread.sleep(Integer.MAX_VALUE);
}
}

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

事例2

public class MyObject {
public Object lock = new Object(); public void doSomething() {
synchronized (lock) {
// ...
}
}
} //untrusted code MyObject myObject = new MyObject();
//change the lock Object reference
myObject.lock = new Object();

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

事例3

package com.lkf.mulithread;

import java.util.Arrays;

public class SyncronizedMethod {
public static void main(String[] args) throws InterruptedException {
String[] arr = {"我是1号", "我是2号", "我是3号", "我是4号", "我是5号", "我是6号"};
HashMapProcessor hmp = new HashMapProcessor(arr);
Thread t1 = new Thread(hmp, "线程t1");
Thread t2 = new Thread(hmp, "线程t2");
Thread t3 = new Thread(hmp, "线程t3");
long start = System.currentTimeMillis();
//start all the threads
t1.start();
t2.start();
t3.start();
//wait for threads to finish
t1.join();
t2.join();
t3.join();
System.out.println("Time taken= " + (System.currentTimeMillis() - start));
//check the shared variable value now
System.out.println(Arrays.asList(hmp.getMap()));
} static class HashMapProcessor implements Runnable { private String[] strArr = null; public HashMapProcessor(String[] m) {
this.strArr = m;
} public String[] getMap() {
return strArr;
} @Override
public void run() {
processArr(Thread.currentThread().getName());
} private void processArr(String name) {
for (int i = 0; i < strArr.length; i++) {
//process data and append thread name
processSomething(i);
addThreadName(i, name);
}
} private void addThreadName(int i, String name) {
strArr[i] = strArr[i] + ":" + name;
} private void processSomething(int index) {
// processing some job
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}

输出结果:

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

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

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

private Object lock = new Object();
private void addThreadName(int i, String name) {
synchronized(lock){
strArr[i] = strArr[i] +":"+name;
}
}

输出结果:

Time taken= 15015
[我是1号:线程t1:线程t2:线程t3,
我是2号:线程t1:线程t3:线程t2,
我是3号:线程t1:线程t3:线程t2,
我是4号:线程t1:线程t3:线程t2,
我是5号:线程t3:线程t2:线程t1,
我是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. Android获取网络时间的方法

    一.通过免费或者收费的API接口获取 1.免费 QQ:http://cgi.im.qq.com/cgi-bin/cgi_svrtime 淘宝:http://api.m.taobao.com/rest/ ...

  2. 【原创】大叔经验分享(69)docker启动java应用的时区问题

    在docker中启动tomcat或java类应用,获取时间默认是UTC时间,这是因为容器内的locale没有设置为东8区,最简单的方式是增加JAVA_OPTS 如果是java,直接在java命令后增加 ...

  3. WCF寄宿windows服务二

    如果有很多WCF服务需要寄宿,需要额外做一些工作:总体思路是:先把这些WCF服务的程序集打包,然后利用反射加载各个WCF服务的程序集,按顺序一个一个寄宿.先来看看我们需要寄宿的WCF服务: 实现步骤: ...

  4. linux core文件的打开和分析

    1. core文件生成打开方式: ulimit -c unlimited echo "1" > /proc/sys/kernel/core_uses_pid 看下服务器上是否 ...

  5. 分库分布的几件小事(四)分库分表的id主键生成

    1.问题 其实这是分库分表之后你必然要面对的一个问题,就是id咋生成?因为要是分成多个表之后,每个表都是从1开始累加,那肯定不对啊,需要一个全局唯一的id来支持.所以这都是你实际生产环境中必须考虑的问 ...

  6. 深入理解hadoop之排序

    MapReduce的排序是默认按照Key排序的,也就是说输出的时候,key会按照大小或字典顺序来输出,比如一个简单的wordcount,出现的结果也会是左侧的字母按照字典顺序排列.下面我们主要聊聊面试 ...

  7. 关于javascript的事件执行机制理解

    理解javascript事件执行机制 众所周知,js是一个单线程的语言,这意味着同一时间只能做一件事,但是我们又说js是异步的.首先,单线程并不是没有优点.作为浏览器脚本语言,JavaScript 的 ...

  8. vuex用法

    // 配置vuex的步骤 // 1. 运行 cnpm i vuex -S // 2. 导入包 import Vuex from 'vuex' // 3. 注册vuex到vue中 Vue.use(Vue ...

  9. DevOps与Kubernetes 、容器的关系

    近两年,随着容器.Kubernetes 等技术的兴起,DevOps 这个概念被广泛提及并被大量使用. 本文将会从以下几个方面着手,结合实验展现的方式,让读者真正理解 DevOps 的含义. DevOp ...

  10. $q defer

    实际应该是这样更好理解 把,asyncGreet换成实际的http请求SyncRequest. 如果我们多个请求那么 就是多个 promise我们要么就是链式then,要么就是$q.all 可以根据需 ...