当前位置:   article > 正文

线程线程线程_atomicboolean控制多线程顺序

atomicboolean控制多线程顺序

什么是线程安全

线程安全是指在多线程环境下,程序能够正确处理共享内存中的数据,而不会出现数据不一致、数据丢失、死锁等问题。在多线程环境下,多个线程可能会同时访问同一个共享变量或者对象,如果没有采取相应的措施,就可能出现线程安全问题。

为了保证线程安全,可以采取以下措施:

  • 加锁:使用同步机制,如synchronized、lock等来保证同一时间只有一个线程访问共享变量和对象。
  • 使用线程安全的数据结构:如ConcurrentHashMap、CopyOnWriteArrayList等
  • 使用不可变对象:不可变对象一旦被创建就不可修改,因此不需要同步控制
  • 使用ThreadLocal变量:ThreadLocal变量只能被当前线程访问,因此不存在线程安全问题。
  • 避免共享状态:尽可能避免共享变量和对象。通过消息传递等方式通信。

一、线程与进程的区别

进程:1、进程是资源分配的最小单位。

           2、 一个进程包含多个线程。

           3、每个进程拥有独立的地址空间。

           4、进程的创建、销毁和切换的开销比较大

线程:1、线程是cpu调度与分配的最小单位。

            2、一个进程中的多个线程拥有相同的地址空间,各个线程共享进程中的所有资源。

            3、线程的创建、销毁和切换的开销小

二、死锁与活锁

1、什么是死锁

是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

2、死锁的必要条件

  • 互斥条件:一个资源每次只能被一个线程使用,此时若有其他线程请求该资源,则只能等待。
  • 请求与保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不可剥夺条件:线程已获得的资源在未使用完之前,其他线程不能获得,只能在使用完时由自己释放。
  • 循环等待条件: 在发生死锁时,必然存在一个进程--资源的环形链。

3、什么是活锁

 并未产生线程阻塞,但是由于某种问题的存在,导致无法继续执行的情况。

例如:消息一直重试导致其他消息无法进行。

相互协作的线程彼此相应从而修改自己的状态。

二、创建线程的几种方法

1、继承Thread类 

  1. public class MyThread extends Thread(){
  2. @Override
  3.     public void run(){
  4. for(int i=0;i<10;i++){
  5. System.out.println("MyThread执行+"i);
  6. }
  7.     }
  8. }
  9. public class test {
  10. public static void main(String[] args) {
  11. MyThread thread = new MyThread();
  12. thread.start();
  13. }
  14. }

2、实现Runnable接口

  1. public class MyRunnable implements Runnable(){
  2. @Override
  3.     public void run(){
  4. for(int i=0;i<10;i++){
  5. System.out.println("MyRunnable执行+"i);
  6. }
  7.     }
  8. }
  9. public class test {
  10. public static void main(String[] args) {
  11. Thread thread = new Thread(new MyRunnable());
  12. thread.start();
  13. }
  14. }

 3、实现Callable接口

  1. public class MyCallable implements Callable<String>(){
  2. @Override
  3.     public String call() throws Exception{
  4. for(int i=0;i<10;i++){
  5. System.out.println("MyCallable执行+"i);
  6. }
  7. return "MyCallable执行成功!"
  8.     }
  9. }
  10. public class test {
  11. public static void main(String[] args) {
  12. FutureTask<String> task = new FutureTask<String>(new MyCallable());
  13. Thread thread = new Thread(task);
  14. thread.start();
  15. }
  16. }

四、Runnable接口和 Callable接口区别

callable的call方法可以抛出异常,runnable的run方法不能抛出异常。

callable的call方法有返回值,runnable的run方法没有返回值。

五、 线程生命周期

新建:new关键字创建一个线程后,该线程为新建状态。

就绪:当线程调用start()方法后,该线程为就绪状态。

运行:当线程获得cpu资源,执行run()方法后,该线程为运行状态。

阻塞:1、当线程调用sleep()方法主动放弃所占用的处理器资源。

            2、当线程试图获取一个同步锁,该同步锁被其他线程所持有。

            3、当线程调用了一个阻塞式IO方法,在该方法返回之前,该线程是阻塞的。

            4、当线程在等待某个通知(notify)

            5、当程序调用了线程的suspend方法将该线程挂起。这个方法容易导致死锁。

死亡:1、run()或者call()方法执行完成,线程正常结束。

            2、线程抛出一个未捕获的Exception或Error。

            3、调用线程的stop()方法结束线程,容易产生死锁。

六、现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行?

  • 使用join。在T1线程中调用T2.join(),在T2线程中调用T3.join()。

       join讲解参考博客Java多线程中join方法的理解 - 每天进步一点点! - ITeye博客

七、java内存模型JMM

JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

java内存模型是java程序对内存的访问都能得到一致效果的机制与规范。每条线程有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存的副本拷贝,解决了由于多线程通过共享内存进行通信时存在的原子性、可见性(缓存一致性)以及有序性问题。java还提供了一些常用的关键字:synchronized、volatile、并发包等。

原子性:线程是CPU调度的基本单位,CPU有时间片的概念,会根据不同的调度算法进行调度,在多线程场景下,一系列操作可能没有都执行完就被要求放弃CPU,等待重新调度,这种情况下,这一系列操作就不是一个原子操作,就存在原子性问题。

可见性(缓存一致性):所有的变量都存在主存中,每个线程有自己的工作内存,里面保存了线程用到的变量的主内存副本拷贝,线程不能直接操作主内存。就可能存在缓存不一致的情况。

有序性:由于处理器优化和指令重排导致CPU可以对代码进行乱序执行,在多线程情况下就有可能出现有序性问题。

八、ReentrantLock和synchronized的区别

  • 锁的获取方式:synchronized是隐式锁,即在代码块或者方法上加上关键字synchronized,而ReentrantLock是显式锁,需要在代码中创建ReentrantLock对象,并调用lock方法获取锁。
  • 锁的释放方式:synchronized在代码块或方法执行后会自动释放锁,而ReentrantLock则需要在finally中调用unlock方法释放锁。
  • 锁的中断性:synchronized在获取锁失败时会一直等待,知道获取到锁为止。而ReentrantLock提供了可中断的获取锁方式,即在等待锁的过程中,可以调用lockInterruptibly方法中断等待。
  • 锁的公平性:synchronized不保证现场获取锁的公平性,而ReentrantLock提供了可以选择公平性或者非公平性的锁获取方式。
  • 能否实现Condition:ReentrantLock可以通过Condition接口实现精确的线程等待/通知机制,而synchronized不可以。

九、ReentrantLock原理

ReentrantLock是可重入的互斥锁,基于AQS的实现的,AQS是一个先进先出的双端队列,ReentrantLock维护了一个同步队列,线程在获取锁时,如果锁已经被占用,就会进入同步队列等待。如果同步队列不为空,那么队列的第一个节点就是当前持有锁的线程。获取锁时,会先尝试CAS操作修改同步状态,如果成功则获取锁,如果失败则加入同步队列并自旋等待锁的释放。

ReentrantLock定义了一个volatile int的变量计数器表示同步状态。每个线程在获取锁的时候都会将计数器加一,每次释放锁的时候都会将计数器减一。如果计数器为0,则标识锁已经完全释放,任何线程都可以对锁进行获取。

非公平锁:lock()方法的逻辑: 多个线程调用lock()方法, 如果当前计数器为0, 说明当前没有线程占有锁, 那么只有一个线程会CAS获得锁, 并设置此线程为独占锁线程。那么其它线程会调用acquire方法来竞争锁,acquire方法调用tryAcquire,成功获取锁返回true,不成功返回false,并把当前线程放到AQS队列中去。队列中的线程只能按顺序获取锁。

公平锁:lock()方法的逻辑:不检查计数器字段,直接调用acquire方法,直接放到AQS队列尾部,按顺序获取锁。

放到队列后如果队列为空的情况下不用挂起可以直接获取锁。

队列中的线程什么时候被唤醒:前驱节点是同步队列的头节点时被唤醒。

深入剖析ReentrantLock公平锁与非公平锁源码实现_研发之道的博客-CSDN博客

十、synchronized和 volatile的区别

synchronized和volatile都是java中关键字。

synchronized:

  • 用于保证多个线程在同一时刻只有一个线程可以访问临界区代码,从而保证数据的一致性和线程安全性
  • synchronized可以用在方法或者代码块中。
  • 可以保证原子性、可见性和有序性。

volatile:

  • 用于保证多个线程之间共享变量的可见性。
  • 只能用于修饰变量
  • 只能保证可见性,不能保证原子性和有序性。只能保证对单个volatile变量的写操作具有原子性,对于符合操作i++不能保证原子性。volatile可以禁止指令重排序,保证同一个volatile变量的有序性,但是不能保证多个volatile变量之间操作的有序性。

十一、volatile

只能保证可见性,不能保证有序性、原子性。

  • 可见性:在一个线程中改变变量的值,新值能立即同步到主内存,每次使用该变量前都会立即从主内存获取最新的值,即保证了内存的可见性,避免了线程之间的数据不一致的问题。

十二、为什么synchronized无法禁止指令重排序,却能保证指令有序性。

为了提升计算机各个方面能力,在硬件层面做了一优化,比如指令重排序等,但是这些技术的引入就会导致有序性的问题。但是,在java中,不管怎么排序,都不能影响单线程程序的执行结果 ,就是遵守as-if-serial语义。当某个线程执行到一段被synchronized修饰的代码之前,会先进行加锁,执行完之后解锁,在加锁和解锁之间,其他线程是无法获得这段代码的锁,所以就保证了同一时间内,被synchroized修饰的代码是单线程执行的,满足as-if-serial语义的单线程的关键前提,所以可以保证有序性。

十三、synchronized的原理是什么

synchronized是基于对象的监视器锁来实现的,每一个对象都有一个监视器锁,当一个线程执行synchronize代码块时,会尝试获取这个对象的监视器锁。如果该锁没有被其他线程占用,则获取该锁并执行synchronize代码块,如果该锁被其他区线程占用,则该线程进入阻塞状态,直到获取锁为止。

  • 同步代码块是通过monitorenter和monitorexit来实现,当线程执行到monitorenter的时候要先获得monitor锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。
  • 同步方法是通过中设置ACC_SYNCHRONIZED标志来实现,当线程执行有ACC_SYNCHRONI标志的方法,需要获得monitor锁。
  • 每个对象维护一个加锁计数器,为0表示可以被其他线程获得锁,不为0时,只有当前锁的线程才能再次获得锁。
  • 同步方法和同步代码块底层都是通过monitor来实现同步的。
  • 每个对象都与一个monitor相关联,线程可以占有或者释放monitor。

 monitor实现原理

  • 想要获取monitor的线程,首先会进入_EntryList队列。
  • 当某个线程获取到对象的monitor后,进入_Owner区域,设置为当前线程,同时计数器_count加1。
  • 如果线程调用了wait()方法,则会进入_WaitSet队列。它会释放monitor锁,即将_owner赋值为null,_count自减1,进入_WaitSet队列阻塞等待。
  • 如果其他线程调用 notify() / notifyAll() ,会唤醒_WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入_Owner区域。
  • 同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。

对象与monitor怎么关联

  • 对象里有对象头
  • 对象头里面有Mark Word
  • Mark Word指针指向了monitor

参考Synchronized解析——如果你愿意一层一层剥开我的心 - 掘金

十三、wait()和sleep()的区别

  • 调用wait()方法会释放锁资源;调用sleep()方法不会释放锁资源。 
  • wait()方法是Object类中的,作用于对象本身,线程间交互;sleep()定义在Java.lang.Thread中,作用于当前线程,暂停线程执行。
  • 唤醒:调用wait()后需要其他线程调用对象的notify()或者notifyAll()来唤醒;调用sleep()后,过了睡眠时间自动唤醒,或者调用interrupt()方法。
  • wait()只能在同步代码(synchronized)中使用;sleep()不需要在同步代码中使用。
  • wait()是实例方法;sleep()是静态方法。

十四、如何确定锁对象

  • 如果修饰的是代码块,表示指定参数对象 为锁对象。如Synchronized(对象名)。
  • 如果修饰的方法为非静态方法,表示此方法对应的对象 为锁对象。
  • 修饰的方法为静态方法,则表示此方法对应的类对象 为锁对象。

十五、JVM 对 Java 的原生锁做了哪些优化?

锁升级的过程:最开始是无锁状态,当有线程请求时是偏向锁,会偏向于第一个获得这个锁的线程,如果偏向锁不成功的话,这个时候锁会升级为轻量级锁,是用cas实现的锁,如果cas没有设置成功的话,他会进行一个自旋,自旋到一定次数的时候会升级为synchronized这样一个重量级锁。

  • 自旋锁:让等待锁的线程循环等待其他线程释放锁,不放弃处理器的执行时间。避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果锁被占用的时间很长,那么自旋线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次,用户可以使用参数-XX:PreBlockSpin来更改。
  • 自适应自旋锁:自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在进行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将请允许自旋等待持续相对更长的时间,比如100个循环。另一方面,如果对于某个锁,自旋很少成功获得过,那在以后获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。
  • 锁消除:锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。
  • 锁粗化:原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小---只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快地拿到锁。大部分情况下,上面的原则都是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
  • 轻量级锁:在整个同步周期内都是不存在竞争的情况下,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个Bits)u将转变为“00”,即表示此对象处于轻量级锁定的状态。轻量级锁使用CAS操作避免了使用互斥量的开销。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥锁)的指针,后面等待锁的线程也要进入阻塞状态。
  • 偏向锁:这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。假设当前虚拟机启用了偏向锁(启用参数-XX:+UseBiasedLocking,这是JDK1.6的默认值),那么,当锁对象第一次被线程获取的时候,虚拟机将会把对象逆流而上中的标志位设为“01”。即偏向模式。同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word之中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束

       参考:Java的锁优化 - 简书

十六、乐观锁和悲观锁

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,就是共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。悲观锁先加锁,效率低。更新失败的概率比较低。适合读少写多的场景。
  • 乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。适合读多写少的场景。乐观锁只能保证一个共享变量的原子操作。CAS 长时间不成功而一直自旋,会 给 CPU 带来很大的开销。

参考既生synchronized,何生volatile-HollisChuang's Blog

十七、如何终止一个线程

  • 使用volatile变量终止正常运行的线程 + 抛异常法/Return法
  • 组合使用interrupt方法与interruptted/isinterrupted方法终止正在运行的线程 + 抛异常法/Return法
  • 使用interrupt方法终止 正在阻塞中的 线程

二十、线程间通信

  • Object的wait(必须在synchronized下使用)、notify、notifyAll
  • Condition的await(必须在Lock锁下使用)、signal、signalAll

二十一、java.util.concurrent包

1、Java并发包原子类automic

AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

原理:CAS(比较并交换)、volatile修饰变量。

CAS:它包含 3 个参数 CAS(V,E,N),V表示要更新变量的值,E表示预期值,N表示新值。仅当 V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程完成更新,则当前线程则什么都不做,最后CAS 返回当前V的真实值。

AtomicInteger原理:AtomicInteger有一个volatile修饰的value属性,可以保证多个线程修改时能够看到最新的值。通过CAS来保证多个线程之间对value的操作是互斥的。

AtomicIntegerArray原理:AtomicIntegerArray内部维护了一个volatile修饰的数组,每个数组元素都是一个AtomicIntegerArray类型。

2、同步器CountDownLatch、CyclicBarrier、Semaphore

  • CountDownLatch:用于某个线程A等待若干个线程执行完成之后,线程A才执行。(只能只用一次)

是基于AQS实现的,通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

  • CyclicBarrier:一组线程等待某个状态之后再全部同时执行。(可多次使用)

是 ReentrantLock 和 Condition 的组合使用,在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。CyclicBarrier有一个静态内部类Generation,该类的对象代表栅栏的当前代,就像玩游戏时代表的本局游戏,利用它可以实现循环等待。barrierCommand表示换代前执行的任务,当count减为0时表示本局游戏结束,需要转到下一局。在转到下一局游戏之前会将所有阻塞的线程唤醒,在唤醒所有线程之前你可以通过指定barrierCommand来执行自己的任务。

参考深入理解CyclicBarrier原理_晨初听雨的博客-CSDN博客_cyclicbarrier

  • Semaphore:是用于控制同时访问特定资源的线程的数量,它通过协调各个线程,保证合理的使用公共资源。(比如:1、多个共享资源互斥使用。2、并发线程数的控制。)

线程可以通过acquire()方法来获取信号量的许可,当信号量中没有可用的许可的时候,线程阻塞,直到有可用的许可为止。线程可以通过release()方法释放它持有的信号量的许可。

二十二、线程池 https://www.cnblogs.com/superfj/p/7544971.html

在 Java 中,所谓的线程池中的“线程”,其实是被抽象为了一个内部类Worker,存放在线程池的HashSet<Worker> workers 成员变量中;而需要执行的任务则存放在成员变量 workQueue(BlockingQueue<Runnable> workQueue)中。这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出 需要执行的任务,放在 Workers 中进行处理。

1、为什么要用线程池

  • 降低创建、销毁线程造成的消耗。
  • 有效的控制线程最大并发数,避免同时执行的线程过多,就有可能导致系统资源(cpu频繁切换)不足而产生阻塞的情况。
  • 对线程进行一些简单的管理,比如:延时执行、定时循环执行的策略等。

2、线程执行过程

当一个任务被添加进线程池时:

  1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  2. 线程数量达到了corePools,则将任务移入队列等待
  3. 队列已满,新建线程(非核心线程)执行任务
  4. 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常 

3、线程池的参数

  • corePoolSize:线程池的核心线程数。
  • maximumPoolSize:线程池允许的最大线程数。
  • keepAliveTime:线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁。
  • workQueue:任务执行前保存任务的队列,保存由 execute 方法提交的 Runnable 任 务 。线程池所使用的任务缓冲队列。
  • RejectedExecutionHandler:饱和策略当等待队列已满,线程数也达到最大线程数时,线程池会根据饱和策略来执行后续操作,默认的策略是抛弃要加入的任务。

       常用的workQueue类型:

  • SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
  • LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
  • ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
  • DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务 

       拒绝策略

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException
  • CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
  • DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
  • DiscardPolicy:丢弃任务,不做任何处理。

 4、如何在 Java 线程池中提交线程 

execute():没有返回值。

submit():有返回值,内部调用execute(),方便Exception处理。

5、线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

6、都有哪些已经实现的线程池

  • FixedThreadPool 线 程 池:是固定大小的线程池,这个方法创建的线程池适合能估算出需要多少核心线程数量的场景。
    1. public static ExecutorService newFixedThreadPool(int nThreads) {
    2. return new ThreadPoolExecutor(nThreads, nThreads,
    3. 0L, TimeUnit.MILLISECONDS,
    4. new LinkedBlockingQueue<Runnable>());
    5. }

  • SingleThreadExecutor 线 程 池:有且只有一个线程在工作,适合任务顺序执行,缺点但是不能充分利用CPU多核性能。
    1. public static ExecutorService newSingleThreadExecutor() {
    2. return new FinalizableDelegatedExecutorService
    3. (new ThreadPoolExecutor(1, 1,
    4. 0L, TimeUnit.MILLISECONDS,
    5. new LinkedBlockingQueue<Runnable>()));
    6. }

  • CachedThreadPool 线 程 池:是无界线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。SynchronousQueue 是一个是缓冲区为 1 的阻塞队列。 缓存型池子通常用于执行一些生存期很短的异步型任务,因此适合生存期短的异步 任务。

    1. public static ExecutorService newCachedThreadPool() {
    2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    3. 60L, TimeUnit.SECONDS,
    4. new SynchronousQueue<Runnable>());
    5. }

  • newScheduledThreadPool线程池:执行周期性任务,类似定时器。此线程 池支持定时以及周期性执行任务的需求。创建一个周期性执行任务的线 程池。

    1. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    2. return new ScheduledThreadPoolExecutor(corePoolSize);
    3. }
    4. //ScheduledThreadPoolExecutor():
    5. public ScheduledThreadPoolExecutor(int corePoolSize) {
    6. super(corePoolSize, Integer.MAX_VALUE,
    7. DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
    8. new DelayedWorkQueue());
    9. }

 二十三、ThreadLocal

threadlocal:为每个线程提供一个变量副本,从而实现访问互不干扰。多个线程之间数据相互隔离。

 synchronized:只提供一份变量,让不同线程排队访问。多个线程访问资源同步。

 threadlocalmap:

  • 初始容量16,扩容容量为原容量的2倍。
  • 计算新增元素在map中的位置:先获取hashcode,再根据黄金分割数算出一个值(目的是使元素分布更均匀),再和length-1做&操作。
  • 解决冲突算法:线性探测法。冲突map索引值加一。

 threadlocal内存结构

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/674643
推荐阅读
相关标签
  

闽ICP备14008679号