线程与锁模型
翁正存
做一名合格的工程师
展开
-
Java并发-对容器类的原子操作
我们知道很多Java容器类都是非线程安全的,比如常用的ArrayList就是非线程安全的,表现在并发场景下,经常出现莫名其妙的错误,看个例子:public class ConcurrentAccessListTest { public static List<Integer> LIST = new ArrayList<>(); static { ...原创 2018-09-15 16:42:01 · 303 阅读 · 1 评论 -
并发中的先验条件
先验条件:在执行某些逻辑前需要满足的变量状态。例如,不能从空队列中删除元素,在删除元素前,队列必须处于非空的状态。如果在某个操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作。在单线程中,某个操作无法满足先验条件,只能失败。但是在并发程序中,先验条件可能会由于其他线程执行的操作变成真。在并发程序中要一直等到先验条件为真,才能执行操作,可以利用BlockingQueue、Sem...转载 2019-02-02 11:09:02 · 435 阅读 · 0 评论 -
客户端加锁
对封装好的jar包来说,使用它的代码都是客户端。客户端加锁是指,对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户端代码。要使用客户端加锁,必须知道对象X使用的是哪一种锁。public class ListHelper<E> { public List<E> list = Collections.synchronizedList(...翻译 2019-02-02 14:34:07 · 596 阅读 · 0 评论 -
java.util.ConcurrentModificationException异常原因
当线程A迭代访问容器时,如果线程B调用remove()方法,会导致modCount和expectedModCount的值不一致,从而使checkForComodification方法抛出ConcurrentModificationException异常。public E next() { checkForComodification(); int i = cursor; ...翻译 2019-02-02 15:33:48 · 201 阅读 · 0 评论 -
Future使用Demo
Future的使用场景:将计算或IO异步化,提高程序并行度。public class FutureTaskTest{ public static void main(String[] args) { //计算模块 Callable<Integer> eval = new Callable<Integer>() { ...原创 2019-02-14 19:37:58 · 331 阅读 · 0 评论 -
使用Lock实现异步锁
synchronized获取和释放锁都是在同一代码块中,而Lock获取和释放锁可以放在不同的代码块中,形成一种异步锁。public class Account { private long userId; private BigDecimal amount; public ReentrantLock lock;}public Boolean lockA...原创 2019-02-21 14:57:34 · 1442 阅读 · 0 评论 -
公平锁和非公平锁
公平锁:线程按照他们发出获取锁请求的顺序来获取锁,使用FIFO队列实现。非公平锁:当获取锁的请求到达的时,如果锁状态为可用,则进行插队,直接获取锁,否则,将请求加入队列,队列中的请求依然遵循FIFO原则。在激烈竞争锁的环境中,非公平锁比公平锁的性能更好,下图是使用公平锁和非公平锁对HashMap提供同步保护时的压测结果,非公平锁的吞吐量比公平锁高出2个量级。原因:恢复一个挂起的线程...原创 2019-02-21 15:17:53 · 447 阅读 · 0 评论 -
synchronized与Lock使用哪个
只有当synchronized无法满足需求时,才需要使用Lock,否则,一律使用synchronized。使用Lock的场景:可定时的、可轮询的、可中断的锁获取操作,公平队列(synchronized只实现了非公平锁),非块结构的锁。内置锁与ReentrantLock相比还有另一个优点:在线程转储(使用jstack)中能给出在哪些调用帧中获得了哪些锁,并能检测和识别发生死锁的线程。JVM并...翻译 2019-02-21 15:39:35 · 277 阅读 · 0 评论 -
线程池简单实现
public class SimpleExecutorImpl { //默认可用状态 private volatile boolean RUNNING = true; private volatile boolean shutdown = false; //Task Queue:任务队列,FIFO,阻塞 private static Blockin...原创 2019-02-16 14:14:30 · 167 阅读 · 0 评论 -
使用Future取消超时任务
在某些场景下,获取的资源存在过期时间,超过期望的新鲜时间,再获取到的结果就是无效的,这时可以用Future来取消过期的任务。public class FutureTimeOutDemo { static final ExecutorService executorService = Executors.newFixedThreadPool(5); public stati...原创 2019-02-16 15:41:25 · 1508 阅读 · 0 评论 -
Java线程同步
Java提供的线程同步机制:synchronized、volatile、Lock、原子变量。保证线程同步的方法:1.不在线程间共享状态变量。2.将状态变量修改为不可变的变量。3.在访问共享的状态变量时使用同步机制。...原创 2019-01-30 17:15:46 · 431 阅读 · 0 评论 -
ThreadLocal使用demo
public class ThreadLocalDemo { public static ThreadLocal<String> threadName = new ThreadLocal<String>(){ protected String initialValue() { return "-1"; }...原创 2019-01-30 18:19:19 · 1939 阅读 · 0 评论 -
解决死锁问题-设置加锁顺序
大部分死锁都是由于不恰当的加锁顺序造成的,例如,线程A持有lock1等待lock2,而线程B持有lock2等待lock1,解决办法是每个线程都以相同的顺序获取多个锁。先看一个由于错误的加锁顺序导致的死锁:public class TransferMoneyDeadLock { public void transferMoney(Account fromAccount, Acco...原创 2019-02-19 14:11:41 · 3215 阅读 · 2 评论 -
解决死锁问题-open call
当调用一个外部的接口时,如果持有锁,由于不知道外部接口做了什么操作,此时,可能由于资源依赖形成锁环路,造成死锁。解决办法是,使用开放调用,调用外部接口时,不持有锁。先看看错误的调用:如果有两个线程同时调用Taxi的setLocation和Dispatcher的printLocation,则可能因为错误的执行时序造成两个线程都只获得了一个锁,等待另一个永远无法获得的锁,从而造成死锁。pub...原创 2019-02-19 14:51:46 · 951 阅读 · 0 评论 -
并发程序的性能考量
资源:CPU时钟周期、内存、网络带宽、I/O带宽、数据库请求、磁盘空间以及其他资源。当操作性能由于某种特定的资源而受到限制时,我们通常将该操作称为资源密集型的操作,例如,CPU密集型操作、数据库密集型操作。对于单核的系统,如果任务是CPU密集型的,那么最优的线程数是1。性能开销:线程之间的协调(例如加锁、触发信号、内存同步),增加上下文切换,线程创建和销毁,以及线程的调度等。性能指标:...翻译 2019-02-19 20:27:16 · 195 阅读 · 0 评论 -
安全发布对象的模式
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:1.在静态初始化函数中初始化一个对象引用,利用了JVN内部的同步机制。2.将对象的引用保存到volatile类型的域或者AtomicReference对象中。3.将对象的引用保存到某个正确构造对象的final类型域中。4.将对象的引用保存到由一个锁保护的域中。...转载 2019-02-02 10:49:55 · 215 阅读 · 0 评论 -
Java可重入锁之内置锁
public class Father { static { System.out.println("Father init"); } public void doSomething(String threadName){ synchronized (this) { System.out.println(thre...原创 2019-02-01 16:16:42 · 226 阅读 · 0 评论 -
线程同步之可见性试验
线程同步的作用包含2个方面:1.原子性:这里的原子性跟数据库事务的原子性相似,不论多个可变状态变量还是单个可变状态变量,要保证并发操作的正确性,必须保证每个线程拿到的数据都是正确的,不存在过期数据或者部分过期数据。最常见的违反原子性错误的情形是:"select-update"、"check-use"、"read-change-write",由于最终使用数据要依赖数据以前的状态,不是一个原子的操...原创 2018-09-12 19:18:05 · 356 阅读 · 0 评论 -
Java并发模型-封装变量保证并发正确性
Java编码规范要求对变量的访问必须通过getter/setter,这样做,其实是最简单的确保线程安全的技术,叫做保证作用域不逸出。接下来,来看看封装变量,对并发结果有什么影响,注意CNT不是private的变量,意味着其他线程可以直接访问CNT,不需要经过incr()和getCNT()!这是工具类:public class PrivateFieldDemo { static i...原创 2018-09-14 14:43:35 · 223 阅读 · 0 评论 -
Java多线程与锁模型-顺序锁与资源锁
顺序锁:当应用程序使用2把以上的锁时,就容易出现因为多线程获取锁的顺序不同而死锁的情形,包括交叉获取应用程序范围内的多把已知锁、交叉获取应用程序与第三方方法中的多把锁而造成的顺序死锁。绝大多数死锁都是因为CPU调度多线程时,在执行时序上是交叉进行的而造成乱序获得多把锁,从而形成死锁,所以,解决顺序锁的办法就是总是按照一定的顺序来获取锁!资源锁:最典型的资源锁是CPU时钟,多线程的程序如果是计算...原创 2018-09-25 10:23:32 · 792 阅读 · 0 评论 -
阻塞操作不可取消
public class BlockedInterrupteTest extends Thread{ private final BlockingQueue<BigInteger> queue; private volatile boolean cancelled = false; BlockedInterrupteTest(BlockingQueue&l...原创 2018-09-19 21:12:58 · 374 阅读 · 0 评论 -
并发的本质
说到底,并发的核心就是如何在代码中协调好并行处理和串行处理!各种并发技术,其实质都是在并行和串行之间转换,追求性能最大化,又要守住结果正确性这条红线!就像高速公路,有些路段你可以飙车,但是到了收费站,你必须慢下来,一个接一个通过检查。并发中的锁,必须是唯一的锁,不然就会失去唯一性访问控制,锁的唯一作用就是排他的访问控制。...原创 2018-09-15 13:43:51 · 2116 阅读 · 0 评论 -
处理不可阻塞中断
对于支持阻塞的库方法(会抛出InterruptedException),可以通过设置中断标志位来实现取消任务。但是,如果一个线程是因为进行同步Socket I/O或者等待获取内部锁而阻塞的,设置线程的中断状态并不能让线程中断。java.io中的同步Socket I/O:read和write方法都不响应中断,关闭Socket时,抛出IOException,所以,可以调用Sokcet的close方...原创 2018-09-20 16:39:34 · 352 阅读 · 0 评论 -
ReentrantLock可重入实现原理
ReentrantLock持有实现了AbstractQueuedSynchronizer的static内部类,而AbstractQueuedSynchronizer继承了AbstractOwnableSynchronizer,AbstractOwnableSynchronizer有个保存当前持有锁的线程的变量exclusiveOwnerThread。/** * The current ow...原创 2018-10-13 11:55:09 · 714 阅读 · 0 评论 -
线程阻塞的实现原理
Java中,当多个线程同时请求独占锁时,JVM虚拟机会根据上一次获取锁操作中对锁的持有时间来决定是挂起线程还是让线程自旋,当上一次持有锁的时间相对较长时,会将线程加入阻塞队列,否则让线程通过while来自旋。常用的阻塞方式:加入FIFO的队列,或者while自旋。...原创 2018-10-13 13:54:06 · 2720 阅读 · 1 评论 -
CAS的ABA问题与解决办法
CAS的核心操作就是比较V值与A是不是一致,如果一致,则将V更新为B。如果V先被线程1更新为C,又被线程2更新为A,最后当前线程进行CAS操作时,看到V的值还是A,认为可以将V更新为B,但是某些场景下,ABA被认为是发生了变化,需要重新执行计算。产生问题的原因:缺失足够多的信息。解决方案:增加信息量,原来要校验并更新1个值,现在校验并更新2个值,包括原来要更新的值和一个版本号。Java...原创 2018-10-13 17:10:12 · 331 阅读 · 0 评论 -
static和final在并发场景下的应用
1.static:静态初始化不需要同步,自带线程安全属性public class LazyInitialization { private static class ResourceHolder { public static Resource resource = new Resource(); } public static Resource ...原创 2018-10-19 11:13:02 · 619 阅读 · 0 评论 -
volatile的语义
1.可见性:当一个线程修改了这个值,新值对其他线程是立即可知的。当被volatile修饰的变量的值发生改变后,新值会立即从线程的工作内存刷新到主内存(执行store和write操作),而且线程每次使用这个变量前,都会将主内存的值刷新到工作内存(read和load操作),普通变量的值在线程间传递均需要通过主内存来完成。2.禁止JIT进行指令重排序优化,普通变量不能保证变量赋值操作的顺序与代码中的...原创 2018-10-31 20:19:45 · 200 阅读 · 0 评论 -
说说yield()函数
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attemp...原创 2019-01-31 14:24:41 · 224 阅读 · 0 评论 -
同一个线程不要调用2次Thread.start()
同一个线程,如果调用start()两次会报异常:Exception in thread "main" java.lang.IllegalThreadStateException看源码:/* Java thread status for tools, * initialized to indicate thread 'not yet started' */private volat...原创 2019-01-31 16:20:58 · 701 阅读 · 0 评论 -
可重入锁和不可重入锁
不可重入锁:public class NoReentrantLock { private boolean isLocked = false; public synchronized void lock() throws InterruptedException { while(isLocked) { wait(); }...转载 2019-02-01 14:20:07 · 127 阅读 · 0 评论 -
锁优化-减小锁粒度
锁优化有3个方向:1.减少持有锁的时间:例如,将CPU密集和I/O密集的任务移到锁外,可以有效减少持有锁的时间,从而降低其他线程的阻塞时间。2.减小加锁的粒度:将单个独占锁变为多个锁,从而将加锁请求均分到多个锁上,有效降低对锁的竞争。但是,增加锁的前提是多线程访问的变量间相互独立,如果多线程需要同时访问多个变量,则很难进行锁分解,因为要维持原子性。3.放弃使用独占锁,使用非阻塞算法来保...原创 2019-02-20 11:19:52 · 1790 阅读 · 0 评论