线程状态
线程可以划分为三种状态:运行状态(表示正在运行);可运行状态,也是就绪状态(表示cpu资源足够即可立即执行,与其他线程竞争cpu资源);阻塞状态(没有获取到锁,即没有cpu资源;或者没有被唤醒);
阻塞队列,就绪队列,cpu同一时间只能运行一个线程
线程5个阶段:创建,就绪,运行,阻塞,终止
start方法并不是立即执行线程代码,而是使线程进入可运行状态,具体什么时候运行是有CPU决定。
多线程代码执行顺序是不确定的,每次执行结果都随机
start()方法不能重复调用,若重复调用,会出现IllegalThreadStateException
继承Thread或实现Runnable接口会使类具有多线程特征,所有多线程的代码都在run()方法里面
Thread类也是实现了Runnable接口
不管以何种方式来实现多线程,最终还是通过Thread类的API来控制线程
Runnable接口比Thread类优势:
避免单继承限制;代码可以被多个线程共享,代码和数据独立;可以放入线程池中进行调用;适合多个相同的程序代码去处理同一资源。(比如微信公众号生成图片,多个地方会使用到)
在java中,每次程序运行至少启动两个线程,一个是main线程,一个是垃圾回收线程。
线程状态转化
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程调度
1、线程拥有优先级(1-10):优先级高的线程会获得较多的机会运行,可以通过setPriority来进行设置,默认取值为5。优先级有继承关系,如果A线程中创建了B线程,那么B将和A具有相同的优先级。优先级在不同的操作系统中不好映射,建议使用默认常量作为优先级,这样保证同样的优先级采用了同样的调度方式。
2、线程睡眠:通过调用sleep使线程进入阻塞状态,可设定睡眠时间,当睡眠结束后,进入就绪状态
3、线程等待:wait()方法,导致当前线程等待,直到其他线程调用此对象的notify()或notifyAll()进行唤醒操作
4、线程让步:yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程
5、线程加入;join()方法。在当前线程中调用另一个线程的join()方法,则当前线程进入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态
6、线程唤醒:notify(),唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择其中一个线程。选择是任意性的
什么情况下使用join()?
如果子线程需要进行大量的耗时运算,主线程将于子线程之前结束。如果主线程需要用到子线程的结果,这个时候就需要join
wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
interrupt();并不是中断线程,只是向线程发送中断信号,改变线程状态为中断(不能中断运行中的线程,只是改变状态)
操作系统调度基本单位:进程【系统为进程分配资源,不对线程分配资源】
操作系统调度最小单位:线程
cpu调度基本单位:线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
wait、sleep、yield区别
都是用来暂停线程方法的,其中sleep、yield是定义在Thread类中,而wait 方法是定义在Object类中
wait是用于线程间通信,sleep是用于短时间暂停当前线程。当线程调用wait方法时,会释放它所持有的对象的资源和锁,而sleep不会释放(即还持有锁)。
wait()应该在同步代码块中调用
yield方法,使当前线程让出cpu占用权,不可能使较低较低优先权的线程获得cpu占有权,而sleep可以
yield仅仅释放线程所占有的cpu资源,进入执行队列,并不是等待状态,具体谁能获取到cpu资源完全取决于调度器。让当前线程进入可运行状态,以允许具有相同优先级的其他线程获得运行机会。有可能进入可执行状态后又马上被执行。
yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行
Thread.sleep()是一个静态方法,作用在当前线程上;wait()是一个实例方法,并且只能在其他线程调用本实例的notify()方法时被唤醒。使用sleep()暂停的线程会在被唤醒之后立即进入就绪状态;使用wait()方法时,被暂停的线程唤醒后,会先获取锁(阻塞状态),然后再进入就绪状态。
Thread.yield(),作用在当前线程
线程安全
多个线程同时运行一段代码,要保证和单线程运行的结果一致,就是线程安全的。
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
private int sum = 100;
public void run(){
while(true){
if(sum>0){
try {
Thread.sleep(10);
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
}
如上代码,多个线程执行同一个任务,操作同一属性sum,对其进行自减并打印。运行结果出现了很多重复,且有-1,与预期结果不一致。为什么呢?主要是因为三个线程在执行过程中不断抢夺cpu执行权,当某一个线程通过Thread.sleep()进入睡眠状态时,cpu的执行权交由了两外两个线程,三个线程都运行到这里,面临的是输出sum,并执行sum--
当我们使用多线程访问同一资源的时候,且多个线程对资源有写的操作,就容易出现线程安全问题。
所以java提供了同步机制去解决
同步代码块
synchronized(同步锁){
//需要同步操作的代码
}
无论是否失去cpu的执行权,其他线程只能处于等待状态。
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable{
private int sum = 100;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if (sum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable {
private int sum = 100;
Object obj = new Object();
public void run() {
while (true) {
show();
}
}
public synchronized void show() {
if (sum > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum);
sum--;
}
}
}
Lock锁
public class Test {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
Thread t3 = new Thread(r);
t1.start();
t2.start();
t3.start();
}
}
class MyRunnable implements Runnable {
private int sum = 100;
Lock l = new ReentrantLock();
public void run() {
while (true) {
l.lock();
if (sum > 0) {
try {
Thread.sleep(10);
System.out.println(sum);
sum--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
}
计时等待状态
sleep使当前线程进行休眠
锁阻塞状态
比如多个线程执行前需获取锁,在获取锁的等待过程就属于锁阻塞状态
无限等待状态
调用wait()方法会进入无限等待状态,直到调用notify()方法,释放锁对象
等待唤醒机制
多线程之间的协作机制。比如A线程在触发条件后,调用wait()方法进入等待状态;其他线程执行完指定代码后在调用notify()方法将其唤醒;如果有多个线程在进行等待时,如需要,可以调用notifyAll()来唤醒所有等待的线程。
- wait():线程不再参与调度,不去竞争锁,进入无限等待,直到被唤醒,进入调度队列
- notify():把一个等待中的线程释放出来
- notify():释放所有等待的线程
注意:
wait和notify都属于Object方法,所以锁对象可以是任意对象;必需在通过代码块中使用;必需用对象来调用方法,且是同一个对象;
线程类的一些常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
Synchronized关键字的作用域
修饰方法(实例对象),修饰静态方法(类对象),方法中的某块(当前对象)
被Synchronized修饰的方法是不能被继承的
线程同步与异步
线程同步:多个线程同时访问同一资源,为保证线程安全,进行加锁处理。只有获取到锁的线程才能进行操作(使用同一个锁),例如同步代码块,必需要等到执行结束(获取到结果),才能进行下一步操作
单线程是不需要同步的
线程异步:调用者在没有得到结果前,就可以继续执行后续的操作。调用完成后,一般通过状态、通知或回调来通知调用者。调用的返回并不受调用者控制。
线程数据传递
1、声明变量,通过构造方法传递
2、set方法传递
3、通过回调函数
锁
互斥锁,读写锁,乐观锁,悲观锁,公平锁,非公平锁,自旋锁
什么时候需要锁呢?
共享实例变量,共享连接资源
Lock
lock()获取锁,拿不到将一直阻塞,无视interrupt()方法,unlock()释放锁,tryLock()尝试获取锁,如果取得返回true,tryLock(time,单位)单位时间内获取锁
newCondition()条件状态
lockInterruptibly()获取锁,会响应interrupt
ReentrantLock可重入锁
构造函数,默认非公平锁。内部有一个Sync变量,其所有方法基本都是调用Sync方法,比如lock方法,调用的是sync.lock()
public ReentrantLock() {
sync = new NonfairSync(); //默认非公平锁
}
public ReentrantLock(boolean fair) {
sync = (fair)? new FairSync() : new NonfairSync();//公平锁
}
公平锁就是先等待的线程先获取锁;非公平锁是看操作系统的调度,有不确定性,所以性能好;
Sync,是ReentrantLock的内部类
ThreadFactory
线程工厂,用来创建线程,只有一个方法
public interface ThreadFactory {
Thread newThread(Runnable r);
}
实现ThreadFactory只有一个地方,Executors中的静态内部类DefaultThreadFactory,统一给线程池中的线程设置线程组,线程前缀名,优先级
ThreadLocal
在高并发场景下,如果只考虑线程安全,而不考虑延迟性,数据共享,那么就使用ThreadLocal
使用ThreadLoca维护变量时,会为每个使用该变量的线程提供独立的副本,这样线程之间互不影响
每个Thread中都有一个ThreadLocal.ThreadLocalMap。其中Key为ThreadLocal这个实例,value为每次initialValue()得到的变量
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份拷贝
ThreadLocal 不是用于解决共享变量的问题的,不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制,理解这点对正确使用ThreadLocal至关重要。
ThreadGroup
线程组,一旦一个线程归属到一个线程组中,就不可更改其线程组。线程组可以方便统一管理
Callable
public interface Callable<V> {
V call() throws Exception;
}
FutureTask是Future的一个实现,并实现了Runnable,所以可以通过Executors来执行,也可以传递给Thread。如果在主线程中需要执行耗时操作,又不想阻塞主线程,可以把作业交给Future在后台完成操作。当主线程将来需要时,就可以通过Future对象获得后台作业的结果。Executors框架利用FutureTask来完成异步任务。(如果有返回结果,一定要注入一个Callable对象)
completionService
ExecutorService executorService = Executors.newCachedThreadPool();
List<FutureTask<Integer>> result = new ArrayList<>();
for(int i=0; i<10; i++) {
result.add((FutureTask<Integer>) executorService.submit(new MyFuture(i)));
}
executorService.shutdown();
for(int i=0; i<10; i++) {
System.out.println(result.get(i).get());
}
任务放入线程池中执行,需要获取返回结果,只能通过一个线程一个线程执行完,按顺序获取;
ExecutorService executorService2 = Executors.newCachedThreadPool();
// 构建完成任务
CompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService2);
for(int i=0; i<10; i++) {
completionService.submit(new MyFuture(i));
}
executorService.shutdown();
for(int i=0; i<10; i++) {
System.out.println(completionService.take().get());
}
对于CompletionService,构造参数是线程池
假设我们开了100个线程,放入到线程池中进行执行,而又需要返回结果。线程执行时间未知,我们只能通过不同遍历线程池中的线程来获取结果。有的线程执行完,而我们不知道。针对此问题,CompletionService诞生了。
它是将一组线程的执行结果放入到一个BlockingQueueing中,其顺序只和线程执行时间有关,与启动速度无关。
线程池
newCachedThreadPool();缓存线程池;有线程就复用,没有就新建线程并加入池中;通常用于执行时间短的异步任务;如果在一定时间内(默认60秒)没有任务执行,将会进行销毁;(线程可以被循环复用)
newFixedThreadPool();固定数量线程池;只有固定数量的活动线程存在;如果有新的线程要执行,将任务放入队列中进行等待,直到某个线程任务完毕进行调用;线程数量固定,不会被销毁;
SingleThreadPool();单线程池;只有一个线程;且不会被销毁;
ScheduledThreadPool();调度线程池
shutdown();任务都执行完才关闭线程池;
shutdownNow();舍弃任务,直接关闭;
原文:
https://www.jianshu.com/p/b8197dd2934c
https://blog.csdn.net/chenruijia170707/article/details/78505351