多线程
文章目录
进程?线程?
- 进程是一个应用程序(1个进程是一个软件)
- 资源管理的最小单元
- 独立的内存空间
- 包含一个或者多个线程
- 线程是一个进程的执行场景/执行单元
- 程序执行的最小单元
- 拥有独立的栈空间
一个进程可以启动多个线程。
Eg
java程序中至少有两个线程并发
- 垃圾回收线程
- 执行main方法的主线程
进程和线程是什么关系
进程A和进程B的内存独立不共享
线程A和线程B是什么关系
在java语言中:
线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个进程一个栈。
Eg
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
java中之所以用多线程机制,目的就是为了提高处理效率。
Think
- 使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
2.对于单核CPU来说,真的能做到真正的的多线程并发吗?
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!
线程对象的生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
继承java.lang.Thread
编写一个类,直接继承 java,lang.Thread, 重写run方法。
- 怎么创建线程对象? new继承线程的类。
- 怎么启动线程呢? 调用线程对象的start()方法。
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread myThread = new MyThread();
// 启动线程。
myThread.start();
注意
- myThread.run() 不会启动线程,只是普通的调用方法而已。不会分配新的栈。(这种方式还是单线程)
- myThread.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个全新的栈空间
- 这段代码的任务只是为了开启一个新的栈空间,子要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
- 启动成功的线程回自动调用run方法,并且run方法在分支栈的栈底。
- run方法在分支栈的栈底
- main方法在主栈的栈底
- run和main是平级的
实现 java.lang.Runnable
编写一个类,实现java.lang.Runnable接口,实现run方法。
- 怎么创建线程对象? new线程类传入可运行的类/接口。
- 怎么启动线程呢? 调用线程对象的
start()
方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
线程类 Thread
获取当前线程对象、获取线程对象名字、修改线程对象名字
方法名 | 作用 |
---|---|
static Thread currentThread() | 获取当前线程对象 |
String getName() | 获取线程对象名字 |
void setName(String name) | 修改线程对象名字 |
线程的sleep方法
方法名 | 作用 |
---|---|
static void sleep(long millis) | 让当前线程休眠millis秒 |
- 静态方法:Thread.sleep(1000);
- 参数是毫秒
- 作用:让当前线程进入休眠,进入“阻塞状态”, 放弃占用CPU时间片,让给其他线程使用。
方法名 | 作用 |
---|---|
void interrupt() | 终止线程的睡眠 |
为什么run()方法只能try…catch…不能throws?
因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
强制终止一个线程执行
不推荐使用
- 容易丢失数据;
- 这种方式直接将线程杀死了,线程没有保存的数据将会丢失。
合理结束一个进程的执行
常用
public class ThreadTest10 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.start();
// 模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止线程
// 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
r.run = false;
}
}
class MyRunable4 implements Runnable {
// 打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(run){
...
}else{
...
}
}
}
}
java进程的优先级
- 最低优先级1
- 默认优先级是5
- 最高优先级10
常量
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法
方法名 | 作用 |
---|---|
int getPriority() | 获取线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
线程安全问题
满足三个条件:
- 多线程并发
- 有共享数据
- 共享数据有修改行为
满足以上三个条件之后,就会存在线程安全问题。
解决线程安全问题
线程排队执行。(不能并发)用排队执行解决线程安全问题。
这种机制称为:线程同步机制
异步编程模型
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。
同步就是排队。
线程安全
synchronized-线程同步
语法
synchronized(){
// 线程同步代码块。
}
synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享
的数据。才能达到多线程排队。
Eg
假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。
你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
- 这里的共享对象是:账户对象。
- 账户对象是共享的,那么this就是账户对象!!!
- ()不一定是this,这里只要是多线程共享的那个对象就行。
Java中三大变量
-
实例变量:在堆中。
-
静态变量:在方法区中
-
局部变量:在栈中
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量
局部变量永远不会存在线程安全问题。
- 局部变量不共享。(一个线程一个栈)
- 局部变量在栈中。所以局部变量永远都不会共享。
死锁
是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。
产生的原因
资源竞争
- 系统中的资源可以分为两类
- 可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
- 另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
- 产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
- 产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
进程间推荐顺序非法
- 若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
- 例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
产生的4个必要条件
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
解决死锁的基本方法
预防死锁:
- 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
- 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
- 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
- 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
避免死锁:
- 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
- :首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
- 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
Object类
方法名 | 作用 |
---|---|
void wait() | 让活动在当前对象的线程无限等待(释放之前占有的锁) |
void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |
wait()
Object o = new Object();
o.wait();
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
notify()
Object o = new Object();
o.notify();
唤醒正在o对象上等待的线程。
notifyAll()
Object o = new Object();
o.notifyAll();
的线程无限等待(释放之前占有的锁) |
| void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
| void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |
wait()
Object o = new Object();
o.wait();
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
notify()
Object o = new Object();
o.notify();
唤醒正在o对象上等待的线程。
notifyAll()
Object o = new Object();
o.notifyAll();
这个方法是唤醒o对象上处于等待的所有线程。