进程:资源分配的最小单位。
线程:CPU调度的最小单位
进程和线程的关系
进程类比项目组,每一个项目组都是由产品、前端、后端、测试组成的,每组各代表一条线程,每条线程共享项目组的产品资源,也就是每组都需要知道产品的需求,进程开启就是项目组的项目启动,每组开始并行干活,每组也都依赖于项目组存在,各个项目组之间出现问题不受影响,如果项目组下的前端组出现问题直接就会影响该项目组的运行进度。
进程和线程的区别
1.地址空间和资源:同一进程的所有线程共享本进程的地址空间和资源,而不同的进程之间的地址空间和资源是独立的。
2.执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
3.健壮性: 因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。 但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。
线程的六种生命周期状态
新建(New):当使用new操作符创建新线程时,线程处于新建状态
运行(Runnable):调用start()方法线程进入运行状态
阻塞(Blocked):当线程需要获得对象的内置锁,就是使用synchronized修饰,而该锁正在被其他线程拥有,被synchronized修饰的方法或者代码块同一时刻只能有一个线程执行,而其他竞争锁的线程就从Runnable切换到Blocked状态,当某个线程竞争到锁了它就变成了Runnable状态。注意并发包中的Lock,是会让线程属于等待状态而不是阻塞,只有synchronized是阻塞。
等待(waiting):当线程等待其他线程通知调度表可以运行时
①调用无参的Object.wait()方法,等到notifyAll()或者notify()唤醒就会回到Runnable状态。
②调用无参的Thread.join()方法,比如在主线程里面创建线程A,调用A.join(),主线程会等A执行完了才会继续执行,这时你的主线程状态就是等待状态。
③调用LockSupport.park()方法,再调用LockSupport.unpark()方法就会回到Runnable状态
计时等待(Time_Waiting):其实这个状态和Waiting就是有没有超时时间的差别,对于一些含有时间参数的方法,如Object.wait(long timeout)。Thread.join(long millis)。Thread.sleep(long millis)。LockSupport.parkNanos(Object blocked,long deadline)。LockSupport.parkUntil(long deadline)。
终止(Terminated):当run()方法运行完毕或出现异常时就是终止状态,注意有个方法Thread.stop()是让线程终止的,但是这个方法已经被废弃了,不推荐使用,因为比如你这个线程得到了锁,stop之后这个锁也随着没了,其他线程也拿不到这个锁了,所以推荐使用interrupt()方法。interrupt()会使得线程Waiting和Timed_Waiting状态的线程抛出interruptedException异常,使得Runnable状态的线程如果是在I/O操作会抛出其他异常。
查看线程运行状态:Thread类的getState()方法
Thread中的start和run方法的区别
调用start()方法会创建一个新的子线程并启动
run()方法只是Thread的一个普通方法的调用,并不会创建新的线程
Thread和Runnable是什么关系
Thread是实现Runnable接口的类,使得run方法支持多线程
因类的单一继承原则,推荐多使用Runnable接口。这样能够赋予子类较高的拓展性,便于后续将普通类升级成多线程
如何实现处理线程的返回值
主线程等待法:在主线程中使用while循环直到返回值不为空,这样做的缺点也很明显,我们需要手动去编写循环的逻辑,并且我们无法知道线程执行完成的具体时间,设置的sleep时长不精确,当成员变量多的时候程序会变得异常臃肿无法维护。
使用Thread类的join()方法阻塞当前线程以等待子线程处理完毕,这样做的缺点是粒度不够细,无法精确控制成员之间的依赖关系。
通过Callable接口实现
1.FutureTask
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
Thread.sleep(5000);
return value;
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
if(!futureTask.isDone()) {
System.out.println("线程正在执行...");
}
System.out.println("返回值:" + futureTask.get());
2.线程池
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); Future<String> future = newCachedThreadPool.submit(new MyCallable()); if(!future.isDone()) {
System.out.println("线程正在执行...");
} try {
System.out.println("返回值: " + future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
newCachedThreadPool.shutdown();
}
sleep和wait的区别
sleep是Thread类的方法,wait是Object类的方法
sleep方法可以在任何地方使用
wait方法只能在synchronized方法或块中使用
Thread.sleep()只会让出CPU,不会导致锁行为的改变
Object.wait()不仅会让出CPU,还会释放已经占有的同步资源锁
notify和notifyAll的区别
notifyAll会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会
notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
锁池:假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个synchronized方法或者块,由于B、C线程在进入对象的synchronized方法或者块之前必须先获得该对象锁,而恰巧该对象的锁目前正在被A线程占用,此时B、C线程就会被阻塞,进入到一个地方去等待锁的释放,这个地方便是该对象的锁池。
等待池:假设线程A调用了某个对象的wait方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
yieId是什么
当调用Thread.yieId()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示
如何中断线程
已经弃用的方法:通过stop()方法停止线程
调用interrupt(),通知线程应该中断了
1. 如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常
2. 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响
调用interrupt()不会立即中断线程,需要被调用的线程配合中断。在正常运行任务时,经常检查本线程的中断标志位,如果设置了中断标志就自行停止线程。
线程死锁的现象
两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在 无外力作用的情况下,这些线程会一直互相等待而无法继续运行下去
线程死锁的四个条件
互斥条件
资源只能被一个线程占用,如果其他线程请求获取该资源,则请求者只能等待,直到占用资源的线程释放该资源
请求并持有条件
指一个线程已经持有了至少一个资源,但又提出新的资源请求,而新的资源已被其他线程占用,所以当前线程会被阻塞,但阻塞的同时不释放自己获取的资源
不可剥夺条件
获取到的资源在自己使用完之前不能被其他线程抢占,只能在使用完之后释放
环路等待条件
发生死锁的时候必然存在一个线程-资源的环形链,即线程集合{T0,T1,T2...Tn}中的T0正在等待一个T1占用的资源,T1正在等待T2占用的资源...Tn正在等待T1占用的资源
上述请求并持有条件和环路等待条件是可以通过资源申请的有序性破坏的
最后附上线程状态转换图: