线程线程线程

什么是线程安全

线程安全指的是在多线程环境下,当多个线程同时访问同一个共享资源时,系统能够保证资源数据的正确性和一致性,不会出现数据污染或不一致的情况

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

  • 加锁:使用同步机制,如synchronized、ReentrantLock等来保证同一时间只有一个线程访问共享变量和对象。
  • 使用线程安全的集合类:如ConcurrentHashMap、CopyOnWriteArrayList等
  • 使用不可变对象:不可变对象一旦被创建就不可修改,因此不需要同步控制
  • 使用ThreadLocal变量:ThreadLocal变量只能被当前线程访问,因此不存在线程安全问题。

一、线程与进程的区别

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

           2、同一进程的多个线程共享进程的资源(内存、文件等)。

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

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

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

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

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

二、死锁与活锁

1、什么是死锁

死锁是指多个线程在执行过程中,因争夺资源而造成的一种互相等待的状态,导致所有线程在无外力作用下都无法继续执行下去。

2、死锁的必要条件

  • 互斥条件:资源一次只能被一个线程占用。

  • 占有并等待:线程持有至少一个资源,并等待获取其他线程占用的资源。

  • 不可抢占:已分配给线程的资源不能被其他线程强行夺取,必须由线程主动释放。

  • 循环等待:存在一个线程的循环等待链(如T1等待T2,T2等待T3,T3等待T1)。

3、什么是活锁

并未产生线程阻塞,但是由于某种问题(不断重试失败的操作)的存在,导致无法继续执行的情况。

  • 线程不会阻塞,但无法完成任务(类似于“谦让”导致的无限循环)。

  • 通常发生在重试机制冲突解决策略不当时。

二、创建线程的几种方法

1、继承Thread类 

public class MyThread extends Thread(){
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("MyThread执行+"i);
        }
    }
}
public class test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

2、实现Runnable接口

public class MyRunnable implements Runnable(){
    @Override
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("MyRunnable执行+"i);
        }
    }
}
public class test {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

 3、实现Callable接口

public class MyCallable implements Callable<String>(){
    @Override
    public String call() throws Exception{
        for(int i=0;i<10;i++){
            System.out.println("MyCallable执行+"i);
        }
        return "MyCallable执行成功!"
    }
}
public class test {
    public static void main(String[] args) {
        FutureTask<String> task = new FutureTask<String>(new MyCallable());
        Thread thread = new Thread(task);
        thread.start();
    }
}

四、Runnable接口和 Callable接口区别

runnable:run方法无返回值,不能抛出异常,通过 Thread 或 ExecutorService 执行

callable:call方法有返回值,并且能抛出异常,只能通过 ExecutorService 提交(submit()

五、 线程生命周期

状态说明
NEW线程刚被创建,但尚未启动(未调用 start()
RUNNABLE线程正在 JVM 中执行(可能正在运行,就绪状态(等待 CPU 时间片))
BLOCKED线程被阻塞,等待获取监视器锁(如进入 synchronized 代码块时锁被其他线程占用)
WAITING线程无限期等待,直到被其他线程显式唤醒(如 Object.wait()Thread.join()
TIMED_WAITING线程有限期等待(如 Thread.sleep(ms)Object.wait(timeout)
TERMINATED线程执行完毕,已终止

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

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

七、java内存模型JMM

Java内存模型(Java Memory Model, JMM)是Java多线程编程的底层规范,它定义了:

  • 线程如何与内存交互

  • 共享变量的可见性规则

  • 指令重排序的限制

为什么需要JMM?

由于CPU缓存、指令重排序等优化,多线程环境下可能出现:

  • 内存可见性问题(一个线程修改数据,另一个线程看不到)

  • 指令重排序问题(代码执行顺序与编写顺序不一致)

JMM通过 happens-before规则 和 内存屏障 解决这些问题。

JMM内存模型(抽象概念)

  • 主内存(Main Memory):所有共享变量存储的位置

  • 工作内存(Work Memory):每个线程私有的内存,存储该线程使用的变量副本

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

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

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

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

十一、volatile

volatile 的作用

  1. 保证可见性:写操作立即刷新到主内存,读操作直接从主内存读取

  2. 禁止指令重排序:通过插入内存屏障(Memory Barrier)

  3. 不保证原子性(如volatile int i; i++仍不安全)

十、synchronized和 volatile的区别

synchronized和volatile都是java中关键字。

synchronized:

  • 保证代码块或方法的原子性。
  • 保证变量的可见性。线程在释放锁时,会将工作内存中的变量刷新到主内存。线程在获取锁时,会从主内存中读取变量的最新值。
  • 保证代码块的有序性(禁止指令重排序)。
  • 是一种锁机制,线程进入同步代码块前需要获取锁。性能开销较大。

volatile:

  • 不保证原子性,仅保证单个变量的读写原子性,不适合需要复合操作的场景(如 i++)。
  • 保证变量的可见性。每次读取 volatile 变量时,都会从主内存中读取最新值。每次写入 volatile 变量时,都会立即刷新到主内存。
  • 保证变量的有序性(禁止指令重排序)。
  • 不是锁机制,不会阻塞线程,性能开销较小。

八、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的原理是什么

    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解析——如果你愿意一层一层剥开我的心 - 掘金

      十四、如何确定锁对象

      • 如果修饰的是代码块,表示指定参数对象 为锁对象。如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、为什么要用线程池

      • 降低资源消耗:通过复用已创建的线程,避免频繁创建和销毁线程的开销。

      • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。

      • 提高线程的可管理性:线程是稀缺资源,线程池可以统一进行分配、调优和监控,避免无限制创建线程导致系统崩溃。

      • 提供更强大的功能:支持延迟执行、定期执行、流量控制等功能

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

      1. 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
      2. 线程数量达到了corePoolSize,则将任务移入队列等待
      3. 队列已满,新建线程(非核心线程)执行任务
      4. 队列已满,总线程数又达到了maximumPoolSize,就会触发拒绝策略 RejectedExecutionHandler

      3、线程池的参数

      public ThreadPoolExecutor(
          int corePoolSize,          // 核心线程数
          int maximumPoolSize,       // 最大线程数
          long keepAliveTime,        // 空闲线程存活时间
          TimeUnit unit,            // 时间单位
          BlockingQueue<Runnable> workQueue, // 工作队列
          ThreadFactory threadFactory,       // 线程工厂
          RejectedExecutionHandler handler   // 拒绝策略处理器
      )
      参数说明
      corePoolSize核心线程数。线程池长期维持的线程数量,即使线程空闲也不会被回收(除非设置allowCoreThreadTimeOut)。
      maximumPoolSize最大线程数。线程池允许创建的最大线程数量。
      keepAliveTime空闲线程存活时间。当线程数大于核心线程数时,多余的空闲线程在等待新任务的最长时间,超过这个时间将被回收。
      unitkeepAliveTime的时间单位。
      workQueue工作队列。用于存放待执行任务的阻塞队列。
      threadFactory线程工厂。用于创建新线程,可以设置线程名、优先级、守护线程等,便于排查问题。
      handler拒绝策略处理器。当线程池和队列都已满时,如何处理新提交的任务。

             常用的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 线 程 池:是固定大小的线程池,这个方法创建的线程池适合能估算出需要多少核心线程数量的场景。
          public static ExecutorService newFixedThreadPool(int nThreads) {
                return new ThreadPoolExecutor(nThreads, nThreads,
                                              0L, TimeUnit.MILLISECONDS,
                                              new LinkedBlockingQueue<Runnable>());
          }

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

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

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

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

          public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
                return new ScheduledThreadPoolExecutor(corePoolSize);
          }
        
          //ScheduledThreadPoolExecutor():
          public ScheduledThreadPoolExecutor(int corePoolSize) {
            super(corePoolSize, Integer.MAX_VALUE,
                  DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
                  new DelayedWorkQueue());
          }

       二十三、ThreadLocal

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

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

       threadlocalmap:

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

       threadlocal内存结构

      评论 1
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

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

      抵扣说明:

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

      余额充值