线程安全性是多线程开发中经常讨论的一个概念,是一个基本且很重要的概念,掌握了这个概念才能为后续沟通与开发铺扫除障碍。本人初学多线程作下总结,难免有误,还望本文能抛砖引玉。
1.什么是线程安全性?
定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
定义的核心就是“正确的行为”。
下面通过几个例子来理清线程安全的概念,首先看看下面的例子是否是线程安全的,如果不是线程安全那么为什么不安全。
例1:
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
例2:
public class Counting{
private long count=0;
public long increment() {
++count;
}
}
例3:
public class Singleton {
private static Singleton instance=null;
private Singleton(){}
public static Singleton getInstance() {
if(instance==null)
instance=new Singleton();
return instance;
}
}
例1中当客户端多个线程访问StatelessFactorizer 的同一个实例的service方法时,service能否表现正确的行为?
答案是能,因为多个线程访问service方法时,方法里面用到的临时变量,只有每个线程自己能访问到,也就是说线程之间根本没有发生数据共享,所以这个类是线程安全。为什么方法里面用到的临时变量只有每个线程自己能访问到,关于线程存储可以参考查阅java内存模型与线程。
这里StatelessFactorizer类是没有状态的,总结就是无状态对象一定是线程安全的。
例2中当客户端多个线程访问Counting的同一个实例的increment方法时,返回结果是否是正确的呢?
这里就不一定了,虽然++count看起来是一条很简单的语句,但是这个操作并非原子的,实际上,它包含了三个独立的操作:读取count的值,将值加1,然后将计算结果写入count。假设两个线程都做了前面两步还未做第三步,那么都做完第三步后结果肯定是不正确的。
例3大家应该不陌生,就是一个懒汉式的单例类。如果两个线程都调用了Singleton.getInstance(),返回的是否是同一个实例呢?答案是不一定。getInstance方法有三行条语句,假设有两个线程A、B。如果线程A执行了前两行后,这时CPU被线程B抢占了,因为此时instance==null仍然成立,那么线程B也能执行到第二行语句,这样最终两个线程返回的就不是同一个实例了,那么这个单例类在多线程环境下是错误的。
上面例2例3,在多线程环境下都不能表现出正确的行为,那么就不是线程安全的类。
举完几个例子,相信大家对多线程安全概念应该清晰些了。
2.如果你写的代码不是线程安全的,那么将造成难以预料的结果。那么如何能写出线程安全的代码呢?下面我作简单总结。
方式一就是加锁。加锁是最常用的方式,在java中Synchronized就是一个加锁的关键字。加了锁后,只有一把钥匙,哪个线程拿到了钥匙就能访问被锁住的代码。下面我们修改下例2:
public class Counting{
private long count=0;
//这里给increment整个方法加上锁,钥匙就是this对象.
public Synchronized long increment() {
++count;
}
}
在修改后的例2中,一次只能有一个线程进入increment方法,也就是说++count里面的三步操作会一次性完成,这样该方法就始终能返回正确的结果了。
方式二线程封闭。这是实现线程安全性的最简单方式之一。如果仅在单线程内访问数据,就不需要同步。线程封闭技术的一种常见应用是JDBC的Connection对象。JDBC规范并不要求Connection对象必须是线程安全的。在典型的服务器应用程序中,线程从连接池中获得一个Connection对象,并且用该对象来处理请求,使用完后再将对象返还给连接池。由于大多数请求(例如Servlet请求或EJB调用等)都是由单个线程采用同步的方式来处理,并且在Connection对象返回之前,连接池不会再将它分配给其他线程,因此,这种连接管理模式在处理请求时隐含地将Connection对象封闭在线程中。维持线程封闭性的一种更规范方法是使用ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
方式三是使用不可变对象。不可变对象一定是线程安全的。如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。线程安全性是不可变对象的固有属性之一。什么是不可变对象?当满足一下条件时,对象才是不可变的:
·对象创建以后起状态就不能修改。
·对象的所有域都是final类型。
·对象是正确创建的(在对象的创建期间,this引用没有逸出)。
所以能用final就用,这也是我们编写代码的基本规范。即使对象是可变的,通过将对象的某些域声明为final类型,仍然可以简化对状态的判断,因此限制对象的可变性也就相当于限制了该对象可能的状态集合。仅包含一个或两个可变状态的“基本不可变”对象仍然比包含多个可变状态的对象简单。通过将域声明为final类型,也相当于告诉维护人员这些域是不会变化的。
线程安全性就总结这么多了,欢迎大家指正与讨论。
分享到:
相关推荐
什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 各线程之间变量不可见,线程通信通过共享主...
总之,理解并处理多线程环境下的线程安全问题是提升C#应用程序稳定性和性能的关键。在使用List或其他非线程安全的数据结构时,要时刻警惕潜在的并发问题,并采取适当的同步措施,确保数据的一致性和完整性。
Servlet 线程安全问题是指在使用 Servlet 编程时,如果不注意多线程安全性问题,可能会导致难以发现的错误。Servlet/JSP 技术由于其多线程运行而具有很高的执行效率,但这也意味着需要非常细致地考虑多线程的安全性...
申明:不是原创,不是原创,只是转载。 这是一个来自网上的例子 ...用于测试QList的线程安全性,因原作者只给出源代码,没有给出测试结果,这里生成一个QT工程,打开即可编译,内部有ReadME.txt,简要说明
### Java并发中的线程安全性 #### 1. 引言 随着Java技术的发展以及多核处理器的普及,Java并发编程成为软件开发中的一个重要领域。Java并发控制问题是国内外学者研究的热点之一,特别是在J2SE 1.5版本中引入了`...
用思维导图将Java线程安全性相关基本概念联系起来
1.什么是线程安全性(what) 2.如何分辨一个类是否线程安全?(HOW) 3.为什么hashmap不安全 why 3.1 插入HashMap.put 3.1.1 HashMap 在扩容的时候 3.2 HashMap 在删除数据的时候 0.背景 经常会看到说HashMap是线程...
深入研究Servlet线程安全性问题...
首先,线程安全性关注的是对象在操作过程中的状态完整性。在单线程环境下,对象的操作不会被打断,状态变化是连续的,不会出现中间状态。然而,在多线程环境下,一个操作的执行可能会被另一个线程的操作所干扰,导致...
总结起来,理解HashMap的线程不安全性及其潜在问题,并根据实际需求选择合适的安全措施,是确保Java程序稳定运行的关键。无论是使用Collections.synchronizedMap()、ConcurrentHashMap还是避免在多线程环境中使用,...
在LabWindows/CVI中,实现线程安全变量可能需要使用特定的线程库函数,如`cvAtomicInc()` 和 `cvAtomicDec()`,它们提供原子性地增加或减少变量的值。此外,还可以使用`cvMutexVar`结构体来创建自定义的线程安全变量...
这些测试可能包括并发读写、竞争条件、死锁等问题的测试,通过运行这些测试并分析结果,我们可以理解在不同情况下局部变量是否能够保持其线程安全性。 总之,局部变量线程安全测试是一项重要的软件质量保证措施,...
现在我们来详细讨论`Action`以及其线程安全性。 首先,让我们区分两种可能的`Action`类型: 1. **`java.util.concurrent.Action`**:这个接口在Java标准库中并不存在,可能是用户自定义的一个接口。如果它包含了...
然而,仅仅确保事件处理程序列表的线程安全性还不够。在触发事件时,你也需要考虑线程安全。一个常见的做法是使用`lock`关键字或`Monitor`类来保护事件触发过程: ```csharp private object _eventLock = new ...
虽然提供了基本的线程安全性,但它们不是高度优化的并发解决方案,因为所有操作都需要全局锁定,可能导致性能瓶颈。 2. 并发集合(Concurrent Collections): Java的`java.util.concurrent`包提供了更为高效且...
首先,我们需要理解什么是线程安全。线程安全是指在多线程环境下,当多个线程同时访问同一资源时,该资源仍能保持正确的行为。在日志库中,线程安全通常涉及到对日志文件的并发写入和日志级别的同步控制。 在`Log....
在Java中,线程安全问题通常与并发、内存模型和可见性有关。Java内存模型(JMM)定义了如何在多线程环境下共享数据的规则,确保线程之间的正确交互。 线程安全可以分为三种类型: 1. 不安全:当多个线程访问共享...
易语言提供了原子加减、原子比较并交换(CAS)等原子操作,确保了这些操作的线程安全性。 读写锁则是一种更高级的同步机制,它允许多个线程同时读取共享资源,但只允许一个线程进行写入。这种锁的特性提高了多线程...
首先,我们要理解什么是线程安全。线程安全是指当多个线程同时访问一个对象或方法时,代码仍然能够正确执行,不会出现数据混乱或者异常的情况。在C#中,实现线程安全通常有几种策略:锁(Lock)、Monitor、Mutex、...