多线程与高并发JUC

线程池

线程池定义及作用

Java线程池主要用于管理线程组及其运行状态,以便Java虚拟机更好地利用CPU资源.线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,以保证系统高效且安全地运行。

Java线程池的工作原理

JVM先根据用户的参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量(用户设置的线程池大小),则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从队列中取出任务并执行。

线程池的三大好处

1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

3.提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

线程复用

在Java中,每个Thread类都有一个start方法。在程序调用start方法启动线程时,Java虚拟机会调用该类的run方法。也就是,在Thread类的run方法中其实调用了Runnable对象的run方法,因此可以继承Thread类,在start方法中不断循环调用传递进来的Runnable对象,程序就会不断执行run方法中的代码。可以将在循环方法中不断获取的Runnable对象存放在Queue中,当前线程在获取下一个Runnable对象之前可以是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。这样就简单实现了一个线程池,达到了线程复用的效果。

线程池的执行过程

1.如果当前运行的线程少于corePoolSize,则创建新线程来执行任务

2.如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3.在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。

4.如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

5.在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。

6.在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

线程池的核心组件和核心类

◎ 线程池管理器:用于创建并管理线程池。

◎ 工作线程:线程池中执行具体任务的线程。

◎ 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,线程中的任务才能够被线程池调度。

◎ 任务队列:存放待处理的任务,新的任务将会不断被加入队列中,执行完成的任务将被从队列中移除。

Java中的线程池是通过Executor框架实现的,在该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask这几个核心类

工作线程

线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到这点。

线程池中的线程执行任务分两种情况

1.在execute()方法中创建一个线程时,会让这个线程执行当前任务。

2.反复从BlockingQueue获取任务来执行。

线程池的创建

可以通过ThreadPoolExecutor来创建一个线程池

七大核心参数

四大拒绝策略

1.AbortPolicy:直接抛出异常。

2.CallerRunsPolicy:只用调用者所在线程来运行任务。

3.DiscardOldestPolicy:移除线程队列中最早的一个线程任务,并尝试提交当前任务

4.DiscardPolicy:不处理,丢弃掉。

常用阻塞队列

❑ ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。

❑ LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

❑ SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

❑ PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

向线程池提交任务

execute方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功

submit方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,

shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true

合理地配置线程池

1.CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

2.优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

3.执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。

4.依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU。

线程池的监控

❑ taskCount:线程池需要执行的任务数量。

❑ completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。

❑ largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。

❑ getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。

❑ getActiveCount:获取活动的线程数。

可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。例如,监控任务的平均执行时间、最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。

5大常用的线程池

对于执行时间很短的任务而言,newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。在线程池的keepAliveTime时间超过默认的60秒后,该线程会被终止并从缓存中移除,因此在没有线程任务运行时,newCachedThreadPool将不会占用系统的线程资源。
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}
newFixedThreadPool用于创建一个固定线程数量的线程池,并将线程资源存放在队列中循环使用。在newFixedThreadPool线程池中,若处于活动状态的线程数量大于等于核心线程池的数量,则新提交的任务将在阻塞队列中排队,直到有可用的线程资源,具体的创建方式如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
newScheduledThreadPool创建了一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
一池有1个工作线程,newSingleThreadExecutor线程池会保证永远有且只有一个可用的线程,在该线程停止或发生异常时,newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务:
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}
newWorkStealingPool创建持有足够线程的线程池来达到快速运算的目的,在内部通过使用多个队列来减少各个线程调度产生的竞争。这里所说的有足够的线程指JDK根据当前线程的运行需求向操作系统申请足够的线程,以保障线程的快速执行,并很大程度地使用系统资源,提高并发计算的效率,省去用户根据CPU资源估算并行度的过程。当然,如果开发者想自己定义线程的并发数,则也可以将其作为参数传入。 
public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool
        (parallelism,
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}
public static ExecutorService newWorkStealingPool() {
    return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

Executor框架

在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源。同时,为每一个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。

Java的线程既是工作单元,也是执行机制.从JDK 5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。

Executor框架的两级调度模型

在HotSpot VM的线程模型中,Java线程(java.lang.Thread)被一对一映射为本地操作系统线程。Java线程启动时会创建一个本地操作系统线程;当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU.

在上层,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;在底层,操作系统内核将这些线程映射到硬件处理器上。

应用程序通过Executor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。

Executor框架3大部分组成部分

❑ 任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。

❑ 任务的执行。包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。

❑ 异步计算的结果。包括接口Future和实现Future接口的FutureTask类。

Executor框架核心类

❑ Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。

❑ ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。

❑ ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。

❑ Future接口和实现Future接口的FutureTask类,代表异步计算的结果。

❑ Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。

 

 

多线程与高并发

基础概念

程序 进程 线程 纤程或协程

程序 进程 线程 纤程或协程的概念

硬盘上某一个可执行文件就是一个程序,当双击这个程序的时候,操作系统会启动一个和这个程序对应的进程,一个程序可以有很多个进程

进程是指这个程序跑起来了,放在内存中开始执行了,CPU开始执行他的指令了,这就是一个进程

在一个进程中有好多个工作时并行进行的,如显示页面,网络传输,存盘这些工作时并行进行的,如何进行并行进行的任务呢,通过线程来

在一个线程中还可以做并行任务,一个线程里面不同的执行路径同时可以执行的这种,叫纤程也称协程

进程和线程的区别

进程叫做分配资源的单位,线程叫做执行单位(调度单位),线程是一个进程中的不同执行路径,是进程的最小执行单元

进程启动会给他分配内存空间,起线程的时候不会给线程分配任何的内存空间,这些线程共享进程的内存空间,所以,分配资源按照进程来,真正执行按照线程来

线程或协程

轻量级的线程,线程中的线程

在用户空间中,不经过操作系统内核,在jvm内部模拟一些线程出来,stack和PC就放在用户空间中,这些线程都是自己模拟出来的,不需要经过操作系统,这些模拟出来的线程叫做纤程也叫协程

线程状态迁移图

1.synchronized锁升级过程

解析:锁升级就是synchronized内部进行了一些相应的优化,要锁定某一个对象,是在对象头上面的某两位,来指定到底锁的是哪种类型的锁,在对象头上还记录着当前哪个线程申请了这把锁,偏向锁的概念就是,当线程来了的时候,先不尝试对它进行加锁,只是记录这个线程的id值,我们就认为这个对象是这个线程独有,下次再来申请这把锁的时候,这个锁就会偏向于当前线程,没有真正的加锁,他只是说你下次来的时候判断你如果还是原来的那个线程你就不需要加锁了,就继续直接访问,因此效率上稍微要高一些.当来了一个新的线程,发现新的线程id和原来锁对象上记录的id不相等,就会进行锁升级,首先会尝试自旋锁的升级,新的线程会一个CAS操作尝试获取锁,默认10次,如果拿不到,这时候升级为重量级锁.经过OS,进入等待队列,就不再占用CPU时间了

2. synchronized和ReentrantLock异同

synchronized系统自动加锁自动解锁,默认进行了四种状态的升级

ReentrantLock需要我们手动加锁手动解锁,可以出现各种的Condition,Condition就是不同的等待队列,底层时CAS的实现

3. LockSupport

当前线程阻塞park,如果没有LockSupport,如果想让当前线程阻塞,你得加在一把锁上面,使用wait和unwait,必须得有一把锁。而LockSupport不需要,想什么时候停就什么时候停。而且原来如果想要叫醒一个线程,叫醒一个线程我们得用notify,而这个线程往往是放在一个等待队列里面的,如果想要叫醒某个指定的线程其实是比较费劲的,LockSupport可以直接叫醒。而且unpark可以先于park调用,先调了代表线程中某个状态改变了,说我已经给你放行了,你后面要停车的时候也停不住了,因为unpark里面告诉你了,你不用停车了,停车了也没用了。用法上要比wait和notify要灵活。如果park两次,unpark一次,可以停住

unpark底层使用unsafe这个类来实现的

4.介绍volatile

volatile主要作用有两个,内存可见性和禁重排
内存可见性是由缓存一致性协议MESI来保证的,禁重排是由JMM来保证的(写屏障)

内存可见性指的是,当一个线程修改共享变量时,另一个线程可以读到修改的值

它的底层实现原理是,volatile修饰的共享变量在进行写操作的时候,底层会多出一个lock前缀指令,这个lock前缀指令做了两件事情,一个是将当前处理器缓存行的数据写回到系统内存,还一个就是这个写回内存的操作会是其他CPU里缓存了该内存地址的数据无效

通常情况下,为了提高处理速度,处理器是不会和内存直接进行通信的,而是将系统内存的数据先读取到内部缓存,但是操作中就不知道这些数据什么时候写回到系统内存;如果对声明了volatile的变量进行修改时,jvm就会向处理器发送一条lock的前缀指令,就会使这个变量所在的缓存行中的数据写回到系统内存,当其他处理器发现自己缓存的数据行对应的内存地址被修改时,就会将当前处理器的缓存数据失效,当这个处理器在对这个数据进行修改操作时,就会重新把数据从系统内存中加载到缓存中

可以通过追加64位字节来提高性能,像LinkedTransferQueue,它在使用volatile变量的时候用一种追加字节的方式来优化队列的出队和入队的性能;缓存行是64字节宽,如果队列的头结点和尾节点都不足64字节的话,处理器会将他们读到同一个缓存行中,在多个处理器下,每个处理器都会缓存相同的头尾节点,当一个处理器试图修改头结点的时候,会将整个缓存行锁住,在缓存一致性的作用下,会导致其他处理器不能访问自己Cache中的尾节点,而队列需要频繁的修改队头和队尾节点,在多处理器的情况下会严重影响入队和出队的效率

有两种情况下不需要追加64字节,缓存行非64字节的处理器,共享变量不会被频繁的修改,因为使用追加字节的方式,需要处理器读取更多的字节到缓冲区,这本省就是一定的性能消耗,如果共享变量没有频繁的写操作的时候,锁的几率就比较小,没有必要通过追加字节的方式来避免互相锁定

当volatile修饰的引用的时候,引用内部发生变化,volatile时观察不到的

5.CAS

CAS比较并交换,CAS算法包括三个参数,要更新的变量(由对象内存位置和对象中变量的偏移量控制),变量的预期值和新值,当且仅当要更新的变量值等于预期值的时候,才会将新值设置为要更新的值,如果不成功,则循环再次尝试修改值

CAS是一种乐观锁,在访问之前不会加排它锁,而是在变量提交更新的时候,才会正式的去检测数据是否发生冲突

CAS的底层原理是Unsafe类和自旋锁

在多线程同时操作一个变量的时候,只有一个线程会操作成功,其他的都会失败,失败的线程不会被挂起,而是被告知失败,并且允许再次尝试,这就是自旋;

Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都是直接调用操作系统底层资源执行相应的任务

变量valueOffset表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存的偏移地址来获取数据的

变量value用的是volatile修饰,保证了多线程之间的内存可见性

CAS是一条CPU并发原语,在调用Unsafe类中的CAS方法的时候,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过他来实现原子操作

由于CAS是一条系统原语,原语属于操作系统范畴,是由若干条指令组成的,用于完成某一个特定功能的过程,原语的执行过程不允许被中断,也就是说,CAS是一条CPU原子指令,也就不会造成数据不一致的问题

CAS有三大问题,

循环时间开销大,消耗CPU资源

只能保证一个共享变量的原子操作,但是对于多个共享变量来说,循环CAS就无法保证原子性了,可以把多个共享变量合并为一个共享变量来操作,如i=1,j=a合并为ij=1a;jdk提供的AtomicReference类用来保证引用对象之间的原子性,其中就可以把多个变量放置到一个对象中来进行CAS操作

ABA问题:解决思路就是在变量前面添加版本号,jdk中的AtomicStampedReference类可以用来解决ABA问题.这个类的compareAndSet方法的作用是,首先检查当前引用是否为预期引用,并且检查当前标志是否为预期标志,如果全部相等,则以原子的方式更新值

6.AQS

AQS是一个抽象队列同步器,通过维护一个共享资源状态(volatile int state)和一个先进先出(fifo)的线程等待队列,来实现一个多线程访问共享资源的同步框架

AQS的原理是,为每个共享资源都设置一个共享资源锁,线程在访问共享资源时,首先会尝试获取共享资源锁,如果成功,则当前线程便可以使用共享资源,如果失败,则将当前线程放入线程等待队列,等待下一次的调度

AQS只是一个框架,只定义了一个接口,具体资源的获取,释放都是由具体的自定义同步器实现,不同的自定义同步器争用共享资源的方式也不同,自定义同步器只需要实现共享资源state的获取和释放即可,具体的线程等待队列的维护,获取资源失败入队,唤醒出队等,AQS在顶层已经实现好了,不需要具体的同步器再做处理,自定义同步器主要要实现这些方法

state是volatile类型,只能保证可见性,不能保证原子性,state的原子性是由getState(),setState(),compareAndSetState()这三个原子操作来保证的

AQS共享资源的方式有两种,独占式和共享式

独占式只有一个线程能够执行,如ReentrantLock,共享式多个线程同时执行,如Semaphore,CountDownLatch. 

ReentrantLock为独占式,state初始值0为无锁状态,在线程执行tryAcquire方法获取锁后,state状态+1,线程独占ReentrantLock锁,这时其他线程通过trAcquire方法获取该锁会失败,直到线程释放锁,state状态变为0,其他线程才有可能获取到锁.线程释放锁之前,可以重复获取这个锁,即ReentrantLock为可重入锁,每获取一次,state+1,每释放一次,state-1,获取多少次就要释放多少次,这样才能保证state最终为0;如果获取锁的次数大于释放锁的次数,则该线程一直持有这个锁,如果获取锁的次数少于释放锁的次数,程序会报锁异常

CountDownLatch将任务分为N个子线程去执行,将state初始化为N,N与线程个数一致,N个子线程并行执行,每个子线程执行完成后调用countDown方法一次,state执行CAS操作让state-1,在所有子线程都执行完成即state=0的时候会unpark主线程,然后主线程会从await返回,继续执行后续动作

一般来说自定义同步器要么采用独占方式,要么采用共享方式,实现tryAcquire,tryRelease或tryAcquireShared,tryReleaseShared其中一组即可.但也可以独占和共享方式同时存在,如ReentrantReadWriteLock

jdk9后,AQS中VarHandler->1.普通属性原子操作 2:比反射快,直接操纵二进制码

7. ThreadLocal

提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的副本,当多个线程访问这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题

用途:声明式事务,保证同一个Connection.多线程访问同一个共享变量时,用于同一变量的线程间隔离

    //ThreadLocal
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    //ThreadLocal
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    //ThreadLocal
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    //ThreadLocal
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    //ThreadLocal
    public void remove() {
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }

为什么Entry要使用弱引用,以及内存泄漏问题?

若是强引用,即使tl==null,但key的引用依然指向ThreadLocal对象,所以会有内存泄漏,而是用弱引用则不会

但还是有内存泄漏存在,ThreadLocal被回收,key的值变为null,则导致整个value再也无法被访问到,因此依然存在内存泄漏

所以使用ThreadLocal时,不用的时候一定要remove掉,否则还是会有内存泄漏

多线程编程

1.实现一个容器,提供两个方法,add和size,写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Main{
    List list = new ArrayList();
    public void add(Object o) {
        list.add(o);
    }
    public int size() {
        return list.size();
    }
    public static void main(String[] args) {
        Main main = new Main();
        final Object lock = new Object();
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("t2 启动");
                if (main.size() != 5) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t2 结束");
                lock.notify();
            }
        }, "t2").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println("t1 启动");
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    main.add(new Object());
                    System.out.println("add " + i);
                    if (main.size() == 5) {
                        lock.notify();
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }, "t1").start();
    }
}

2.生产者消费者问题

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class Main<T> {
    private final LinkedList<T> list = new LinkedList<>();
    private final int MAX = 10;
    private int count;
    private Lock lock = new ReentrantLock();
    private Condition producer = lock.newCondition();
    private Condition consumer = lock.newCondition();

    public void put(T t) {
        try {
            lock.lock();
            while (list.size() == MAX) {
                producer.await();
            }
            list.add(t);
            ++count;
            consumer.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public T get() {
        T t = null;
        try {
            lock.lock();
            while (list.size() == 0) {
                consumer.await();
            }
            t = list.removeFirst();
            count--;
            producer.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }

    public static void main(String[] args) throws Exception {
        Main<String> main = new Main<>();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    System.out.println(main.get());
                }
            }, "c" + i).start();
        }
        TimeUnit.SECONDS.sleep(1);
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 25; j++) {
                    main.put(Thread.currentThread().getName() + j);
                }
            }, "p" + i).start();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_43204550/article/details/108289820