生命不息,学习不止,对一切都要维持敬畏之心。
若有不正之处,请谅解和批评指正,不胜感激。
1.并发,并行与串行
- 并发:俩个或多个事件在同一时间段执行,可以互相干扰,同一时间,只执行一个事件,只不过是通过CPU切换,交替执行.
- 并行:俩个或多个事件在同一时间段互补干扰的同时发生和执行.
- 串行:俩个或多个事件不可能在同一时间发生和执行,前一个任务没有完全搞定,不可能执行下一个任务,下一个任务必须等待.
- 一个CPU同一时间只能执行一个线程*
2.进程与线程
- 进程是操作系统分配系统资源的最小单位,至少存在一个线程
- 线程是CPU调度的最小单位.
- 一个进程中可以有多个线程.
- 区别:
进程有独立的内存空间,进程之间,栈和堆都是独立的.
线程间堆空间是共享的,栈空间是独立的.资源消耗比进程小很多,是比进程更轻量级的调度单位.线程的引入,可以把进程的资源分配和执行调度分开,既可以共享进程的资源(内存地址,IO),又可以独立调度.是JAVA进行处理器资源调度的最基本单位.
3.线程的实现
- HosPot线程的实现:
内核实现线程:每一个JAVA线程都是直接映射到操作系统的原生线程来实现,全权交给操作系统去处理.
- 通用应用程序的线程实现:
内核实现线程:由操作系统内核来完成线程的切换,调度,创建,销毁,并将线程任务映射到CPU上,消耗资源,线程数与内核线程数比为1:1.
用户实现线程:建立在用户空间的线程库上,系统内核线程不能感知用户线程的实现,切换,调度,创建,销毁,完全都在用户空间上实现,不需要内核的帮助,消耗低.更多的线程数量,1:N关系.
混合模式:M个内核实现线程,N个用户实现线程,都有.
4.线程的调度
4.1.什么是线程的调度
操作系统为线程分配CPU使用权的过程.协同式抢占调度,和抢占式调度.JAVA使用的是抢占式调度
4.1.1协同式
线程的执行时间由线程本身控制,线程把自己的工作执行完之后,要主动通知系统切换到另一个线程上去.最大的好处是实现简单, 没有同步的问题.缺点是执行时间不可控,容易发生阻塞.
4.1.2抢占式
每个线程将有系统来分配执行时间,线程的切换不由线程本身决定,是有系统决定.线程可以主动让出执行时间,但是不可主动获取执行时间.
4.1.3优先级
我们可以建议操作系统多分配一些时间给某个线程,就是优先级,但是不能通过优先级判断一组状态都为Ready的线程会将要执行哪一个.
5.线程的状态
- java.lang.Thread.State中描述了六种状态
- 状态的转换
6.守护线程
java分为两种线程:用户线程和守护线程所谓守护线程是指程序运行的时候在后台提供一种通用的服务的线程,比如垃圾回收器就是一个很称职的守护者,并且这种线程不属于程序中不可或缺的部分.因此,当所有非守护线程结束时,程序也就终止了,同时会杀死进程中所有守护线程.反过来说,只有任何非守护线程还在运行,程序就不会终止.
守护线程和用户线程没有本质的区别:唯一不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在,虚拟机会直接退出.但是只要有用户线程运行,虚拟机就不会退出.
public final void setDaemon(boolean on)
on的值为true将线程设置为守护线程,注意一定要在开启线程之前设置
7.创建线程的多种方式
7.1.继承
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
7.2.实现java.lang.Runnable接口
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
7.3.JDK1.5开始提供Callable接口
- 定义类实现Callable接口
- 重写call方法
- 开启线程
- 有返回值
- 可抛异常
public class MyCall implements Callable<Integer> { private int n; public MyCall(int n){ this.n = n; } @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < n; i++) { sum += i; } return sum; } } public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { MyCall myCall = new MyCall(100); FutureTask<Integer> ft = new FutureTask<>(myCall); // 需要注意Thread类并没有接收Callable接口的构造方法 //所以需要使用FutureTask包装一下 再传入到Thread类中 Thread t = new Thread(ft); t.start(); System.out.println(ft.get()); } }
7.4.实现接口与继承创建线程的区别和优势
- 如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
7.5.线程内存原理
8.线程安全
8.1.线程安全定义
摘自--周志明老师的<深入理解Java虚拟机第三版>
当多个线程 同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外 的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象 是线程安全的。
8.2.线程问题的引发
- 程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
8.3.解决线程问题的方法
Java中提供了同步机制(synchronized)来解决
8.3.1.synchronized
- 同步代码块:线程操作的共享数据进行同步。
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。synchronized(对象/类.class(静态)){ 需要同步操作的代码 }
- 同步方法:当一个方法中的所有代码,全部是线程操作的共享数据的时候,可以将整个方法进行同步。使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
public synchronized void method(){ 可能会产生线程安全问题的代码 }
- 对象加锁后的变化
- 写在对象头中
8.3.2. ReentrantLock和synchronized关键字有什么区别
- 这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成
- 由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能,主要有以下3项:
1).等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
2).公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。
3).锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。 - synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。4
9.线程池
- 创建线程每次都要和操作系统进行交互,线程执行完任务后就会销毁,如果频繁的大量去创建线程肯定会造成系统资源开销很大,降低程序的运行效率。线程池思想就很好的解决了频繁创建线程的问题,我们可以预先创建好一些线程,把他们放在一个容器中,需要线程执行任务的时候,从容器中取出线程,任务执行完毕后将线程在放回容器。
- 线程池好处
- 降低资源消耗。通过重复利用已创建的线程降低线程创建、销毁线程造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控
9.1. Executors类
- 创建线程池对象的工厂方法,使用此类可以创建线程池对象。
9.2. ExecutorService接口
- 线程池对象的管理接口,提交线程任务,关闭线程池等功能。