除了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. JNPF移动办公解决方案

    市场背景 随着办公自动化系统的普及,电子化.数据化的办公方式已进入越来越多的企业和政府单位,信息化的办公系统在企事业内部编织起一套高效.畅通的信息互联体系,极大推动了企事业单位生产力的发展.但与此同时 ...

  2. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  3. 花两万培训Java的三个同学,最后都怎么样了

    仙路尽头谁为峰,学完Java学Python. 前言 对于IT行业的培训,例如Java.大数据.H5等等,我一直保持着肯定的态度. 因为当年大学时期的我,也差点去参加Java培训.一是因为那时钱包空空, ...

  4. Java编码安全

    目录 Java编码安全 数据校验 规则1.1:校验跨信任边界传递的不可信数据 规则1.2:禁止直接使用不可信数据来拼接SQL语句 规则1.4:禁止直接使用不可信数据来记录数据 规则1.6:验证路径前将 ...

  5. python爬虫之JS逆向某易云音乐

    Python爬虫之JS逆向采集某易云音乐网站 在获取音乐的详情信息时,遇到请求参数全为加密的情况,现解解决方案整理如下: JS逆向有两种思路: 一种是整理出js文件在Python中直接使用execjs ...

  6. 纯css就能实现可点击切换的轮播图,feel起来很丝滑

    前言 轮播图经常会在项目里用到,但是实际上用到的轮播图都是比较简单的,没有复杂的特效,这个时候如果去引入swiper那些库的话,未免就有点杀鸡焉用牛刀了. 所以不如自己手写一个,而今天我要分享的一种写 ...

  7. IE让我首次遭受了社会的毒打

    2022年6月15日,微软终止对IE的支持,自此IE走入历史,可以说这是一个时代的终结. 自己在 2011 年刚从业时,IE 在国内的市场占有率可是遥遥领先的,下图来自于 StatCounter 网站 ...

  8. UiPathExcel读取操作

    一.Uipath操作Excel的相关基本概念 1.UiPath操作Excel的两组方法 App Integration > Excel   VS  System > File > W ...

  9. 不可思议的返回功能——python

    今天给大家分享 3 个比较冷门的知识.教程点这(https://jq.qq.com/?_wv=1027&k=zLK3I0M5) 第一个:神奇的字典键 (https://jq.qq.com/?_ ...

  10. SpringCloud微服务实战——搭建企业级开发框架(四十三):多租户可配置的电子邮件发送系统设计与实现

      在日常生活中,邮件已经被聊天软件.短信等更便捷的信息传送方式代替.但在日常工作中,我们的重要的信息通知等非常有必要去归档追溯,那么邮件就是不可或缺的信息传送渠道.对于我们工作中经常用到的系统,里面 ...