Part1: OS多线程多进程简介
-
多进程多概念:
-
多线程多概念:
一个程序包括多个子任务,可串可并 -
多进程和多线程的区别:
线程共享数据;线程通讯高效 -
Idea多进程/多线程/多模块项目的调试:
Part2:Java多线程的实现
两种实现方法:
- 继承Thread类 extends Thread
- 实现Runnable方法 implements Runnable
在类中实现run方法
public void run(){
//todo: do thread task at here
}
Java四大接口:
- Clonable 用于对象克隆
- Comparable 用于对象比较
- Serializable 用于对象序列化 (*序列化:将数据对象转化成二进制流)
- Runnable 用于对象线程化
线程启动
start方法:底层用JNI实现 (*JNI:java native interface 可以使java程序调用c/c++程序)
Runnable方式:
public class myThread implements Runnable{
public void run(){
//todo: do thread task at here
}
}
public class test{
public static void main(String[] arg){
//必须把实现类包装在线程类里面再进行start
new Thread( new myThread() ).start();
}
}
当前线程名:
Thread.currentThread.getName()
一个线程对象只能启动一次线程,要多次启动线程请使用多个对象:
Error:
Thread t = new Thread(new myThread());
t.start();
t.start();
Accept:
Thread t1 = new Thread(new myThread());
Thread t2 = new Thread(new myThread());
t1.start();
t2.start();
Part 3:多线程信息共享
粗粒度:线程之间没有交流
细粒度:线程之间有信息交流通讯
- 通过共享变量达到信息共享
- c/c++ 有MPI并行库,可以支持线程发送消息(jdk没有)
通过共享变量来共享消息
- static变量: 所有线程共享static变量
- extends Thread类的成员变量: 非共享,一个线程对应一个成员对象
- Runnable类的成员变量: Runnable类只被new了一次,只会有一个成员变量
extends Thread:
public class testThread extends Thread{
private int tickets = 100;
public void run(){
//todo: to do thread task at here
}
}
implements Runnable:
public class testRunnable implements Runnable{
private int tickets = 100;
public void run(){
//todo:to do thread task at here
}
}
启动线程
public void main(String[] args){
//启动testThread时,new了三次该类,所以就会有三个tickets,一个线程对应一个tickets
new testThread().start();
new testThread().start();
new testThread().start();
//启动testRunnable时,只new类一次该类,所以只会有一个tickets被三个线程使用
testRunnable t = new testRunnable();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
volatile关键字
解决工作副本的可见性问题
对于加了volatile关键字的成员变量,它发生修改时,java会同步所有线程内的工作缓存与内存
public class testRunnable implements Runnable{
//boolean flag = true;
volatile boolean flag = true;
public void run(){
while(flag){
System.out.println(Thread.currentThread().getName() + "is existing");
}
System.out.println(Thread.currentThread().getName() + "is quit");
}
public static void main(String[] args) throws InterruptedException{
testRunnable t = new testRunnable();
new Thread(t).start();
Thread.sleep(2000);
t.flag = false;
}
}
synchronized关键字
关键步骤加锁限制
- 互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行
- 同步:多个线程运行,必须按照某一种规定的先后顺序来运行
- 互斥是同步的一种特例
以下代码中的示例两把锁,只要加任意一把都可以实现互斥逻辑:
public class saleTickets implements Runnable{
private volatile int tickets = 100;
//代码块的锁
private String lock = new String("");
public void run(){
while(true){
//通过线程竞争lock信号量来同步代码块
synchronized(lock){ sale(); }
if( tickets <= 0 ) break;
}
}
//函数的锁
public synchronized void sale(){
if( tickets > 0){
--tickets;
System.out.println(Thread.currentThread().getName() + "is saling" + tickets);
}
}
public static void main(String[] args){
saleTickkets t = new saleTickets();
new Thread(t,"Thread-0").start();
new Thread(t,"Thread-1").start();
new Thread(t,"Thread-2").start();
new Thread(t,"Thread-3").start();
}
}
synchronized会加大程序性能负担,请务必理解java的内存模型
Part4:Java多线程管理
线程的生命周期管理
IDE线程查看工具:
线程的同步协作:
- 等待
- 通知/唤醒
- 终止
线程状态:
Thread类API:
已废弃:
- 暂停/恢复:suspend/resume
- 停止/消亡:stop/destroy
仍在使用的api:
例子:生产者消费者模型
经典生产者与消费者问题:
生产者不断的往仓库中存放产品,消费者从仓库中消费产品。
其中生产者和消费者都可以有若干个。
仓库规则:容量有限,库满时不能存放,库空时不能取产品。
Storage中pop方法
// 生产者往仓库中放入产品
public synchronized void push(Product product) {
while (top == products.length) {
try {
System.out.println("producer wait");
wait();//仓库已满,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//把产品放入仓库
products[top++] = product;
System.out.println(Thread.currentThread().getName() + " 生产了产品"
+ product);
System.out.println("producer notifyAll");
notifyAll();//唤醒等待线程
}
Storage中的push方法
// 消费者从仓库中取出产品
public synchronized Product pop() {
while (top == 0) {
try {
System.out.println("consumer wait");
wait();//仓库空,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//从仓库中取产品
--top;
Product p = new Product(products[top].getId(), products[top].getName());
products[top] = null;
System.out.println(Thread.currentThread().getName() + " 消费了产品" + p);
System.out.println("comsumer notifyAll");
notifyAll();//唤醒等待线程
return p;
}
上述代码的缺点:
线程被动地暂停和终止
- 依靠别的线程来拯救自己
- 没有及时释放资源(如果这种线程拿到了锁,很容易造成死锁)
改进方法:
线程主动暂停和终止
- 定期监测共享变量(信号量),而不是等待别的线程来notify
- 如需暂停或终止,先释放资源,再做动作
线程被动暂停和线程主动暂停
被动终止:等待其他线程interrupt自己,本线程在sleep时被打断,会抛出sleep interrupt异常
class TestThread1 extends Thread {
public void run() {
// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
while (!interrupted()) {
System.out.println("test thread1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("test thread1 is exiting");
}
}
主动终止:监控共享信号量flag,能做到安全的退出线程
class TestThread2 extends Thread {
public volatile boolean flag = true;
public void run() {
// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
while (flag) {
System.out.println("test thread2 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("test thread2 is exiting");
}
}
多线程死锁
- 每个线程互相持有别人需要的锁
- 预防死锁:对资源进行等级排序
守护线程
线程对象.setDaemon(true)
守护线程与系统main线程同生共死,这个线程具有最低的优先级,用于为系统中的其它对象和线程提供服务。
当java虚拟机中没有非守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,守护线程不管运行到哪里,虚拟机都会退出运行。
例如:垃圾回收线程
注意⚠️:守护线程永远不要访问资源,如文件或数据库