- 线程图
- 创建线程的方法
继承 Thread 重写 run 方法
实现 Runnable 接口
实现 Callable 接口
-
runnable 和 callable 区别
runnable 没有返回值,callable有返回值
-
thread和runable区别
thread是一个是实现Runnable(抽象run方法)接口的类,通过start给Runnable 的run方法赋值了多线程特性
- 给run()方法传参三种方法
构造函数
成员变量
回调函数(引用类型使用)
-
线程有哪些状态
new 创建后尚未启动runnable 执行中。包括(Running:位于可运行线程池中,等待被线程调度选中,获取cpu的使用权 和 Ready:在获得cpu时间后,变为Running)
waiting等待(1、没有配置Timeout参数的Object.wait()方法;2、没有设置TImeout参数的Thread.join()方法;3、LockSupport.park()方法;
timed waiting等待到指定时间重新被唤醒(1、Thread.sleep();2、设置timeout的Object.wait();3、设置timeout的Thread.join()方法;4、LockSupport.parkNanos()方法;5、LockSupport.parkUntil()方法))
blocked阻塞:等待获取排它锁
terminated完成
-
sleep() 和 wait() 区别
父类:sleep(long mills) 来自 Thread,wait() 来自 Object
本质:sleep只会让cpu,不会呆滞锁行为改变;wait不仅让cpu,还会释放已占有的锁
释放锁:sleep() 不释放锁;wait() 释放锁
用法:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。
使用区别:sleep()方法可以在任何地方使用,wait()方法只能在synchronized方法或者synchronized块中使用
-
线程的 run() 和 start() 区别
run调用:会沿用主线程来执行重写run里的方法,只是Thread的一个普通方法的调用
start调用:会沿用非main的线程执行run里方法,会创建一个新的子线程并调用
start() 方法用于启动线程,只能调用一次
run() 方法用于执行线程的运行时代码,可以重复调用
-
处理线程的返回值
主线程等待法
使用Thread类的join()阻塞当前线程以等待子线程处理完毕:缺点力度不够细
通过Callable接口实现:通过FutureTask.get(阻塞等待) Or 线程池获取Future.get
-
创建线程池几种方式
1、newSingleThreadExecutor():工作线程数目被限制为 1
2、newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
3、newFixedThreadPooll(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
4、newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度
5、newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
6、newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
7、ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。
-
线程池状态
running:运行中
shutdown:不接收新任务
stop:中断接收的任务
tidying(收拾):所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
terminated(终结):terminated()方法结束后,线程池的状态就会变成这个
-
线程池中 submit() 和 execute() 区别
submit():可以执行 Runnable 和 Callable 类型的任务。
execute():只能执行 Runnable 类型的任务。
-
在 Java 程序中怎么保证多线程的运行安全?
方法一:使用安全类,比如 Java. uti
l. concurrent 下的类。
方法二:使用自动锁 synchronized。
方法三:使用手动锁 Lock。
-
多线程中 synchronized 锁升级的原理(锁的不是代码,所得是对象)
原理:在锁对象有threadid字段,如果为空,jvm让其持有偏向锁,设置threadid为线程id;再次进入先判断线程id和threadid是否一致,一致直接使用此对象,不一致,偏向锁升级为轻量级锁,然后通过自旋循环一定次数获取锁,如果还没有正常获取到取到更多使用的对象,轻量锁升级为重量级锁,这个过程就是synchronized 锁升级
目的:锁升级就是为了减低锁带来的性能消耗。java6后优化了
-
获取锁的分类:获取对象锁和获取类锁(对象锁和类锁互不干涉)
获取对象锁:1、同步代码块(synchronized(this),synchronized(类实例对象),锁是括号内的实例对象);2、同步非静态方法(synchronized method,锁是当前对象的实例对象)
获取类锁:1、同步代码块(synchronized (类.class),锁是括号内的类对象(class对象));2、同步静态方法(synchronized static method,锁是当前对象的类对象(class对象))
-
死锁
当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而引发的阻塞现象
-
防止死锁
使用trylock,设置超时时间,超时退出
使用并发类 Java. util. concurrent代替手写锁
降低锁使用的粒度,尽量不要几个功能用同一把锁
减少同步的代码块
-
ThreadLocal
参考文章:https://www.cnblogs.com/xzwblog/p/7227509.html
1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
3、volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
- yield 和 sleep 的异同
1)yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。
2)yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。
3)yield 不能被中断,而 sleep 则可以接受中断。
如果一定要用它的话,一句话解释就是:yield 方法可以很好的控制多线程,如执行某项复杂的任务时,如果担心占用资源过多,可以在完成某个重要的工作后使用 yield 方法让掉当前 CPU 的调度权,等下次获取到再继续执行,这样不但能完成自己的重要工作,也能给其他线程一些运行的机会,避免一个线程长时间占有 CPU 资源。
-
中断线程
放弃的方法:调用stop方法停止线程;线程池调用suspend和resume方法
目前使用方法:调用interrupt,通知线程应该中断了。
1、如果线程处于被阻塞状态(sleep、wait、join等),那么线程将立即退出被阻塞状态,并抛出InterruptedException异常。
2、如果线程处于正常活动状态,那么会将该线程的中标志设置为true。被设置中断标志的线程将继续正常执行,不受影响。
需要被调用的线程配合中断
1、在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
2、如果线程处于正常活动状态,那么会将该线程的中标志设置为true。被设置中断标志的线程将继续正常执行,不受影响。
-
线程池
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止
Nthreads=Ncpu*Ucpu*(1+w/c),其中Ncpu=CPU核心数,Ucpu=cpu使用率,0~1W/C=等待时间与计算时间的比率
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
如果是IO密集型任务,参考值可以设置为2*NCPU
- 在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
Executors.newCachedThreadPool();
//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
Executors.newSingleThreadExecutor();
//创建容量为1的缓冲池
Executors.newFixedThreadPool(
int
);
//创建固定容量大小的缓冲池
- CountDownLatch与CyclicBarrier区别
CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行。
CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行
- 如何在两个线程间共享数据(多线程:(五)多个线程之间共享数据_慕课手记)
将共享数据和处理逻辑封装在一个类中:1、如果每个线程执行的代码相同,可以使用同一个Runnable对象;2、如果每个线程执行的代码不同,这时候需要用不同的Runnable对象去操作同一个共享数据,并调用类的处理逻辑
- Callable和Runnable有几点不同
(1)Callable规定的方法是call(),而Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3)call()方法可抛出异常,而run()方法是不能抛出异常的。
(4)运行Callable任务可拿到一个Future对象,
- 一个线程运行时发生异常会怎样
https://www.cnblogs.com/charlesblc/p/6171103.html:线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部(Callable可以,另谈),线程就会终结,而对于主线程和其他线程完全不受影响
要区分普通异常,还是RuntimeException:
1、Callable类call()可以抛出,通过在Future实例的get方法获取返回值catch
2、Runable类没有异常抛出:实现Thread.UncaughtExceptionHandler中的uncaughtException在内部cacth
- wait、notify和notifyAll(你真的懂wait、notify和notifyAll吗 - 简书)
notify:只会随机选取一个处于等待池的线程全部进入锁池去竞争获取锁
notifyAll:会让所有处于等待池的线程全部进入锁池去竞争获取锁
永远都要把wait()放到循环语句里面。防止伪唤醒,例如一个生产者唤醒多个消费者
尽量使用notifyAll()的原因就是,notify()非常容易导致死锁。防止一个线程的生成这和消费者互相等待
- 等待池和锁池的区别
线程被wait()进入等待池中,等待池中的线程被notify()或者notifyAll()方法唤醒进入到锁池,尝试获取锁
如果有个线程执行了objectX.wait(),那么该线程就会被暂停(线程的生命周期状态会被调整为Waiting),并且会释放掉objectX锁,然后被存入objectX的等待池之中。此时,该线程就被称为objectX的等待线程。当其他线程执行了objectX.notify()/notifyAll()时,等待池中的一个(或者多个,取决于被调用的是notify还是notifyAll方法)任意(注意是“任意”,而不一定是等待池中等待时间最长或者最短的)等待线程会被唤醒
- 为什么要用线程池
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
- ThreadpoolExecutor和Executors
JDK帮助文档强烈建议使用Executors
工厂方法Executors.newCachedThreadPool()
(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)
(固定大小线程池)Executors.newSingleThreadExecutor()
(单个后台线程)
- Java中synchronized 和 ReentrantLock 有什么不同(https://www.cnblogs.com/cxzdgs/p/5746895.html)
synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。你只要记住,优先使用synchronied,解决不了的再使用ReentrantLock
主要区别如下:
1、ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
2、ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
3、ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。
4、ReentrantLock
还具有可伸缩性的好处,可以中途取消
join或者CountDownLatch
- Java中ConcurrentHashMap的并发度
-
如果你提交任务时,线程池队列已满。会发会生什么
既然提到阻塞队列已满说明不是LinkedBlockingQueue!因为LinkedBlockingQueue是近似无界的!
由于默认是AbortPolicy拒绝策略,因此极可能是抛出RejectedExecutionException异常,如果是其它的策略
1、ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
2、ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4、ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
newFixedThreadPool -->
new
LinkedBlockingQueue<Runnable>()
newSingleThreadExecutor --> new
LinkedBlockingQueue<Runnable>()
newCachedThreadPool -
-> new
SynchronousQueue<Runnable>()
Java线程池中submit() 和 execute()方法有什么区别
1、入参区别:submit可以是runable和callable;execute只能是runable
2、出参区别:submit返回Future<T>;execute没有返回值
3、异常处理:submit通过Future.get捕获异常;execute没有异常抛出
Java多线程中static变量的使用(
https://www.cnblogs.com/panchanggui/p/9680694.html)
- threadlocal的实现原理(ThreadLocal的理解和使用_web项目 threadlocal 各个用户的请求 是不是不同的-CSDN博客)
1、每个Thread 维护一个 ThreadLocalMap
映射表(key - vulue),这个映射表的 key 是 ThreadLocal
实例本身,value 是真正需要存储的 Object。
2、ThreadLocalMap使用ThreadLocal的弱引用作为key,Value是普通对象,强引用
3、OOM:如果一个线程不停的给不同thradLocal属性设置值,并且该线程长期不结束,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,key为null,value值既不能被访问到又无法被回收,会造成OOM
4、protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null
在ThreadLocal的get(),set(),remove()
的时候调用initialValue清除线程ThreadLocalMap里所有key为null的value;但是不包含:
4.1)使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏
4.2)分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在
5、虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
5.1)使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
5.2)JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
- 线程安全主要原因
存在共享数据(也称临界资源)
存在多条线程共同操作这些共享数据
解决方案:
1、同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作
- 在被spring声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制(spring 多线程事务的问题_为什么线程里面不能使用spring托管的类-CSDN博客)
多线程使用
一、线程池的几种创建方式
1、Executors 执行器创建线程
1)、Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2)、Executors.newCachedThreadPool
3)、Executors.newSingleThreadExecutor
4)、Executors.newScheduledThreadPool
5)、Executors.newSingleThreadScheduledExecutor
6)、Executors.newWorkStealingPool
2、ThreadPoolExecutor 线程执行器创建线程(核心线程数,最大线程数,最大线程存活时间,存活时间单位,阻塞队列,线程工厂和拒绝策略可不传)默认策略为 AbortPolicy:拒绝并抛出异常
二、锁升级过程
无锁:初始状态
一个对象被实例化后,如果还没有被任何线程竞争锁,那么它就为无锁状态(01)。
偏向锁:单线程竞争
当线程A第一次竞争到锁时,通过CAS操作修改Mark Word中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。
轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争
如果线程B再去竞争锁,发现偏向线程ID不是自己,那么偏向模式就会立刻不可用。即使两个线程不存在竞争关系(线程A已经释放,线程B再去获取),也会升级为轻量级锁(00)。
重量级锁:同一时刻多线程竞争
一旦轻量级锁CAS修改失败,说明存在多线程同时竞争锁,轻量级锁就不适用了,必须膨胀为重量级锁(10)。此时Mark Word存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程必须进入阻塞状态。
三、i++保证线程安全性
使用cas(AtomicReference)、锁、synchronized保证i++原子性操作,使用AtomicStampedReference通过时间戳解决CAS ABA问题
四、HashMap和ConcurrentHashMap区别
1、线程安全
2、性能:HashMap单线程更好,多线程ConcurrentHashMap使用分段锁技术更好
3、HashMap允许null值和键,ConcurrentHashMap 不允许null 键,也允许null 值