除了Synchronized关键字还有什么可以保证线程安全?

    日常使用Java开发时,多线程开发,一般就用Synchronized保证线程安全,防止并发出现的错误和异常,那么

除了Synchronized关键字还有什么可以保证线程安全吗?

什么是线程安全?

    在了解什么方法可以保证线程安全之前,我们先定义什么是线程安全。Wikipedia是如此定义的:

线程安全是程式设计中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的

共享变量,使程序功能正确完成。

按我的理解,在多线程环境下程序功能正确完成,说白了就是无论单线程或者多线程环境下,这个程序运行要完全

实现了程序设计时功能的需求,没有BUG。所以为了没有BUG,我们必须保证单或者多线程环境下代码运行的线程

安全。

不考虑线程安全问题行不行?

    有人会说,不要多线程行不行,我只写单线程代码不就可以了,单线程总不会有线程安全问题了吧。假如只有单

线程,那么访问一个网页就会出现类似排队买票的场景,一个请求返回了才能继续处理下一个请求,效率非常之低。

利用多线程,可以充分利用多核CPU资源,提高性能,特别是存在计算密集型、IO密集型任务。

    那么开多进程(process),一个进程对应一个线程行不行?这种情况下,基于Java语言的程序运行,每次访问一

个请求就需要开启一个新的JVM,再来一次class加载等等,包括JVM的优化、缓存、预热之类的优化优势全部没了。

    那么只开一个线程,一个线程对应多个协程(coroutine)或者微线程(fiber)行不行? 首先Java语言本身没有提供原

生的协程实现,其次即便实现了了,只用单线程还是无法充分利用多核CPU资源,性能也会达到瓶颈。

    说到底为了性能,使用多线程有益,代价就是对于一个共享并且状态可变的资源而言,只要存在竞争条件(race

condition),线程安全问题就必须纳入开发代码时的考虑范围。

什么情况下才不线程安全?

    为了避免线程不安全,我们必须先了解什么情况下才会出现线程不安全的问题。一个变量只在线程内使用,这种

情况下需要考虑线程安全问题吗?不需要,只有对需要在多个线程之间共享的变量的访问操作,我们才需要考虑线程

安全问题。所有线程共享的变量都需要考虑吗?不是,如果一个变量初始化后就不再变化,即变量为不可变状态,我

们也无需考虑。所以,只有线程之间存在对共享可变的对象访问操作时,才会出现线程安全问题。

如何保证线程安全?

不共享

    正如前文所说,只有需要操作共享的可变对象时会出现线程安全问题,那么只要不共享对象不就就可以保证线程

安全了吗。方法在于要确保对象本身或者其引用不会被共享出去,例如局部变量肯定是不会暴露出去,也可以利用

ThreadLocal保存并传递对象。

不可变(immutable)

    如果必须共享,那么对象为不可变状态,也能保证线程安全。什么情况下对象才是不可变的呢?对象构造完后所

有属性都不会再发生改变就是不可变。以A类为例,

public class A {
private int fieldA;
private int[] fieldB; public A(C c) {
c.setD(new D());
// 初始化
} public void setFieldA(int a) {
fieldA = a;
} public int[] getFieldB() {
return fieldB;
} private void updateA() {
//修改内部状态
} class D {
public void updateD() {
updateA();
}
}
}

首先需要将属性变为final,保证构造函数初始化后其他属性不会发生变动,防止属性状态会被更改。

public class A {
private final int fieldA;
private final int[] fieldB; public A(C c) {
c.setD(new D());
// 初始化
} public int[] getFieldB() {
return fieldB;
} private void updateA() {
//修改内部状态
} class D {
public void updateD() {
updateA();
}
}
}

其次防止内部的可变对象引用暴露出去。

public class A {
// 置为final状态
private final int fieldA;
private final int[] fieldB; public A(C c) {
c.setD(new D());
// 初始化
} public int[] getFieldB() {
if (fieldB == null) {
return null;
}
// 防止引用暴露,外部可以修改fieldB状态
return Arrays.copyOf(fieldB, fieldB.length);
} private void updateA() {
//修改内部状态
} class D {
public void updateD() {
updateA();
}
}
}

最后构造函数时要防止A类的this逃逸(this escape),防止构造函数还没有完成初始化时,其他线程可以利用逃逸的

this调用内部方法。

public class A {
// 置为final状态
private final int fieldA;
private final int[] fieldB; public A(C c) {
// updateD方法可能在c被其他线程调用,间接调用updateA方法,修改了Class A实例的状态
c.setD(new D());
// 初始化
} public int[] getFieldB() {
if (fieldB == null) {
return null;
}
// 防止引用暴露,外部可以修改fieldB状态
return Arrays.copyOf(fieldB, fieldB.length);
} private void updateA() {
//修改内部状态
} class D {
// 去除updateD方法
}
}

单线程修改

    对象必须共享且是可变的,如果只会被其中一个线程修改,其他线程只是读取,那么只需要对该对象其属性状态添加volatile

关键字修饰,保证内存可见性,其他线程能读取到对象的最新状态,这样也可以保证线程安全。

将对象属性修改为线程安全类型

public class A {
private Map<Integer, B> fieldB; public A() {
fieldB = new HashMap<>();
} public B getB(Integer key) {
return fieldB.get(key);
} public B addB(Integer key, B value) {
B b = fieldB.get(key);
if (b == null) {
fieldB.put(key, value);
}
return b;
}
}

将线程安全委托给属性对象,由属性对象保证线程安全

public class A {
private final Map<Integer, B> fieldB; public A() {
fieldB = new ConcurrentHashMap<>();
} public B getB(Integer key) {
return fieldB.get(key);
} public B addB(Integer key, B value) {
return fieldB.putIfAbsent(key, value);
}
}

使用synchronized关键字

    对于共享的可变对象,要求所有属性状态修改读取都用synchronized修饰

public class A {
private B fieldB; public synchronized B getB() {
return fieldB;
} public synchronized void updateB() {
// 其他操作
fieldB.update();
// 其他操作
}
}

可以优化使用内部的私有对象作为锁对象,避免锁住整个方法

public class A {
private B fieldB;
private final Object lock = new Object(); public synchronized B getB() {
return fieldB;
} public void updateB() {
// 其他操作
synchronized (lock) {
fieldB.update();
}
// 其他操作
}
}

除了Synchronized关键字还有什么可以保证线程安全?的更多相关文章

  1. 并发编程学习笔记(3)----synchronized关键字以及单例模式与线程安全问题

    再说synchronized关键字之前,我们首先先小小的了解一个概念-内置锁. 什么是内置锁? 在java中,每个java对象都可以用作synchronized关键字的锁,这些锁就被称为内置锁,每个对 ...

  2. 从线程池到synchronized关键字详解

    线程池 BlockingQueue synchronized volatile 前段时间看了一篇关于"一名3年工作经验的程序员应该具备的技能"文章,倍受打击.很多熟悉而又陌生的知识 ...

  3. Java并发—synchronized关键字

    synchronized关键字的作用是线程同步,而线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. synchronized用法 1. 在需要同步的方法的方法签名中加入synchro ...

  4. 【Java_多线程并发编程】基础篇——synchronized关键字

    1. synchronized同步锁的原理 当我们调用某对象的synchronized方法或代码块时,就获取了该对象的同步锁.例如,synchronized(obj)就获取了“obj这个对象”的同步锁 ...

  5. 多线程与高并发(三)synchronized关键字

    上一篇中学习了线程安全相关的知识,知道了线程安全问题主要来自JMM的设计,集中在主内存和线程的工作内存而导致的内存可见性问题,及重排序导致的问题.上一篇也提到共享数据会出现可见性和竞争现象,如果多线程 ...

  6. java高并发系列 - 第10天:线程安全和synchronized关键字

    这是并发系列第10篇文章. 什么是线程安全? 当多个线程去访问同一个类(对象或方法)的时候,该类都能表现出正常的行为(与自己预想的结果一致),那我们就可以所这个类是线程安全的. 看一段代码: pack ...

  7. java 线程及synchronized关键字

         从本篇开始,我们将会逐渐总结关于java并发这一块的内容,也可以理解为是我的笔记,主要来自于一些博客和java书籍中的内容,所有的内容都是来自于他们之中并且加上了我自己的理解和认识.     ...

  8. java基础回顾(五)线程详解以及synchronized关键字

    本文将从线程的使用方式.源码.synchronized关键字的使用方式和陷阱以及一些例子展开java线程和synchronized关键字的内容. 一.线程的概念 线程就是程序中单独顺序的流控制.线程本 ...

  9. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

随机推荐

  1. Johnson 全源最短路

    学这个是为了支持在带负权值的图上跑 Dijkstra. 为了这个我们要考虑把负的权值搞正. 那么先把我们先人已经得到的结论摆出来.我们考虑先用 SPFA 对着一个满足三角形不等式的图跑一次最短路,具体 ...

  2. Redis中的原子操作(2)-redis中使用Lua脚本保证命令原子性

    Redis 如何应对并发访问 使用 Lua 脚本 Redis 中如何使用 Lua 脚本 EVAL EVALSHA SCRIPT 命令 SCRIPT LOAD SCRIPT EXISTS SCRIPT ...

  3. 2021.05.03【NOIP提高B组】模拟 总结

    比较水的一场比赛,却不能 AK T1 有 \(n\) 次,每次给 \(A_i,B_i\) 问以 \(i\) 结尾的 \(A,B\) 的匹配中最大和的最小值 问最大和的最小值,却不用二分. 如果暴力排序 ...

  4. php 二维数组转换一维数组

    $result = array_reduce($res, function ($result, $value) { return array_merge($result, array_values($ ...

  5. JS:Boolean

    Boolean数据类型: 有两个值:true false Boolean会把不是Boolean的值变为Boolean值 var a = 1; var b = true; var c = 0; var ...

  6. SAP setting and releasing locks

    REPORT demo_transaction_enqueue MESSAGE-ID sabapdocu. TABLES sflight. DATA text(8) TYPE c. DATA ok_c ...

  7. 命令行工具tabby--gi t仓库Token的使用

    命令行工具tabby--git仓库Token的使用 欢迎关注H寻梦人公众号 前言 再见 Xshell !这款开源的终端工具逼格更高! 终端神器--Tabby Terminal electerm is ...

  8. Metasploit msfvenom

    一. msfvenom简介 msfvenom是msf payload和msf encode的结合体,于2015年6月8日取代了msf payload和msf encode.在此之后,metasploi ...

  9. Python 用configparser读写ini文件

    一.configparser 简介Python用于读写ini文件的一个官方标准库.具体详见官网链接 二.configparser 部分方法介绍 方法 描述 read(filenames) filesn ...

  10. vue this.getOptions is not a function

    错误提示截图: 问题原因:是由于sass-loader引用的版本过低导致 解决方法:在package.json中增加以下配置后 "sass-loader": "^10&q ...