线程

14 篇文章 0 订阅
14 篇文章 0 订阅

~1、悲观锁、乐观锁
转载链接:https://blog.csdn.net/rexct392358928/article/details/52230737
悲观锁:取数之后别人一定会改,所以锁住。类似java单例-sync(行锁表锁读锁写锁)。适用于冲突比较严重比较频繁的事件。
乐观锁:取数之后别人不会改到,但是同时取走了一个状态,另一个人更新之后,顺便更新了状态,第一个人回来发现状态不对,重取。-CAS
觉得没人会去改,但是标记了一个状态,或者说获取了一个版本号,每次更新前都会比对一下,如果不匹配则retry。
适用于冲突不多的情况。省去锁开销,增加系统吞吐量。如果冲突多则不适合,因为上层要一直retry。
适用于更新操作减少,查询较多的场景,否则如果更新多了线程要一直重复去获取最新数据,影响系统性能。
CAS会出现一个问题,就是ABA的问题,一个值改变过之后还是原值,那就无法判断该值是否改变过。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
prv:锁有几种方式:sync跟lock 线程池里面就用到很多lock,unlock。
AtomicInteger:i++跟++i是线程不安全的,递增的时候用getAndIncrement()方法。利用private volatile int value的volatile共享变量值,调用getAndAddInt,底层
其实是调用compareAndSwapInt这个本地native方法
单例模式(Singleton)的定义:是为了确保一个类只能产生一个实例,从而节省对象创建所花费的时间,从而对系统内存使用频率也会减低。
享元模式(FlyWeight)的定义:是为系统中存在多个相同的对象,那么只需要共享一个对象的拷贝。
prv:享元模式可以理解成threadlocal,单例只有一个对象,享元可以有多个对象,只不过只创建一次或者在一个地方统一放置
~2、Java 线程池框架核心代码分析
生命周期
ThreadPoolExecutor中,使用CAPACITY英 [kəˈpæsəti]容量;性能;的高3位来表示运行状态,分别是:
RUNNING:接收新任务,并且处理任务队列中的任务
SHUTDOWN:不接收新任务,但是处理任务队列的任务
STOP:不接收新任务,不处理任务队列,同时中断所有进行中的任务
TIDYING:所有任务已经被终止,工作线程数量为 0,到达该状态会执行terminated()
TERMINATED:terminated()执行完毕
ThreadPoolExecutor中用原子类来表示状态位1
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
线程池模型
核心参数
corePoolSize:最小存活的工作线程数量(如果设置allowCoreThreadTimeOut,那么该值为 0)
maximumPoolSize:最大的线程数量,受限于CAPACITY
keepAliveTime:对应线程的存活时间,时间单位由TimeUnit指定
workQueue:工作队列,存储待执行的任务
RejectExecutionHandler:拒绝策略,线程池满后会触发
线程池的最大容量:CAPACITY中的前三位用作标志位,也就是说工作线程的最大容量为(2^29)-1
四种模型
CachedThreadPool:一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲的线程,当需求增加时,则可以添加新的线程,线程池的规模不存在任何的限制。
FixedThreadPool:一个固定大小的线程池,提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的大小将不再变化。
SingleThreadPool:一个单线程的线程池,它只有一个工作线程来执行任务,可以确保按照任务在队列中的顺序来串行执行,如果这个线程异常结束将创建一个新的线程来执行任务。
ScheduledThreadPool:一个固定大小的线程池,并且以延迟或者定时的方式来执行任务,类似于Timer。
执行任务 execute
核心逻辑:
1. 当前线程数量 < corePoolSize,直接开启新的核心线程执行任务addWorker(command, true)
2. 当前线程数量 >= corePoolSize,且任务加入工作队列成功
检查线程池当前状态是否处于RUNNING
如果否,则拒绝该任务
如果是,判断当前线程数量是否为 0,如果为 0,就增加一个工作线程。
3. 开启普通线程执行任务addWorker(command, false),开启失败就拒绝该任务
从上面的分析可以总结出线程池运行的四个阶段:
poolSize < corePoolSize且队列为空,此时会新建线程来处理提交的任务
poolSize <= corePoolSize此时提交的任务进入工作队列,工作线程从队列中获取任务执行,此时队列不为空且未满。
poolSize == corePoolSize并且队列已满,此时也会新建线程来处理提交的任务,但是poolSize < maxPoolSize
poolSize > maxPoolSize并且队列已满,此时会触发拒绝策略
prv:core不为空就新建线程,core满了就放队列,队列满了<max就新建,>max队列也满就触发拒绝 优先级core>queue>max
拒绝策略
prv:抛异常/不抛异常/谁调谁干/抛弃最老的
前面我们提到任务无法执行会被拒绝,RejectedExecutionHandler是处理被拒绝任务的接口。下面是四种拒绝策略。
AbortPolicy:默认策略,终止任务,抛出RejectedException
CallerRunsPolicy:在调用者线程执行当前任务,不抛异常,主线程自己干
DiscardPolicy: 抛弃策略,直接丢弃任务,不抛异常
DiscardOldersPolicy:抛弃最老的任务,执行当前任务,不抛异常
转载链接:https://blog.csdn.net/jgteng/article/details/54411423
线程池中的 Worker
Worker继承了AbstractQueuedSynchronizer和Runnable,前者给Worker提供锁的功能,后者执行工作线程的主要方法
runWorker(Worker w)(从任务队列捞任务执行)。Worker 引用存在workers集合里面,用mainLock守护。
private final ReentrantLock mainLock = new ReentrantLock();
private final HashSet workers = new HashSet();
核心函数 runWorker
下面是简化的逻辑,注意:每个工作线程的run都执行下面的函数
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
while (task != null || (task = getTask()) != null) {
w.lock();
beforeExecute(wt, task);
task.run();
afterExecute(task, thrown);
w.unlock();
}
processWorkerExit(w, completedAbruptly);
}
从getTask()中获取任务
锁住 worker
执行beforeExecute(wt, task),这是ThreadPoolExecutor提供给子类的扩展方法
运行任务,如果该worker有配置了首次任务,则先执行首次任务且只执行一次。
执行afterExecute(task, thrown);
解锁 worker
如果获取到的任务为 null,关闭 worker
获取任务 getTask
线程池内部的任务队列是一个阻塞队列,具体实现在构造时传入。
总结
ThreadPoolExecutor基于生产者-消费者模式,提交任务的操作相当于生产者,执行任务的线程相当于消费者。
Executors提供了四种基于ThreadPoolExecutor构造线程池模型的方法,除此之外,我们还可以直接继承ThreadPoolExecutor,
重写beforeExecute和afterExecute方法来定制线程池任务执行过程。
使用有界队列还是无界队列需要根据具体情况考虑,工作队列的大小和线程的数量也是需要好好考虑的。
拒绝策略推荐使用CallerRunsPolicy,该策略不会抛弃任务,也不会抛出异常,而是将任务回退到调用者线程中执行。
~3、synchronized 锁不住方法。
只有对象锁跟类锁,没有方法锁
在解释这个问题前,必需先清楚一些同步方法的原理
1、如果是一个非静态的同步函数的锁,锁对象是this对象。 可重入性(一个线程得到一个对象锁后再次请求该对象锁,是允许的)
2、如果是静态的同步函数的锁,锁对象是该类的字节码对象。
~4、 ReentrantLock与synchronized 的区别
1、ReentrantLock,一个可重入的互斥锁Lock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,
在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,底层是基于CAS实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时
出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会
下降几十倍,但是ReetrantLock的性能能维持常态;
5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,
以及各种Atomic类。了解其性能的优劣程度,有助与我们在特定的情形下做出正确的选择。
总体的结论先摆出来:
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化
synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。
而ReentrantLock确还能维持常态。
Atomic:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock
一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个
Atomic之间同步。
所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,
不仅不能提高性能,还可能带来灾难。
~5、Synchronize 和 Lock 的区别与用法
一、synchronized和lock的用法区别
(1)synchronized(隐式锁):在需要同步的对象中加入此控制,可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
(2)lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类
做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
二、synchronized和lock性能区别
synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为 这是一个重量级操作,
需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但 是到了
Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致
在Java1.6上synchronize的性能并不比Lock差。
三、synchronized和lock机制区别
(1)synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。
(2)Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
乐观锁实现的机制就 是CAS操作(Compare and Swap)。
Mybatis KaTeX parse error: Expected 'EOF', got '#' at position 5: {} 和#̲{} 的区别,在什么时候用{}
#{}方式能够很大程度防止sql注入,KaTeX parse error: Expected 'EOF', got '#' at position 26: …l注入。 建议大家使用#̲,至于什么时候用符号进行传参。
有时候可能需要直接插入一个不做任何修改的字符串到SQL语句中。这时候应该使用 语 法 。 当 使 用 {}语法。 当使用 使{}参数作为字段名或表名时,需指定statementType为“STATEMENT”
~6、Executor、Executors、ExecutorsService
Executor顶级接口只有一个cxecute(),Executors是一个类,包含各种创建线程池或线程相关的方法,ExecutorService接口,
实现了Excutor并定义了操作线程池的方法
~7、死锁 2k+1<资源数 k是不死锁的最大线程数
死锁的必要条件
1、互斥 至少有一个资源处于非共享状态
2、占有并等待
3、非抢占
4、循环等待
解决死锁,第一个是死锁预防,就是不让上面的四个条件同时成立。二是,合理分配资源。
三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。
prv:死锁
转载链接:http://www.cnblogs.com/sivkun/p/7518540.html
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,
其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两 种基本的锁类型来对数据库的
事务进行并发控制。
死锁的第一种情况
一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经
锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
解决方法:
这种死锁比较常见,是由于程序的BUG产生的,调整程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时
锁定两个资源
死锁的第二种情况
用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,
而用户B里的独占锁由于A 有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,
于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项 目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,
使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操 作,很容易就出现这种死锁的情况。
解决方法:
1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
2、使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本
解决方案中,一般是 通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本
号加一。此时,将提交数据的版本数据与数 据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本
号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据 库加锁开销(用户A和用户B操作过程中,都没有对数据库
数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于
乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造 成脏数据被更新到数据库中。
3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程
度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统, 当某个
操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个
操作过程中(从操作员读 出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始
终处于加锁状态,可以想见,如果面对成百上千个并发,这 样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考
虑清楚。
死锁的第三种情况
如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生
死锁和阻塞。类似的情 况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来
越慢,最终发生阻塞或死锁。
解决方法:
SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。
~8、进程间的通信方式
1、管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2、有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3、信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享
资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
4、消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、
管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5、信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
6、共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以
访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,
配合使用,来实现进程间的同步和通信。
7、套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
prv:父子进程,没关系的进程,不同机器间的进程通信,进程内线程同步控制,通知进程事件,强化通知/扩展通知消息的丰富性强大功能性,
强化多个进程间通信;父子/无关系/族外/体内/信号/强信号/信号池;管道/有名管道/套接字/信号量/信号/消息队列/共享内存
~9、信号量&互斥锁

互斥量用于线程的互斥,信号量用于线程的同步。
互斥量值只能为0/1,信号量值可以为非负整数。
对于互斥锁(Mutex)来说,只要有线程占有了该资源,那么不好意思,其他线程就是优先级再高,您也得等着,等我用完再说。我用
完之后资源你们爱怎么抢都行,我占有资源的时候别人都不许抢,申请该资源的线程一律等待。
信号量(Semaphore seməˌfɔ:)就更灵活一点,我们如果想把我用完的资源给我关系好的人,就申请一个信号量,这个信号量只有我
们几个知道,我用完资源就偷偷通知关系好的这几个,这几个哥们再有序申请资源,别的人干瞪眼。
而信号量(Semaphore)一般就是互斥的(少许情况读取是可以同时申请的),其保证了线程执行的有序性,可以理解为从一到多的进步,
转载链接:https://blog.csdn.net/shaohua_lv/article/details/70257100
信号量与互斥锁之间的区别:
互斥量用于线程的互斥,信号线用于线程的同步。
这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问
是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,
特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
作用域
信号量: 进程间或线程间(linux仅线程间的无名信号量pthread semaphore)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源
~10、java 常用concurrent类
都是基于AbstractQueueSynchronize
转载链接:https://www.cnblogs.com/beiyeren/p/3865833.html
1、CountDownLatch
它的作用主要是当多个(数量等于初始化CountDownLatch时count参数的值)线程到达了预期状态或完成预期工作时触发事件,其他
线程可以等待这个事件来触发自己后续工作。等待的线程会调用CountDownLatch的await方法,而达到自己预期状态的线程会调用
CountDownLatch的countDown方法。
2、CyclicBarrier
它的作用是协调多个线程,多个线程在这个屏障前等待,直到所有线程都到达屏障时,再一起执行后面的动作。
调用await方法。
3、Semaphore
管理信号量,通过acquire获得信号量,而release是释放信号量。
4、Exchanger
用于在两个线程之间进行数据交换。线程会阻塞在Exchanger的exchange的方法上,直到另外一个线程也到了同一个Exchanger的
exchange方法时,二者进行交换。

189、并发编程 – Concurrent 用户指南
转载链接:http://www.cnblogs.com/abcdwxc/p/9851810.html
~11、CyclicBarrier(栅栏-循环屏障)和CountDownLatch(闭锁-倒计时-可循环使用(Cyclic)的屏障(Barrier))区别
CyclicBarrier和CountDownLatch的区别
(1)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更
为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
(2)CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用
来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。
(3)CountDownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程。
转载链接:https://blog.csdn.net/tolcf/article/details/50925145
CyclicBarrier和CountDownLatch 都位于java.util.concurrent 这个包下
CountDownLatch CyclicBarrier
减计数方式 加计数方式
计算为0时释放所有等待的线程 计数达到指定值时释放所有等待线程
计数为0时,无法重置 计数达到指定值时,计数置为0重新开始
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响
调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用 可重复利用
线程在countDown()之后,会继续执行自己的任务,而CyclicBarrier会在所有线程任务结束之后,才会进行后续任务
转载链接:https://blog.csdn.net/bntX2jSQfEHy7/article/details/78237208
上述的执行结果可以看出,当分配的7个人(7个线程)分别找到龙珠之后,也就是所有的线程执行完毕,才可以召唤龙珠(执行countDownLatch.await()之后的代码)。
注意:
(1)CountDownLatch的构造函数 7表示需要等待执行完毕的线程数量。
(2)在每一个线程执行完毕之后,都需要执行countDownLatch.countDown()方法,不然计数器就不会准确;
(3)只有所有的线程执行完毕之后,才会执行 countDownLatch.await()之后的代码;
(4)可以看出上述代码中CountDownLatch 阻塞的是主线程;
CountDownLatch在实时系统中的使用场景
让我们尝试罗列出在java实时系统中CountDownLatch都有哪些使用场景。我所罗列的都是我所能想到的。如果你有别的可能的使用方法,请在留言里列出来,这样会帮助到大家。
(1)实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。
(2)开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了。
(3)死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。
~12、线程池等一系列
转载链接:http://www.cnblogs.com/dolphin0520/p/3932921.html
补充一个状态:TYDING 转载链接:https://blog.csdn.net/l_kanglin/article/details/57411851
prv:跟读源码,发现看源码揣摩细节的意图能让自己思考更加全面,比如判断线程当前线程数是否大于核心线程池数,明明外层判断了一次,里层lock之后又判断了一次,原因是因为外层没加锁,判断的结果不是最准确的,可能另一个线程同时操作过,还有一种情况是判断线程数是否为0,为0表示线程池被关闭,这时就需要确保即将处理的操作要能持续下去,不被线程池意外关闭影响到。
从大的方面考虑,有种做法是当检测到有空的线程时用空的线程去加载任务,但是这样就多了一个监管的步骤,但是线程池源码采用的是当一个线程执行完任务之后自动去队列里面加载任务,加载不到就循环该操作,这样就省去了监管的额外动作,跟锁sync和CAS的对比同理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值