[内容时间:2021.09.05]
进程的几种状态
创建、就绪、运行、阻塞、死亡
进程和线程的关系
多线程的三大特征
原子性、可见性、有序性
- 进程里包含(一个或多个)线程,一个进程可以启动多个线程
- 进程与进程之间内存不共享,但一个进程下的线程与线程 共享堆内存和方法区内存
- 每个线程有一个栈独立运作,叫多线程并发
- 多线程并发是为了提高程序的执行效率
实现线程有两 (三) 种方式
Java支持多线程机制,并且Java已经将多线程实现了,我们只需要继承就好了
(第三种后面讲)
第一种:分支线程直接继承 java.lang.Thread ,重写run方法
第二种:分支线程实现 java.lang.Runnable 接口 ,重写run方法(这种比较常用,因为实现接口还能继承别的类,而且接口支持多继承)
采用匿名内部类也行,但是不常用
第三种:FutureTask方式,实现Callable接口(JDK8新特性)
优点:可以获取到线程的执行结果,有返回类型,其他两种都是空类型
缺点:效率表弟,在获取 t 线程的执行结果时,当前线程受阻塞,效率较低。
重点:run( ) 方法的异常不能throws,只能try—catch,因为子类重写父类方法不能抛出更多(宽泛)的异常
控制线程的方法( )
sleep方法
interrupt方法
这个方法会让指定对象的sleep( )方法报异常,这就是终止睡眠的原理,所以sleep需要try—catch
stop方法
用法是,引用 . stop()
此方法已过时,因为强行终止线程可能会导致数据的损失,线程没有保存的数据会丢失,所以不建议使用。
如何合理终止一个线程的执行
关于线程的调度
常见的调度模型
抢占式调度模型
哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些(多一些)。Java采用的就是抢占式调度模型
均分式调度模型
平均分配CPU时间片,每个线程占有的CPU时间长度一样
有一些语言采用的调度模型就是这种方式
Java中提供了哪些方法是和线程调度有关?
实例方法
void setPriority(int newPrioirty) 设置线程的优先级
- int getPriority() 获取线程优先级
最低优先级1
默认优先级5
最高优先级10
静态方法
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield方法不是阻塞方法,让当前线程让位,让给其他线程使用
yield方法的执行会让当前线程从“运行状态”回到“就绪状态”。(就绪状态会继续抢夺……相当于给别人一个再抢的机会……给机会了,但没完全给……)
join方法,合并线程(不是栈合并)
栈不会合并不会消失,只是栈直接发生了等待关系,栈之间协调了
class Mythread extands Thread{ public void doSome(){ MyThread2 t = new MyThread2(); //当前线程进入阻塞,t线程执行,直到t线程结束(意思就是t加入进来执行,顺序是从上往下) //记得处理异常 t.join(); System.out.println("Hello World !"); //t执行完才最后到这个。 } } class MyThread 2 extends Thread{ }
线程安全
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将建成的定义、线程对象的创建、线程的启动,都已经实现完了。这些代码我们都不需要编写
最重要的是:要知道,我们编写的程序需要放到一个多线程的环境下运行,更要关注的是这些数据在多线程并发的环境下是否是安全的。重点( * * * * * )
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件
- 多线程并发
- 有共享数据
- 共享数据有修改行为
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,而且这个数据还会被修改,此时就存在线程安全问题
如何解决?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
怎么解决线程安全问题?
使用“线程同步机制”
线程同步就是线程排队了,排队就会牺牲一部分效率。没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
两个专业术语
异步编程模型
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步变成模型
其实就是多线程并发(效率高)
异步就是并发
同步编程模型
线程t1和线程t2,在线程t1执行的时候,必须等待t2执行结束,反之亦然。
两个线程之间发生了等待关系,这就是同步编程模型(效率低,线程排队执行)
同步就是排队
synchronize代码块
// 取钱的方法
public void withdraw(int money) {
/**
* 这里是线程不安全的情况:
* 设置一个网络延迟必出bug
* try {
* Thread.sleep(1000);
* } catch (InterruptedException e) {
* e.printStackTrace();
* }
* //延迟在保存之前,两个线程都共用了account的数据
* //保存之前两个线程都进来了,那就出问题了
* //余额(这里是保存操作了)
* this.setBalance(this.getBalance() - money);
*/
//================================================================
/**
* 以下是线程安全:
* 线程同步机制的语法是:
* synchronize(){
* //线程同步代码块
* }
* ()中写什么?
* 主要是看需要将那些线程同步
* 假设t1,t2,t3,t4,t5,五个线程,但是我只希望t1,t2,t3排队,怎么办?
* 则需要在()中写t1,t2,t3共享的对象,而这个对象对应t4,t5不是共享的。
*
* 而这里的共享对象很显然是账户对象,所以this就是账户对象
* 不一定是this,只要是线程共享的对象就行。
*
* 在Java语言中,任何一个对象都有一把“锁”,其实这把锁就是标记(只是把它叫做锁)
* 100个对象,100把锁。
*
* 以下代码执行原理:
* 1、假设t1和t2线程并发,开始执行以下代码的时候肯定有一个先一个后
* 2、假设t1先执行了,遇到了synchronize,这个时候自动找“后面共享对象”的对象锁
* 找到之后,占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。
* 直到同步代码块及诶书,这把锁才会释放。
* 3、假设t1已经占有了这把锁,此时t2只能在同步代码块外面等待t1的结束,直到t1吧同步代码块执行结束了,
* t1会归还这把锁,此时t2终于等到这把锁,然后占有,进入代码块执行。
*
* 共享对象一定要选好
*/
// synchronized (obj)也行
// Object object2 = new Object();
// synchronized (onject2)不行,因为是局部变量,不是共享对象。
// synchronized ("abc")也行,因为abc在字符串常量池内
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - money);
}
}
三大变量
- 实例变量(在堆区)
- 静态变量(在方法区)
- 局部变量(在栈区)
在栈内存的变量就不存在线程安全问题了,因为多线程不共享栈内存。
方法区中的静态变量中的常量也不存在安全问题
注意一下synchronize扩大范围的问题,其实在安全的情况下,范围越小效率越高
synchronize的使用方法
上述是第一种,包装在实例变量上
说说第二种方式:写在方法体上。
public synchronize void withdraw( ){ xxxxxxxxxxxx }这种方式,如果共享对象是 this 那么推荐使用这种方式。代码简洁。
但这种方式的缺点是可能无故扩大同步范围,导致效率低下。
第三种方式:在静态方法上使用synchronize
表示找类锁。类锁永远只有一把。类锁保证了静态变量的安全
就算创建了一百个对象,那类锁也只有一把。
对象锁:100个对象100把锁
类锁:100个对象,也可能只是1把锁
死锁
synchronize在开发中最好不要嵌套使用,一不小心就可能导致死锁的现象发生,而且很难查出,因为无异常,无报错,很难调试!
守护线程
java语言中线程分为两大类
一类是用户线程
一类是守护线程(后台线程)
其中具有代表性的就是:垃圾回收器线程(守护线程)
守护线程的特点
一般守护线程是个死循环,所有用户线程只要结束,守护线程自动结束
注意:比如主线程main方法是一个用户线程
守护线程用在什么地方呢?
比如每天0:00的时候系统数据自动备份
这个时候需要使用定时器,并且我们可以将定时器设置为守护线程
一直在那里看着,每到0;00的时候就备份一次,所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了
守护线程的实现
用 t.start() 启动线程之前,调用 t.setDaemon(true) 即可让 t 线程设置为守护线程。就算 t 线程的run里是死循环,用户线程结束,守护线程也会结束。
定时器
……