什么是线程安全?

Posted 洋柿子0826

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是线程安全?相关的知识,希望对你有一定的参考价值。

线程安全

“线程安全”的定义:

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

该定义要求线程安全的代码都必须具备一个共同特征:
代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程下的调用问题,更无须自己实现任何措施来保证多线程环境下的正确调用。

java语言中的线程安全

线程安全,将以多个线程之间存在共享数据访问为前提。为了更深入地理解线程安全,我们不把线程安全当作一个非真即假的二元排他选项来看待,而是按照线程安全的“安全程度”由强至弱来排序,可以将java语言中各种操作共享的数据分为以下五类:

  • 不可变
  • 绝对线程安全
  • 相对线程安全
  • 线程兼容
  • 线程对立

不可变

在java语言里(JDK5之后),不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再进行任何线程安全保障措施。

“final关键字带来的可见性”提到过:只要一个不可变的对象被正确的构建出来(即没有发生this引用逃逸的情况),那其外部的可见状态永远都不会改变,永远都不会看到它在多个线程之中处于不一致的状态。“不可变”带来的安全性是最直接,最纯粹的。

java语言中,如果多线程共享数据分为两类:

  • 基本数据类型,只要在定义时使用final关键字修饰它就可以保证它是不可变的。
  • 对象类型,需要对象自行保证其行为不会对其状态产生任何影响。
    • 可以类比java.lang.String类的对象实例,它是一个典型的不可变对象,用户调用它的substring()、replace()和concat()这些方法都不会影响它原来的值,只会返回一个新构造的字符串对象。

保证对象行为不影响自己状态的途径有很多种,最简单的一种就是把对象里面带有状态的变量都声明为final,这样构造函数结束之后,它就是不可变的,如下java.lang.Integer构造函数,通过将内部状态变量value定义为final来保障状态不变。

 /**
     * The value of the @code Integer.
     *
     * @serial
     */
    private final int value;

    /**
     * Constructs a newly allocated @code Integer object that
     * represents the specified @code int value.
     *
     * @param   value   the value to be represented by the
     *                  @code Integer object.
     */
    public Integer(int value) 
        this.value = value;
    

java类库API中符合不可变要求的类型:

  • java.lang.String。
  • 枚举类型。
  • java.lang.Number的部分子类。
    • Long和Double等数值包装类型。
    • BigInteger和BigDecimal等大数据类型。

例外:同为Number子类型的原子类AtomicInteger和AtomicLong则是可变的。为啥这样设计?

绝对线程安全

绝对线程安全的定义是很严格的,一个类要达到“不管运行时环境如何,调用者都不需要任何额外的同步措施”可能需要付出非常高昂的,甚至不切实际的代价。在java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。我们可以通过java API中一个不是“绝对线程安全”的“线程安全类型”来看看这个语境里的“绝对”究竟是什么意思。

Java.util.Vector是一个线程安全的容器,因为它的add()、get()和size()等方法都是被synchronized修饰的,尽管这样效率不高,但保证了具备原子性、可见性和有序性。不过,即使它所有的方法都被修饰成synchronized,也不意味着调用它的时候就永远都不再需要同步手段了。

package com.example.xuniji;

import java.util.Vector;

/**
 * @ClassName VectorTest
 * @Author jia_xx
 * @Date 2022/5/27 21:22
 */
public class VectorTest 
    private static Vector<Integer> vector = new Vector<Integer>();

    public static void main(String[] args) 
        while (true) 
            for (int i = 0; i < 10; i++) 
                vector.add(i);
            

            Thread removeThread = new Thread(new Runnable() 
                @Override
                public void run() 
                    for (int i = 0; i < vector.size(); i++) 
                        vector.remove(i);
                    
                
            );

            Thread printThread = new Thread(new Runnable() 
                @Override
                public void run() 
                    for (int i = 0; i < vector.size(); i++) 
                        System.out.println(vector.get(i));
                    
                
            );

            removeThread.start();
            printThread.start();

            while (Thread.activeCount() > 20) 
            
        
    


Exception in thread "Thread-1007" Exception in thread "Thread-1008" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9
	at java.util.Vector.get(Vector.java:751)
	at com.example.xuniji.VectorTest$2.run(VectorTest.java:32)
	at java.lang.Thread.run(Thread.java:748)
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7
	at java.util.Vector.remove(Vector.java:834)
	at com.example.xuniji.VectorTest$1.run(VectorTest.java:23)
	at java.lang.Thread.run(Thread.java:748)

尽管这里使用到的Vector的get()、remove()和size()方法都是同步的,但是在多线程的环境中,如果不在方法调用端做额外的同步措施,使用这段代码仍然是不安全的。因为如果另一个线程恰好在错误的时间里删除了一个元素,导致序号i已经不再可用,再用i访问数组就会抛出一个ArrayIndexOutOfBoundsException异常。

如果要保证这段代码能正确执行下去,我们不得不把removeThread和printThread定义成如下所示:

Thread removeThread = new Thread(new Runnable() 
    @Override
    public void run() 
        synchronized (vector) 
            for (int i = 0; i < vector.size(); i++) 
                vector.remove(i);
            
        
    
);

Thread printThread = new Thread(new Runnable() 
    @Override
    public void run() 
        synchronized (vector) 
            for (int i = 0; i < vector.size(); i++) 
                System.out.println(vector.get(i));
            
        
    
);

假如Vector一定要做到绝对的线程安全,那就必须在它内部维护一组一致性的快照访问才行,每次对其中元素进行改动都要产生新的快照,这样要付出的时间和空间成本都是非常大的。

相对线程安全

相对线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单次的操作是线程安全的,我们在调用的时候不需要进行额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。以上代码(Vector)就是相对线程安全的案例。

在java语言中,大部分声称线程安全的类都属于这种类型,例如Vector、HashTable、Collections的synchronizedCollection()方法包装的集合等。

线程兼容

线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。平常我们说一个类不是线程安全的,通常就是指这种情况。Java类库API中大部分的类都是线程兼容的,比如ArrayList和HashMap。

线程对立

线程对立是指不管调用端是否采取了同步措施,都无法在多线程环境中并发使用代码。java语言天生就支持多线程的特性,线程对立这种排斥多线程的代码是很少出现的,通常都是有害的,应当尽量避免。

一个线程对立的例子是Thread类的suspend()和resume()方法。如果有两个线程同时持有一个线程对象,一个尝试去中断线程,一个尝试去恢复线程,在并发进行的情况下,无论调用时是否进行了同步,目标线程都存在死锁风险。

什么是线程安全,实现线程安全都有哪些方法

自己学习的时候做了一些笔记,希望对你有帮助


当一个类已经很好的同步以保护它的数据时,这个类就称为“线程安全的”---我没有跑题...


5.线程的同步与死锁

1.什么是同步

通过synchronized关键字标识方法或者代码块,限制线程对其内容的操作(同步详细介绍参见 .)

2.为什么要同步

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),

将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,

从而保证了该变量的唯一性和准确性.

3.进行同步的格式

同步代码块

synchronized (同步的线程对象)

需要同步的代码块;

同步方法

synchronized 其他访问修饰符返回值方法名称()

   方法内容

(synchronized也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类)

4.什么是死锁

死锁是进程死锁的简称,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。它是计算机操作系统乃至并发程序设计中最难处理的问题之一

死锁的解决

(死锁详细介绍参见进程死锁及解决办法.docx)

5.注意点

1.同步是一种高开销的操作,因此应该尽量减少同步的内容。

  通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。



参考技术A

什么是线程安全?

    定义:指代码能够被多个线程调用而不会产生灾难性后果;

    特点:不要求代码在多个线程中高效的运行,只要求能够安全地运行;

方法案例:

1. 使用 synchronized 关键字来获取锁

public class MaxScore
int max;
public MaxScore()
max = 0;

public synchronized void currentScore(int s)
if(s> max)
max = s;


public int max()
return max;


2. 添加另一个方法

public synchronized void reset()
max = 0;


3. 两个独立的同步方法

import java.util.*;
public class Jury
Vector members;
Vector alternates;
public Jury()
members = new Vector(12, 1);
alternates = new Vector(12, 1);

public synchronized void addMember(String name)
members.add(name);

public synchronized void addAlt(String name)
alternates.add(name);

public synchronized Vector all()
Vector retval = new Vector(members);
retval.addAll(alternates);
return retval;



线程

线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。

参考技术B http://blog.csdn.net/haolongabc/article/details/7249098

以上是关于什么是线程安全?的主要内容,如果未能解决你的问题,请参考以下文章

Java线程面试题

Java线程面试题

Java线程面试题

1.29号Java复习题目(针对面试示例版尝试(未分类))

java线程安全和线程不安全,高级Java开发必看

Java线程安全和非线程安全