1.创建线程三种方式
1) start 方法:
用 start 方法来启动线程,真正实现了多线程运行。通过调用 Thread 类的
start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有
运行,一旦得到 cpu 时间片,就开始执行 run()方法,这里方法 run()称为线
程体,它包含了要执行的这个线程的内容,Run 方法运行结束,此线程随即
终止。
2) run():
run()方法只是类的一个普通方法而已,如果直接调用 run 方法,程序
中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序
执行,还是要等待,run 方法体执行完毕后才可继续执行下面的代码,这样
就没有达到写线程的目的。
总结:调用 start 方法方可启动线程,而 run 方法只是 thread 的一个
普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要
并行处理的代码放在 run()方法中,start()方法启动线程将自动调用 run()
方法,这是由 jvm 的内存机制规定的。并且 run()方法必须是 public 访问权
限,返回值类型为 void。
两种方式的比较 :
实际中往往采用实现 Runable 接口,一方面因为 java 只支持单继承,
继承了 Thread 类就无法再继续继承其它类,而且 Runable 接口只有一个
run 方法;另一方面通过结果可以看出实现 Runable 接口才是真正的多线程。
2.线程的状态流转
3.死锁
4.shutdown() VS shutdownNow()
5.isTerminated() VS isShutdown()
6.sleep() 和 wait()
7.yield
8.volatile
9.线程阻塞三种情况
10.线程死亡三种方式
11.守护线程
12.fork/jion
Fork/Join框架是Java7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork/Join框架需要理解两个点,「分而治之」和「工作窃取算法」。
「分而治之」:以上Fork/Join框架的定义,就是分而治之思想的体现啦
「工作窃取算法」:把大任务拆分成小任务,放到不同队列执行,交由不同的线程分别执行时。有的线程优先把自己负责的。任务执行完了,其他线程还在慢慢悠悠处理自己的任务,这时候为了充分提高效率,就需要工作盗窃算法啦~
工作盗窃算法就是,「某个线程从其他队列中窃取任务进行执行的过程」。一般就是指做得快的线
程(盗窃线程)抢慢的线程的任务来做,同时为了减少锁竞争,通常使用双端队列,即快线程和慢线程
各在一端
13.cas–Compare and swap ,即比较并交换
14.synchronized 和 volatile
15.synchronized 和 Lock
16.synchronized 和 ReentrantLock
17.synchronized
用法:
作用:
底层实现原理
synchronized 锁升级的原理
synchronized 非公平锁
jvm对synchronized 的优化
锁膨胀:膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
锁消除:虚拟机另外一种锁的优化,在JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁。object锁是私有变量,不存在所得竞争关系。
锁粗化:虚拟机另一种优化处理,通过扩大锁的范围,避免反复加锁和释放锁。
自旋锁与自适应自旋锁:轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,进行自旋锁的优化手段。
自旋锁:许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得,通过让线程执行循环等待锁的释放,不让出CPU。如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式。
缺点:如果锁被其他线程长时间占用,一直不释放CPU,会带来许多的性能开销。
自适应自旋锁:相当于是对自旋锁优化方式的进一步优化,它的自旋的次数不再固定,其自旋的次数由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定,这就解决了自旋锁带来的缺点。
为什么要引入偏向锁和轻量级锁?为什么重量级锁开销大
重量级锁底层依赖于系统的同步函数来实现,在 linux 中使用 pthread_mutex_t(互斥锁)来实现。这些底层的同步函数操作会涉及到:操作系统用户态和内核态的切换、进程的上下文切换,而这些操作都是比较耗时的,因此重量级锁操作的开销比较大。
很多情况下,可能获取锁时只有一个线程,或者是多个线程交替获取锁,在这种情况下,使用重量级锁就不划算了,因此引入了偏向锁和轻量级锁来降低没有并发竞争时的锁开销。
synchronized锁能降级么
18.ThreadLocal
线程本地变量:如果创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。
应用场景:
实现原理
内存泄露问题
19.ReentrantLock
20.ReadWriteLock
1.现在有T1,T2,T3三个线程,怎样保证T2在T1执行完成后执行,T3在T2执行完后执行?
在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
join()方法的作用就是让主线程等待子线程执行结束之后再运行主线程。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
使用场景,线程2依赖于线程1执行的返回结果
public static void main(String[] args) throws Exception{
Thread t1 = new Thread(()->{
try {
Thread.sleep(500);
System.out.println("线程1醒了");
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<100;i++){
System.out.println("线程1 i:"+i);
}
});
t1.setName("线程1");
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<100;i++){
System.out.println("线程2 i:"+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.setName("线程2");
t2.start();
t1.start();
}
2.在 Java 中 Lock 接口比 synchronized 块的优势是什么?你需要实现一个高效的缓存,它允 许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock 接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像 ConcurrentHashMap 这样的高性能数据结构和有条件的阻塞。
区别:
synchronized
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
public class SynchronizedTest {
public synchronized void test1(){}
public void test2(){
synchronized (this){}
}
}
javap工具可以查看生成的class文件信息来分析Synchronized的实现
同步代码块:是使用monitorenter和monitorexit指令实现的
通过在对象头设置标记,达到获取锁和释放锁的目的。
monitorenter指令是在编译后插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。
线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。获取锁,锁的计数器+1,
线程执行到monitorexit指令时,锁计数器-1,计数器为0时,锁被释放。
获取对象失败,当前线程阻塞等待,直到对象锁被另一个线程释放为止。
同步方法(在这看不出来需要看JVM底层实现):依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。
synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
区别:
//Condition定义了等待/通知两种类型的方法
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();...condition.await();...condition.signal();
condition.signalAll();
使用
synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
2种机制的具体区别:
**synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。**独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。其中比较重要的获得锁的一个方法是compareAndSetState。调用的CPU提供的特殊指令。可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
3.在 java 中 wait 和 sleep 方法的不同?
sleep():使线程暂停执行一算时间的方法。
wait():使线程暂停一段时间的方法。例如,当线程进行交互时,如果线程对一个同步对象x发出一个wait()调用请求,那么该线程会暂停执行,被调用对象进入等待状态,直到被唤醒或者等待时间超时。
slepp()方法与yield()方法有什么区别:
1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程运行的机会。而yield()方法只会给相同的优先级或者更高优先级的线程以运行的机会。
2)线程执行sleep()方法后会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内肯定不会被执行。而yield()方法只是使当前线程重回到可执行状态,所以执行yeild()方法的线程有可能在进入到可执行状态后马上又被执行。
3)sleep()方法声明抛出InterruptException,而yeild()方法没有声明任何异常。
4)sleep()方法比yield()方法(跟操作系统相关)具有更好的可移植性。
7.什么是原子操作, Java 中的原子操作是什么?
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间切换到另一个线程。
java.util.concurrent.atomic 包里面提供了一组原子类。基本特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性。即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,但不会阻塞线程(synchronized 会把别的等待的线程挂,或者说只是在硬件级别上阻塞了)。
8.Java 中的 volatile 关键是什么作用?怎样使用它?在 Java 中它跟 synchronized 方法有什么不同?
volatile是轻量级的synchronized,其在多处理器开发中保证了共享变量的“可见性”。
可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
一个变量被定义成volatile后具备两种特性:
内存模型定义了8种内存间操作
volatile是不是并发安全的
不是,volatile变量在各个线程的工作内存,不存在一致性问题,但运算并非原子操作。
Java代码需要转化为汇编指令在CPU上运行。有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,第二行汇编代码中包含有Lock前缀。
Lock前缀的指令在多核处理器下会引发了两件事:
1.将当前处理器缓存行的数据写回到系统内存。
2.这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
原本处理器会将系统内存的数据读到内部缓存后进行操作,但操作完不知道何时会写到内存。所以当多线程并发的时候,其他处理器缓存的值还是旧的,再执行计算操作就会有问题。
而加上了volatile关键字,对变量进行写操作时,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。这样每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
两大原则
1.Lock前缀指令会引起处理器缓存回写到内存。Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。在多处理器环境中,LOCK#信号确保在声言该信号期间,处理器可以独占任何共享内存
2.一个处理器的缓存回写到内存会导致其他处理器的缓存无效。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的缓存的数据在总线上保持一致。
sychronized
悲观锁(不管是否产生竞争,都会加锁)、非公平锁(获取锁行为上,不是按时间前后给等待线程分配锁的,锁释放,任何线程都有机会竞争到锁,缺点:产生线程饥饿)、可重入锁(获取锁+1),可重入锁最大的作用是避免死锁。
级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,目的是为了提高获得锁和释放锁的效率。
成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入。
public synchronized void synMethod(){
//方法体
}
对某一代码块使用 synchronized后跟括号,括号里是变量,一次只有一个线程进入该代码块,此时,线程获得的是成员锁。
public Object synMethod(Object a1){
synchronized(a1){
//一次只能有一个线程进入
}
}
如果synchronized后面括号里是一个对象,此时,线程获得的是对象锁。如果线程进入,则得到当前对象锁,那么其他没有获得锁的线程,在该类所有对象上的任何操作都不能进行。
public classMyThread implements Runnable{
public static void main(Stringargs[]){
Thread t1=newThread(mt,“t1”);
Thread t2=newThread(mt,“t2”);
t1.start();
t2.start();
}
public void run(){
synchronized(this){
System.out.println(Thread.currentThread().getName());
}
}
如果synchronized后面括号里是类,此时线程获得的是对象锁。如果其他线程进入,则线程在该类中所有操作不能进行,包括静态变量和静态方法。实际上,对于含有静态方法和静态变量的代码块的同步,我们通常选用例4来加锁。
class ArrayWithLockOrder{
public ArrayWithLockOrder(int[]a){
synchronized(ArrayWithLockOrder.class){
//代码逻辑
}
}
}
jvm对java原生锁的优化
① 自旋锁,把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时无需让线程执行阻塞操作,避免了用户态到内核态的切换
②锁消除:JVM对一些代码上要求同步但被检测到不可能存在共享数据竞争的锁进行消除,主要根据逃逸分析。
③锁粗化:增大锁的作用域
synchronized和reentrantLock 实现原理有什么不同
锁的实现原理基本是为了达到一个目的:让所有线程都能看到某种标记
synchronized:对象头
reentrantLock及基于lock接口的实现类:通过用一个volatile修饰的int型变量,并保证每个线程都拥有对该int的可见性和原子修改,本质是基于所谓的AQS框架。
public class RWSample {
private final Map m = new TreeMap<>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public String get(String key){
r.lock();
System.out.println("读锁锁定");
try {
return m.get(key);
} finally {
r.unlock();
}
}
public String put(String key, String entry){
w.lock();
System.out.println("写锁锁定");
try {
return m.put(key, entry);
}finally {
w.unlock();
}
}
}
AQS:一个用来构建锁和同步器的框架,各种Lock包中的锁,eg:ReentrantLock,ReadWriteLock,Semaphore,CountDownLatch,FutureTask等。
同步状态:volatile int state,0:没有任何线程占有共享资源的锁,可以获得锁。1:有线程目前正在使用共享变量,其他线程必须加入同步的队列等待。
同步队列:Node内部类构成的一个双向链表结构,完成线程获取锁的排队工作,当有线程获取锁失败后,就被添加到队列末尾。
Node类:包含线程本身及其状态waitStatus(5种取值,阻塞,等待唤醒等),每个node结点关联其prev结点 和next结点,方便线程释放后快速唤醒下一个在等待的线程,是一个FIFO过程。常量:SHARED(共享模式)EXCLUSIVE(独占模式)
ConditionObject:构建等待队列,Condition调用wait()方法,线程将会加入等待队列,调用signal从等待队列转移到同步队列中进行锁竞争。
AQS和Condition各自维护了不同的队列,在使用Lock和Condition的时候,就是两个队列互相移动。
从功能角度,ReetrantLock比Synchronized的同步操作更精细,可以实现更多高级功能:
等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用。
带超时的获取锁尝试:指定时间范围内没有获取则返回。
可以判断是否有线程在排队等待获取锁
可以响应中断请求:中断异常会被抛出,锁会被释放
可以实现公平锁
释放锁:synchronized在jvm层面上实现,发生异常时,jvm会自动释放锁定,lock通过代码实现,要保证锁一定会被释放,必须将unlock放到finally中。
性能:
竞争不激烈时,synchronized性能要优于ReetrantLock
高竞争时,synchronized性能会下降几十倍,但reetrantlock性能能维持常态。
reentrantLock可重入性
内部自定义了同步器Sync(既实现了AQS,又实现了AOS—提供了一种互斥锁持有的方式),加锁通过cas算法,将线程对象放到一个双向链表中,每次获取锁的时候,看当前维护的那个线程id和当前请求的线程id是否一样,一样就可重入了
synchronized保证原子性和可见性
volatile保证可见性
ThreadLocal和synchronized 都解决多线程并发访问
synchronized用于实现同步机制,利用锁的机制使变量或代码块在某一时刻只能被一个线程访问 -----时间换空间
ThreadLocal为每个线程都提供变量的副本,每个线程在某一时间访问到的不是同一个对象----空间换时间
ThreadLocal
java提供的一种保护线程私有信息的机制,其在整个线程生命周期内有效。方便地再一个线程关联的不同业务模块之间传递信息,例如事务id,cookie等上下文相关信息。
为每个线程维护变量的副本(Map),共享数据的可见范围为同一个线程之内。
使用注意:remove
基于ThreadLocalMap实现,key是个弱引用。废弃的回收依赖于显式的触发,否则就要等待线程结束,回收相应的ThreadLocalMap—OOM的来源。
建议:应用要自己负责remove,并且不要和线程池配合,以为worker线程往往是不会退出的。
JUC并发工具java.util.concurrent及其子包
CountDownLatch,CyclicBarrier,Semaphore等可以实现更丰富的多线程操作的同步结构。
ConcurrentHashMap,有序的ConcurrentSkipListMap或者通过类似快照机制实现线程安全的动态数组CopyOnWriteArrayList等,各种线程安全的容器
ArrayBlockingQueue、SynchorousQueue或针对特定场景的PriorityBlockingQueue等,各种并发队列实现
Executor框架,可以创建各种不同类型的线程池,调度任务运行等。
ReadWriteLock 和 StrampedLock
实际场景中不需要大量竞争的写操作,以并发读为主,进一步优化并发操作的粒度,java提供了读写锁
ReadWriteLock 代表一对锁,读锁和写锁,数据量较大,并发读多,并发写少的时候,能凸出优势。有相对大的开销
StrampedLock:提供类似读写锁的同时,支持优化读模式,逻辑:先试着修改,然后通过validate方法确认是否进入写模式,如果没有进入,就成功避免了开销,如果进入,则尝试获取读锁。
public class StampedSample {
private final StampedLock s1 = new StampedLock();
void mutate(){
long stamp = s1.writeLock();
try{
write();
}finally {
s1.unlockWrite(stamp);
}
}
Data access(){
long stamp = s1.tryOptimisticRead();
Data data = read();
if (!s1.validate(stamp)){
stamp = s1.readLock();
try {
data = read();
}finally {
s1.unlockRead(stamp);
}
}
return data;
}
}
让线程彼此同步,同步器?
同步器:CountDownLatch,CyclicBarrier,Semaphore实现多个线程之间协作的功能
CountDownLatch 倒计数,允许一个或多个线程等待某些操作完成。
场景:
使用:
CountDownLatch 构造方法指明计数数量,被等待线程调用CountDownLatch 将计数器减1,等待线程使用await进行线程等待。
CyclicBarrier 循环栅栏,实现让一组线程等待至某个状态之后再全部执行,而且当所有等待线程被释放后,CyclicBarrier可以被重复使用,
场景:用来等待并发线程结束
主要方法:await(),每被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此CyclicBarrier上面阻塞的线程开始运行。之后如果再次调用await(),计数变成N-1,新一轮重新开始,CyclicBarrier.await()带有返回值,用来表示当前线程是第几个到达这个Barrier的线程。
Semaphore:java版本的信号量实现,用于控制同时访问的线程个数,来达到限制通用资源访问的目的,原理是通过acquire()获取一个许可,如果没有就等待,而release()释放一个许可。如果值初始化为1,那么一个线程就可以通过acquire进入互斥状态,本质上和互斥锁是相似的,区别:互斥锁是持有者的,而对于Semaphore这种计数器结构,虽然有类似的功能,但其实不存在真正意义上的持有者,除非进行扩展包装。
CyclicBarrier和CountDownLatch 对比
行为有一定相似度,区别:
CountDownLatch 不可以重置,无法重用,CyclicBarrier可以重用
CountDownLatch 基本组合:CountDown/await,调用await的线程阻塞等待countDown足够的次数
CyclicBarrier基本组合:CyclicBarrier/await,当所有都调用了await 才继续进行任务并自动进行重置。
CountDownLatch 目的:让一个线程等待其他n个线程达到某个条件后,自己再去做某个事情
CyclicBarrier目的让N多线程互相等待直到所有的都达到某个状态,然后这N个线程再继续执行各自后续。
java线程池的实现
线程:被抽象为一个静态内部类worker,基于AQS实现,存放在线程池HashSet成员变量中
成员变量workQueue(BlockingQueue)需要执行的任务存放在成员变量中。
思想:从workQueue中不断取出需要执行的任务,放在workers中进行处理.
线程池核心构造参数
corePoolSize:核心线程数
maximumPoolSize:线程池允许的最大线程数
keepAliveTime:超过核心线程数时闲置线程的存活时间
workQueue:任务执行前保存任务的队列,保存由execute方法提交的额Runnable任务
线程池中线程如何创建?是一开始就随线程池的启动创建好的么?
不是,线程池默认初始化后不启动worker,等有请求时才启动,调用execute()添加一个任务时,线程池做如下的判断:
一个线程完成任务会从队列中取下一个任务来执行,一个线程没有任务执行,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,线程会被停掉,所有线程池的所有任务完成后,最终会收缩到corePoolSize的大小。
默认线程池
SingleThreadExecutor 线程池:只有一个核心线程在工作,相当于单线程串行执行所有任务,如果唯一线程因为异常结束,会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行
corePoolSize:1;maximumPoolSize:1;keepAliveTime:0L;workQueue:new LinkedBlockingQueue,器缓冲队列是无界的。
FixedThreadPool线程池:固定大小的线程池,只有核心线程,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。多数针对一些很稳定很固定的正规并发线程,多用于服务器
CachedThreadPool线程池,无界线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲线程,当任务数增加时,此线程池又可以智能的添加新任务来处理任务。线程池大小完全依赖于操作系统能够创建的最大线程大小,SynchronousQueue是一个缓冲区为1的阻塞队列,缓存性通常用于执行一些生存期很短的异步型任务,在一些面向连接的daemon型SERVER中用得不多,但对于生存期短的异步任务是首选。
ScheduledThreadPool:核心线程池固定,大小无限的线程池,支持定时周期性的执行任务的需求,创建一个周期性执行任务的线程池,如果闲置,非核心线程会在DEFAULT_KEEPALIVEMILLIS时间内回收。
在java线程池中提交线程