Java多线程
一、进程和线程
1. 进程和线程的由来
(1)串行:初期的计算机智能串行执行任务,并且需要长时间等待用户输入;
(2)批处理:预先将用户的指令
(3)进程:进程独占内存空间,保存各自运行状态,相互间不干扰且可以相互切换,为并发处理任务提供了可能。
a. 孤儿进程:父进程先于子进程终止;
b. 僵尸进程:子进程先于父进程终止,而父进程又没有调用wait或waitpid函数,此时子进程始终占有着资源,同时也减少了系统可以创建的最大进程数。
(4)线程:共享线程的内存资源,相互切换更快速,支持更细粒度的任务控制,使进程内的子任务得以并发执行。
2. fork之后,子进程自父进程继承了什么,又有哪些独有?
(1)继承
a. 进程的资格
b. 环境
c. 堆栈
d. 内存
e. 文件资源描述符
f. close-on-exec标志
g. 控制设定
h. Nice值:表示函数的优先级
i. ……
(2)独有
a. 进程号
b. 计时器
(3)通过exec函数簇可以让子进程不运行父进程正在执行的程序
3. 进程和线程的区别
进程是资源分配的最小单位,线程是CPU调度的最小单位
(1)所有与进程相关的资源,都被记录在PCB(进程控制块)中;
(2)进程是抢占处理机的调度单位;线程属于某个进程,共享进程资源
(3)线程只由堆栈寄存器、程序计数器和TCB组成
(4)总结
a. 线程不能被看做独立应用,而进程可以被看做独立应用;
b. 进程有独立的地址空间但互相不影响;线程是进程的不同执行路径,进程中某一个线程出了问题,该进程也会出现问题;
c. 线程没有独立的地址空间,多进程的程序比多线程程序的鲁棒性要高;
d. 进程的切换比线程的切换开销大
4. Java进程和线程的关系
(1)java对操作系统提供的功能进行封装,包括进程和线程
(2)运行一个程序还会产生一个进程,进程包含至少一个线程
(3)每个进程对应一个JVM实例,多个线程共享JVM里的堆
(4)主线程可以创建子线程,原则上要后与子线程完成执行
5. 进程间通信方法
进程间通信(IPC,InterProcessCommunication)是指在不同进程之间传播或交换信息。
(1)管道
a. 无名管道
b. 命名管道:FIFO
(2)消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
(3)信号量:信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
(4)共享内存:共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
(5)Socket:远程进程通信,内核态和用户态间通信;
(6)流:同上
6. 线程间通信方式
(1)传统线程通信synchronized + wait + notify;
(2)使用Condition控制线程通信lock + condition + await + signal;
(3)使用阻塞队列(BlockingQueue)控制线程通信
二、Thread中的start和run方法的区别
1. Start()方法
2. Run()方法:在Runable接口中定义,是线程执行的入口;
3. start()方法和run()的区别
(1)调用start()方法会创建一个新的子线程并启动;
(2)run()方法只是Thread的一个普通方法的调用,还是在主线程里面执行,这并不属于多线程;
三、Thread类和Runnable接口,线程安全
1. 区别
(1)Thread是实现了Runnable接口的类,使得run支持多线程;
(2)因为类的单一继承原则,推荐使用Runnable接口
2. 线程安全
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行 这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的 值也和预期的是一样的,就是线程安全的。
四、如何给run()方法传参
1. 实现方式
(1)构造函数传参
(2)成员变量传参:set()&get();
(3)回调函数传参:process();
2. 实现处理线程的返回值
(1)主线程等待法:无法进行精确控制
(2)使用Thread类的join()方法阻塞当前线程以等待子线程处理完毕
(3)通过Callable接口实现:通过FutureTask或者线程池获取。线程池不需要start()方法进行启动,但是需要关闭线程池
五、线程和进程的状态
1. 线程六个状态
(1)New:创建后尚未启动的线程的状态
(2)Runnable:包含Running和Ready
(3)Waiting:不会被分配CPU执行时时间,需要显式被唤醒
没有设置Timeout参数的Object.wait()方法;没有设置Timeout参数的Thread.join()方法;LockSupport.park()方法
(4)Timed Waiting:在一定时间后会由系统自动唤醒
Thread.sleep()方法;设置Timeout参数的Object.wait()方法;设置Timeout参数的Thread.join()方法;LockSupport.parkNanos()方法;LockSupport.parkUntil()方法
(5)Blocked:等待获取排它锁
(6)Terminated:已终止线程的状态,线程已经结束执行。当主线程main方法或者run方法完成时,就认为线程终止了,即使对象是活的,也不是一个单独执行的线程了。在一个终止状态的线程上调用start方法会有java.lang.IllegalThreadStateException异常。就是一个线程对象不能连着被start多次。
2. 进程三个状态
(1)运行态:进程占有处理器正在运行
(2)就绪态:进程具备运行条件,等待系统分配处理器以便运行。
(3)等待态:又称为阻塞(blocked)态或睡眠(sleep)态,指进程不具备运行条件,正在等待某个事件的完成。
六、sleep方法和wait方法
1. 基本差别
(1)sleep是Thread类的方法,wait是Object类中定义的方法;
(2)sleep()方法可以在任何地方使用
(3)wait()方法只能在synchronized方法或者synchronized块中使用
2. 最主要的本质区别
(1)Thread.sleep()只会让出CPU,不会导致锁行为的改变
(2)Object.wait()不仅让出CPU,还会释放已经占有的同步资源锁
七、notify()和notifyAll()
1. 两个概念
(1)锁池EntryList:
(2)等待池WaitSet:
2. 锁池
假设线程A已经拥有了某个对象的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
3. 等待池
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就会进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
4. 保证线程的顺序执行
可以用线程类的 join()方法 在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。
5. notify()和notifyAll()的区别
(1)notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会;
(2)notify()只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。
(3)在j