什么是线程安全性?

定义:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么久称这个类时线程安全的。

解释:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,

并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

  • 在线程安全类中封装了必要的同步机制,因此客户端无需进一步采取同步措施。

实例:一个无状态的Servlet

@ThreadSafe
public class SafeThreadTest implements Servlet{
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        encodeIntoRespose(resp,factors);
    }
}

这个Servlet将请求参数经过提取数值,然后转换后再封装到Servlet的响应中。

这个Servlet与大多数的Servlet相同,SafeThreadTest是无状态的,它不包含包域,也不包含对其他类的域的引用。

计算的过程中的局部变量只存在执行的线程的栈上,访问SafeThreadTest不会影响同一访问该Servlet的另一个线程

的计算结果。所以无状态的对象一定是线程安全的。

实例:一个有状态的Servlet

@NotThreadSafe
public class UnsafeThreadTest implements Servlet{
    private long count=0;

    public long getCount(){return count;}

    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        count++;
        encodeIntoRespose(resp,factors);
    }
}

我们增加一个统计请求数量,给这个Servlet增加了一个状态,UnsafeThreadTest并非是一个线程安全的,尽管它在单线程中能正确运行。

这个类很可能丢失一些更新操作,看上去这是一个操作,但是这个操作丢失了原子性。实际上它包含了三个独立的操作:读取count,将值+1,

然后将结果写入count,这是一个“读取-->修改-->写入”的操作序列,并且其结果取决于之前的状态 。

  如果在某些情况下,多个线程同时读取到值为10,当都加1,写入11.这样数值将会偏差好多。在并发编程中,这种由于不恰当的执行时序而出现

不正确的结果是一种非常重要的情况,它有一个正式的名字:竞争条件(Race Codition)

竞争条件:当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞争条件(就是说正确的结果要取决于运气)。最常见的竞争类型就是

“先检查后执行(Check-Then-Act)操作”,即通过一个可能失效的观测的结果来决定于下一步动作。

实例:延迟初始化中的竞争条件

@NotThreadSafe
public class LazyInitRace{
  private ExprensiveObject instance =null;

  public   ExprensiveObject getInstance(){
    if(instance==null)
        instance=new ExprensiveObject();
        return instance;
    }

}    

如果两个线程同时执行,都产生一个实例,这样实例完全不同了。与大多数并发错误一样,竞争条件并不是总是产生错误的,还需要某种不恰当的执行时序。

复合操作:以上的两个含有竞争条件的实例,都需要包含一组需要以原子方式执行(或者说不可分割)的操作。

原子性操作:操作A和B,如果从执行A的线程来看,当另一个线程执行B,要么将B完全执行完,要么完全不执行B,那么A和B对彼此来说是原子的。

我们使用AtomicLong类型变量来统计已经处理请求数量

@ThreadSafe
public class CountSafeThreadTest implements Servlet{
    private final AtomicLong count=new AtomicLong(0);

    public long getCount(){return count.get();}

    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        count.incrementAndGet();
        encodeIntoRespose(resp,factors);
    }
}

AtomicLong以用原子方式更新的 long 值。有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范。

在实际情况中,应该尽可能使用现有的线程安全类来管理类的状态。

加锁机制:当在Servlet中添加一个状态变量时,可以通过线程安全的对象来管理Servlet的状态来维护Servlet的线程安全性,如果想在Servlet添加更多的状态,那么是否只需要

添加更多的线程安全状态变量就足够了?

  要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

内置锁:java提供了一种内置的锁机制来支持原子性:同步代码块。

(1)以synchronized来修饰的方法就是横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在对象。

(2)静态的synchronized方法以Class对象作为锁。(在类加载时,HotSpot虚拟机是在方法区产生java.lang.Class对象)

  synchronized(lock){//访问或修改由锁保护的共享的状态}

每个java对象都可以用作一个实现同步锁,这些锁被称为内置锁或监视器锁。线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。

获取内置锁的唯一途径就是进入这个锁保护的同步代码块或同步方法。java的内置锁相当于一种互斥体,最多只有一个线程持有这个锁。由于每次只有一个

线程执行内置锁保护的代码块。因此,由这个锁保护的同步代码块会以原子性方式执行。并发环境中的原子性和事务应用程序中的原子性有着相同的含义:

一组语句作为一个不可分割的单元被执行。

重入:当某个线程请求一个由其它线程持有的锁时,发出请求的线程的就会阻塞。然而,由于内置锁是可以重入的,因此如果某个线程试图获得一个已经有它自己持有

的锁,那么这个请求就会成功。“重入”意味着获取锁的操作粒度是“线程”,而不是调用。

对于可能被多个线程同时访问的可变状态变量时,在访问它时需要持有同一个锁,在这种情况下,我们称这个状态变量时由这个锁保护的。

每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。

对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要同一个锁来保护。

当执行较长时间的计算或者无法快速完成的操作时(例如:网络I/O或者控制台I/O),一定不要持有锁。

时间: 2024-11-03 20:15:26

什么是线程安全性?的相关文章

JAVA并发编程实战 读书笔记(一)线程安全性

线程安全性   1.什么是线程安全 在线程安全的定义中,最核心的概念是正确性.正确性的含义是,某个类的行为与规范完全一致.当对正确性有了一个比较清晰的定义后,就可以定义线程安全性:当多个线程访问某个类时,这个类始终能表现出正确的行为,那这个类就是线程安全的. 举例:无状态对象一定是线程安全的. 大多数Servlet都是无状态的,当Servlet在处理请求时需要保存一些信息时,线程安全才会成为一个问题. 2.原子性 举个例子:语句 ++i:虽然递增操作++i是一种紧凑的语法,使其看上去是一个操作,

java线程(二) - 线程安全性

前言: 要编写线程安全的代码,其核心在于要对状态访问的操作进行管理,特别是对共享的和可变的状态的访问. 当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误,有三种方式可以修复这个问题: 不在线程之间共享该状态变量 将状态变量修改为不可变的变量 在访问状态变量时使用同步 线程安全性的定义: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 那

VC和gcc在保证函数static变量线程安全性上的区别

VC和gcc不同,不能保证静态变量的线程安全性.这就给我们的程序带来了很大的安全隐患和诸多不便.这一点应该引起我们的重视!尤其是在构造函数耗时比较长的时候,很可能给程序带来意想不到的结果.本文从测试代码开始,逐步分析原理,最后给出解决方案. 多线程状态下,VC不能保证在使用函数的静态变量的时候,它的构造函数已经被执行完毕,下面是一段测试代码: class TestStatic { public: TestStatic() { Sleep(1000*10); m_num = 999; } publ

第二章:线程安全性——java并发编程实战

一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问状态变量时使用同步机制 完全由线程安全类构造的程序也不一定是线程安全的,线程安全类中也可以包含非线程安全的类 一.什么是线程安全性 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的.(因为线程安全类中封装了必要的同步代码) 一个无状态的类是线程安全的.无状态类是指不

并发编程初探-线程安全性

在Java并发编程中,对于线程安全是非常重要的,也是必须要考虑的一个问题.可以这么说,只要涉及到网络的,都必须考虑线程安全问题.好了,开始噼里啪啦地开始敲代码之前,我觉得有必要了解一些文绉绉的理论知识,因为这些理论知识是我们敲出来的代码是否是线程安全的一个依据. 当多个线程访问某个状态变量并且其中有一个线程执行写入操作的时候,必须考虑采用同步机制来协同这些线程对变量的访问,Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,但"同步"这个术语还包括类型的变量,显

Java并发编程学习笔记(一)——线程安全性

1.当多个线程访问某个状态变量并且其中有一个献策灰姑娘执行写入操作时,必须采用同步机制来协同这些线程对变量的访问.Java中的主要同步机制是关键字synchronized,他提供了一种独占的加锁方式. 2.在任何情况下,只有当类中仅包含自己的状态时,线程安全类才是有意义的. 3.当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些献策灰姑娘讲如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的. 4.无状态对象一定是线程安全的

《Java并发变成实践》读书笔记---第二章 线程安全性

什么是线程安全性 要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问.从非正式的意义上来说,对象的状态是指存储在状态变量(例如实例或静态域)中的数据."共享"意味着变量可以由多个线程同时访问,而"可变"则意味着变量的值在其生命周期内可以发生变化.所以编写线程安全的代码更侧重于如何防止在数据上发生不受控的并发访问. 如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误

并发基础知识 — 线程安全性

前段时间看完了<并发编程的艺术>,总感觉自己对于并发缺少一些整体的认识.今天借助<Java并发编程实践>,从一些基本概念开始,重新整理一下自己学过并发编程.从并发基础开始,深入进去,系统学习一下并发编程. 编写线程安全的代码,核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问.对象的状态是指存储在状态变量(实例或静态域)中的数据.对象的状态还可能包括其他依赖对象的域.(Map.Entry) 一个对象是否需要时线程安全的,取决于该对象

并发编程之线程安全性

一.什么是线程安全性 并发编程中要编写线程安全的代码,则必须对可变的共享状态的访问操作进行管理. 对象的状态就是存储在实例或者静态变量中的数据,同时其状态也包含其关联对象的字段,比如字典集合既包含自己的状态, 也包含KeyValuePair. 共享即可以多个线程同时访问变量,可变即变量在其声明周期内可以发生变化. 代码线程安全性关注的是防止对数据进行不可控的并发访问. 是否以多线程的方式访问对象,决定了此对象是否需要线程安全性.线程安全性强调的是对对象的访问方式,而不是对象 要实现的功能.要实现