一.线程与进程
(一)线程发展史
早期的大型计算机一次只能运行一个程序,后来当操作系统出现之后,就可以处理多个程序了,运行在操作系统上的每个应用程序,都会占用一个个独立的进程。
比方说有四个指令:
A:------->阻塞(IO操作耗时太长)
B
C
D
当A被阻塞的时候,会造成CPU的严重浪费,此时解决方案是,内存分为不同的区域,不同的区域存放不同的程序,当一个进程被阻塞的时候,可以让cup切换到其他的进程去执行(进程之间相互独立),这是因为单核的cup只能通过时间片的切换来实现进程之间的切换。
随着时间的发展,对于程序的实时性要求提高,而进程由于太重,所以花费的时间久,进程之间的通信也比较麻烦,所以出现了线程。线程是轻量级的进程,当线程出现之后,cup的调度单位就变成了线程,四核的cpu可以同时运行4个线程。
(二)线程与进程的概念和区别
进程:运行在操作系统上的每个应用程序,都会占用一个个独立的进程,而进程内又允许运行多个线程,这意味者一个程序可以同时执行多个任务的功能。进程是指系统当中正在运行的应用程序,它拥有自己独立的内存空间。一个进行当中允许同时启动多个线程,分别执行不同的任务,比如:运行腾讯课堂这一进程,它可以同时开启很多不同的线程,比如录视频线程,在线播放线程,在线弹幕线程等
多线程帮助我们最大的利用CPU。
线程:线程是进程当中的一个执行流程,他是轻量级进程。
进程与线程的区别:运行在操作系统上的每个应用程序,都会占用一个个独立的进程,每个进程都需要操作系统为其分配独立的内存空间,而同一进程当中的线程共享所有的线程内存块,这些线程之间可以共享数据,因此线程之间的通信比较简单,消耗的系统开销也相对较小。
(三)多线程的好处
多线程的好处:
可以同时并发的执行多个任务,当程序的某个功能正在等带某个资源的时候,此时又不愿因为等待某个资源而造成程序暂停,那么就可以创建另外的线程进行其他的工作。比方下MV的时候听歌。
多线程可以最大限度的降低CPU的鲲翔时间,从而提高cpu的利用率。
二.线程的创建
(一)主线程
任何一个java程序启动时,一个线程立即运行,它执行main方法,这个线程称为程序的主线程。
也就是说任何java程序都至少又一个线程,即:主线程
主线程的特殊之处在于:
它是产生其他线程子线程的线程,即在子线程当中产生其他子线程。
通常它必须要最后结束,因为它要执行其他子线程的关闭工作。
(二)创建线程
1.继承Thread //注意Thread类实现了runnable接口
2.实现runnable
3.通过Callable接口:通过FutureTask/线程池
实现 Runnable 接口比继承 Thread 类所具有的优势:
1):适合多个相同的程序代 码的线程去处理同一个资源
2):可以避免 java 中的单继承的限制
3):增加程序的健 壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现 Runabl e 或 callable 类线程,不能直接放入继承 Thread 的类
5)runnable 实现线程可以对线 程进行复用,因为 runnable 是轻量级的对象,重复 new 不会耗费太大资源,而 Threa d 则不然,它是重量级对象,而且线程执行完就完了,无法再次利用
三.线程的状态
1.线程的5大状态
1.新建(New):创建后尚未start的线程状态
2.就绪(Runnable):创建线程,并调用start方法
3.运行(Running):获得CPU时间片以后执行run方法
4.阻塞(blocked):线程因为某种原因放弃 CPU 使用权,暂时停止 运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行 wait()方法,JVM 会把该线程放入等待池中(wait 会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用, 则 JVM 会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处 理完毕时,线程重新转入就绪状态。(注意,sleep 是不会释放持有的锁)
5.死亡(dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
2.阻塞状态
1.等待阻塞(无期限等待wait)
在线程调用wait方法后就会进入等待阻塞状态,等待阻塞分为无期限等待和期限等待。
无限线等待(没有设置Timeout的wait方法):
因为执行了wait的线程会释放CPU和锁,然后进入等待池,直到通过notify/notifyAll唤醒之后(进入锁池)才会重新拥有锁和cpu。
有限等待(设置了Timeout的wait方法):
在一定时间后会被系统自动唤醒。
2.同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用, 则 JVM 会把该线程放入锁池中。
3.其他阻塞
Thread.sleep(),不会释放锁,只会让出CPU,CUP就可以去执行其他的任务了,调用sleep不会影响锁的相关情况。
3.sleep和wait的区别
wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用(使用范围),因为要先获取锁才可以释放锁。当wait方法不传任何的参数的时候,该线程会陷入无限的等待当中,即在释放锁和CPU以后,一直无法在获取锁和CPU,必须要使用Object的notify()方法去唤醒该线程,当唤醒之后,wait线程就会获得锁和CPU,然后继续执行。
Thread.sleep(),不会释放锁,只会让出CPU,CUP就可以去执行其他的任务了,调用sleep不会影响锁的相关情况,
调用wait之后,当前线程不仅仅会释放锁,而且会让出CPU,以及其他正在等待该资源的CPU.
4.notify和notifyAll
5.yield和join
Yield对锁是没有影响的。
yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。 如果没有的话,那么 yield()方法将不会起作用,并且由可执行状态后马上又被执行。
join 方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执 行结束后,再继续执行当前线程。如:t.join();//主要用于等待 t 线程运行结束,若无此句, main 则会执行完毕,导致结果不可预测。
6.interrupt中断线程
sleep 的过程中过程中有可能被其他对象 调用它的 interrupt(),产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程 就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继 续执行 catch 语句块(可能还有 finally 语句块)以及以后的代码。
注意 sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep()让 t 对象进入 sleep,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程。
7.start和run的区别
四.一般线程和守护线程的区别
守护线程就是在程序运行的时候在后台提供的一种通用服务线程,比如java的垃圾回收线程就是一种很称职的线程,它并不是程序当中不可获取的一部分,当当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来 说,只要任何非守护线程还在运行,程序就不会终止。
(1) thread.setDaemon(true)必须在 thread.start()之前设置,否则会跑出一个 IllegalThreadStateException 异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在 Daemon 线程中产生的新线程也是 Daemon 的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一 个操作的中间发生中断。
五.中断线程
中断线程有很多方法:
(1)使用退出标志,使线程正常退出,也就是当 run 方法完成 后线程终止。
(2)通过 return 退出 run 方法
(3)通过对有些状态中断抛异常退出 thread.interrupt() 中断。
(4)使用 stop 方法强行终止线程(过期)
中断线程可能出现的问题:
使用:Thread.interrupt()并不能使得线程被中断,线程还是会执行。最靠谱的方法就是 设置一个全局的标记位,然后再Thread中去检查这个标记位,发现标记位改变则中断线程。
六.多线程如何避免死锁
1.何为死锁
进程因为相互等待对方持有的资源而导致多个进程无法向下执行,造成了死锁。
死锁的四个必要条件:
1.互斥条件:在某个时间内某个资源为某个进程锁独占,其他进程想要访问该资源只能进行等待。
2.不剥夺条件:进程所持有的资源不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
3.请求和保持资源:进程至少要持有一个资源,同时又提出要访问其他的资源,请求被阻塞,自己持有的资源不释放。
4.循环等待条件:存在一种进程资源的循环等待链,链当中的每一个进程持有资源的同时被其他的进程锁等待。
2.避免死锁
转载:https://blog.csdn.net/ls5718/article/details/51896159
在有些情况下死锁是可以避免的。三种用于避免死锁的技术:
1.加锁顺序(线程按照一定的顺序加锁)
2.加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
3.死锁检测