线程安全性

xiaoxiao2021-03-01  11

                                              线程安全性

什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为。 在线程安全类中封装了必要的同步机制,因此客户端无须进一步采取同步措施。 示例:一个无状态的Servlet 无状态对象一定是线程安全的。

原子性

如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性。

竞态条件

在并发编程中,由于不恰当的执行时序而出现不正确的结果,叫做竞争条件。 当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。最常见的竞态条件类型是“先检查后执行”操作即通过一个可能失效的观测结果来决定下一步的动作。

延迟初始化中的竞态条件

目的是将对象的初始化操作推迟到实际被使用时才进行,同时要确保只初始化一次。

public class LaxyInitRace{ private ExpensiveObject inatance = null; public ExpensiveObject getInatance() { if(instance == null) { instance = new ExpensiveObject; }else { return instance; } } }

 

复合操作

为了确保线程安全性,“先检查后执行”和“读取-修改-写入”等操作必须是原子的,将它们等操作称为复合操作。 通过线程安全类AtomicLong来管理计数器的状态,从而确保了代码的线程安全性。  

import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; public class CountingFactorizer implements Servlet{ private final AtomicLong count = new AtomicLong(0); public long getCount(){ return count.get(); } public void service(ServletRequest req, ServletResponse resp) { BigInteger i= extractFromReuest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeINtoResponse(resp, factors); } }

加锁机制:内置锁 和 重入 同步块代码(Synchronized Block),包括两个部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。 synchronize(lock){   //访问或者修改为锁保护的共享状态 } 内置锁(Intrinsic Lock)或监视器锁(Monitor Lock):每个Java对象都可以用来做一个实现同步的锁。  

import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; public class CountingFactorizer implements Servlet{ private BigInteger lastumber; private BigInteger[] lastFactors; public sybchronized void service(ServletRequest req, ServletResponse resp) { BigInteger i= extractFromReuest(req); if(i.equals(lastNumber)){ encodeINtoResponse(resp, lastFactors); }else{ BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeINtoResponse(resp, factors); } } }

重入:如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功,访问其他线程持有的锁仍然线程会阻塞。

public class Widget{ public synchronized void doSomething(){ ... } public class LoggingWidget extends Widget{ public synchronized void doSomething(){ System.out.println(toString() + "calling doSomething"); super.doSomething(); } } }

                                          如果内置锁不是可重入的,那么这段代码将发生死锁。 用锁来保护状态:由于锁能使其保护的代码路径以串行形式来访问,因此可以通过锁来构造一些协议以实现对共享状态的独占式访问,只要始终遵循这些协议,就能确保状态的一致性。 活跃性与性能: 过多的在方法前添加Synchronized同步方式,代码的执行性能将非常糟糕。 措施:可以通过缩小同步代码块的作用范围,但要确保同步块不要过小,并且不要将本应在是原子的操作拆分到多个同步代码块种,应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出来,从而在这些操作的执行过程中,其他线程可以访问共享状态。 下面程序将之前 CountingFactorizer的代码改成两个独立的同步代码块,每个同步代码块包含一小段代码。其中一块负责保护判断是否只需要返回缓存结果的“先检查后执行”操作序列,另一块负责确保对缓存的数值和因数分解结果进行同步更新。还要重新引入“命中计数器”和“缓存命中”计数器。

import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.math.BigInteger; import java.util.concurrent.atomic.AtomicLong; public class CountingFactorizer implements Servlet{ private BigInteger lastumber; private BigInteger[] lastFactors; private long hits; private long cacheHits; public synchronized long getHits(){return hits;} public synchronized double getCacheHitRatio(){ return (double)cacheHits/(double)hits; } public void service(ServletRequest req, ServletResponse resp) { BigInteger i= extractFromReuest(req); BigInteger[] factors = null; synchronized (this){ ++hits; if(i.equals(lastNumber)){ ++cacheHits; factors = lastPactors.clone(); } } if(factors == null){ factors = factor(i); synchronized (this){ lastNumber = i; lastFactors = factors.clone(); } } encodeINtoResponse(resp, factors); } }

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

转载请注明原文地址: https://www.6miu.com/read-3349949.html

最新回复(0)