【Java 并发】线程安全性
来源:互联网 发布:淘宝怎么搜店铺客服 编辑:程序博客网 时间:2024/06/05 06:49
【Java 并发】线程安全性
一个对象是否需要线程安全的,取决于他是否被多个线程访问,要对象是线程安全的,需要采用同步机制来协同对象可变状态的访问。
一,如果多个线程访问一个可变的状态变量时没有使用合适的同步机制,可以使用以下方法解决
1,不共享该状态变量。
2,将可变修改为不可变的变量。
3,在访问变量时使用同步。
线程安全的定义:多个线程访问某个类,这个类始终都能表现出正确的行为。
二,原子性
原子性一种很通俗的理解是。要么一次做完,不会中断,要么就不做。
1,原子操作
原子操作是不能被线程调度所中断的操作,一旦开始,那么它一定在可能发生线程切换之前完成。原子操作有除long和double之外的所有基本类型操作。因为JVM可以将64位的读写操作当做两个分离的32位操作来执行,这就可能产生在读写的过程中出现线程的切换。对域中的值做赋值和返回操作通常是原子操作。
看代码理解
public class UnsafeCountingFactorizer 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; encodeIntoResponse(resp, factors); }
++count;虽然看上去像是一个操作,但是这个操作非原子性,它不是一个不可分割的操作,实际上有三个独立的操作:读取-修改-写入,三种操作的结果都是依赖于前一个操作。举个栗子:A线程读取count=1,在要进行加1操作时,B线程正好读取count,此时的count=1,结果A,B线程运行的结果都是count=2。如果A,B线程是按顺序执行时,则A的结果是count=2,B的结果是count=3。整个计算正确性取决于多个线程的交替执行时序时,就会发生竞态条件。也就是说正确的结果取决于运气。
2,竞态条件
本质:基于一个可能失败的观察结果来做出判断或者执行操作。简单来说就是“先检查后执行”。++count也存在竞态,要根据前一个结果,再执行加1操作。
再看个例子
public class LazyInitRace { private ExpensiveObject instance = null; public ExpensiveObject getInstance() { if (instance == null) instance = new ExpensiveObject(); return instance; }}class ExpensiveObject { }
程序中的初始化操作推迟到实际使用时才进行,延迟初始化。程序中也存在竞态。假设A和B线程都调用 getInstance() ,A判断instance为空,于是new ExpensiveObject()实例。B线程也同样要判断instance是否为空,instance是否为空就取决于A线程创建对象的时间,若B线程判断判断instance为空,那么又new ExpensiveObject()实例,就存在两个实例了。
3,复合操作
之前两个程序++count和instance = new ExpensiveObject()都是复合操作,当然还有许多复合操作,这些操作都包含一组必须以原子方式执行的操作以确保线程安全性。先介绍一种方式:使用线程安全类。
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 = extractFromRequest(req); BigInteger[] factors = factor(i); count.incrementAndGet(); encodeIntoResponse(resp, factors); }}
在java.util.concurrent.atomic包中包含一些原子变量类,用于实现在数值和对象引用上的原子状态转换。 AtomicLong 代替Long。能够确保所有对计数器状态的访问操作都是原子的。
在平时编程时很少使用原子变量类,但是在性能调优时就大有用武之地。
三,加锁机制
先看段程序
public class UnsafeCachingFactorizer implements Servlet { private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>(); private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>(); public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber.get())) encodeIntoResponse(resp, lastFactors.get()); else { BigInteger[] factors = factor(i); lastNumber.set(i); lastFactors.set(factors); encodeIntoResponse(resp, factors); } }}
如果程序正常运行,程序中的lastNumber缓存的值应该等于lastFactors缓存的因数之积。所以这两个变量不是彼此独立的,是互相依赖的,lastNumber缓存的值改变则lastFactors缓存的因数也要同时做出相应改变。那么问题就来了,如果只修改的一个值,那么其他线程运行时就出问题了。还有一种可能,A线程正在读取两个值,而B线程在修改,那么就无法保证同时获取两个值。
Java提供了一种锁机制:同步代码块来解决这个问题。它包含两部分:锁的对象引用,有这个锁保护的代码块。用synchronized修饰的方法就是一个同步代码块。其中同步代码块的锁就是方法调用所在的对象。
synchronized(lock){//代码块}
修改成同步代码块
public class SynchronizedFactorizer extends GenericServlet implements Servlet { @GuardedBy("this") private BigInteger lastNumber; @GuardedBy("this") private BigInteger[] lastFactors; public synchronized void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); if (i.equals(lastNumber)) encodeIntoResponse(resp, lastFactors); else { BigInteger[] factors = factor(i); lastNumber = i; lastFactors = factors; encodeIntoResponse(resp, factors); } } }
锁机制还有一个重入问题
重入的一种实现方法是,为每个锁关联一个获取计数器和一个所有者线程,计数器为0时,这个锁没被任何线程持有,当线程请求一个未被持有的锁时,JVM记录锁的持有者,计数器加1,如果通过线程再次获取锁,则计数器相应增加。当线程退出同步代码块时,计数器递减。当计数器为0时,锁被释放。
使用锁机制时,要注意同个同步代码块只能由同一个所对象来保护。
以上都是阅读《Java编程思想》-并发和《Java并发编程实践》的笔记,不喜勿喷!
- 【Java 并发】线程安全性
- Java并发之线程安全性
- java并发编程:线程安全性
- Java并发编程-线程安全性
- java 并发编程第二节 线程安全性
- Java 并发编程之线程安全性
- 《Java并发编程实战》---线程安全性
- 《Java并发编程实战》---线程安全性---加锁
- 如何判断 Java 线程并发的安全性
- java并发2.0-线程安全性引言
- java并发2.1-什么是线程安全性
- java并发编程(一)线程安全性
- java并发编程实战之线程安全性
- Java并发编程(一)_线程安全性
- java并发编程实战笔记-线程安全性
- java并发编程实践(2)线程安全性
- JAVA并发编程之线程安全性
- java 并发编程实战 之 线程安全性
- leetcode: Exception: Type <class '__main__.ListNode'>: Not implemented
- Bootstrap折叠
- Mybatis学习笔记(四)【入门程序二】
- Maximum Length of Repeated Subarray
- JavaScript命名规范
- 【Java 并发】线程安全性
- 表单验证 二级联动 添加 删除
- Java for Web学习笔记(九十):消息和集群(5)利用websocket实现订阅和发布(上)
- laravel使用excel报错情况
- java面试需要掌握知识点
- lucene第一天
- Redis之 字符串(String)
- Python变量、自定义函数
- node和npm版本更新