JAVA多线程——线程的状态与同步
一,线程的五种状态
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态:线程无法正常执行,可以能会进入到阻塞状态
- 终止状态
注意:
-
1.一个线程一旦执行完毕,不能回复,重新new,也是新的线程
-
2.阻塞解除无法直接恢复到运行状态,会恢复到就绪状态,等待下一次被cpu调度
1.1 就绪状态
一个线程如何进入到就绪状态:
-
start()
-
线程切换 : 被切换的线程恢复到就绪状态,等待下一次被调用
-
阻塞解除
-
yield() 礼让线程
yield()
当前正在执行的线程,一旦遇到yield,就会让出cpu的资源,下一次是哪一个线程的执行,等待cpu的调度
public class Class002_Yield implements Runnable{
public static void main(String[] args) {
Class002_Yield cy = new Class002_Yield();
new Thread(cy,"A").start();
new Thread(cy,"B").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始了");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"结束了");
}
}
1.2 阻塞状态
进入阻塞状态的方式:
- sleep() 模拟网络延迟 放大问题的可能性 实现倒计时
- join()
- wait()
- IO异常等
join()
当前正在执行的线程,遇到join方法另外一个线程插队,当前线程会进入阻塞状态,等待插队线程执行完毕,然后阻塞解除,等待cpu的调度,会进入到就绪状态
void join() 等待这个线程死亡。
void join(long millis) 此线程最多等待 millis毫秒。
注意: 需要线开启线程,然后再插队线程
public class Join01 {
public static void main(String[] args) {
new Thread(new Start()).start();
}
}
class Start implements Runnable{
@Override
public void run() {
System.out.println("起床");
System.out.println("去上课");
Study st = new Study();
Thread td = new Thread(st);
td.start();
try {
td.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("下课了");
System.out.println("睡觉");
}
}
class Study implements Runnable{
@Override
public void run() {
System.out.println("上课了,认真听课");
System.out.println("敲代码");
}
}
1.3 终止状态
进入终止状态的方式:
-
stop() 终止方法-->不推荐使用->比较暴力手法
-
通过添加标识判断 --> 推荐
-
正常执行完毕
interrupt
void interrupt() 为一个线程添加中断标识
boolean isInterrupted() 测试此线程是否已经添加过中断标识(调用过interrupt()方法添加的中断标识) ,true->添加过 false->没有添加过
static boolean interrupted() 测试当前线程是否已经添加过中断标识(调用过interrupt()方法添加的中断标识), 并同时复位标识
注意:
复位中断标识可以在某些情况下使用,符合业务需求(比如: 当中断添加指定次数,满足条件做…)
InterruptedException: sleep interrupted 当一个线程处于sleep休眠状态,遇到添加中断标识会遇到的异常
sleep–>InterruptedException - 如果有任何线程中断了当前线程。 抛出此异常时,将清除当前线程的中断状态 。
1.4 getState() 获取线程状态
Enum Thread.State :
- NEW
新生的线程处于此状态。 - RUNNABLE
在Java虚拟机中就绪或者执行的线程处于此状态。 - BLOCKED
被阻塞等待监视器锁定的线程处于此状态。在多线程通过锁控制线程执行中,排序等待锁资源的线程处于这种状态 - WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。 wait(),join() - TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。等待状态与实现相关处于这种状态 sleep(时间),join(时间),wait(时间) - TERMINATED
已退出的线程处于此状态。 终止状态
注意:当一个线程进入终止状态结束,中断标识会自动复位,但是可以根据线程 的状态进行判断getState
二,线程优先级
哪一个线程的优先级高,就可能优先执行谁,优先执行的概率大
优先级范围: 1~10 之间 1最小 10最大 默认5
void setPriority(int newPriority) 更改此线程的优先级。
int getPriority() 返回此线程的优先级。
注意: 优先级不能决定执行顺序,但是可以放大先执行的概率
public class Class006_setPriority {
public static void main(String[] args) {
Thread th1 = new Thread(()->{
System.out.println(Thread.currentThread().getName());
},"A");
Thread th2 = new Thread(()->{
System.out.println(Thread.currentThread().getName());
},"B");
Thread th3 = new Thread(()->{
System.out.println(Thread.currentThread().getName());
},"C");
//设置优先级
th1.setPriority(1);
th3.setPriority(10);
System.out.println(th1.getPriority()); //5
System.out.println(th2.getPriority()); //5
System.out.println(th3.getPriority()); //5
th1.start();
th2.start();
th3.start();
}
}
三,守护线程
线程:
用户线程
守护线程
- void setDaemon(boolean on) 参数为true,设置当前线程为守护线程,否则默认为用户线程
boolean isDaemon() 测试此线程是否为守护程序线程。
注意:
- 创建的线程默认都是用户线程
- 守护线程是用来守护用户线程的,当所有的用户线程全部执行完毕,守护线程会自动结束
- 垃圾回收机制是典型的守护线程
- 先设置守护线程,再开启线程
public class Class007_Daemon {
public static void main(String[] args) {
Thread th = new Thread(()->{
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程 th ");
}
});
//默认用户线程
System.out.println(th.isDaemon()); //false
//设置守护线程
th.setDaemon(true);
System.out.println(th.isDaemon());
//开启th线程
th.start();
//用户线程
for(int i=1;i<=5;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户线程-->main--->"+i);
}
}
}
四,synchronized线程安全
4.1 线程同步
多个线程同时操作同一份资源的时候,可能会遇到线程不安全问题
通过锁控制线程数据安全问题
同步锁 synchronized 关键字
注意点:
- 锁的对象内容->关心锁哪一个对象,能够锁住
同步的代码的范围–> 关心想要哪一个段代码多线程之后需要排序执行
同步的代码范围太大,效率太低,范围太小,可能不安全
锁的对象一定要锁不变的内容,不变的内容才能锁住
synchronized 使用方式:
- 同步方法 : 在方法上synchronized修饰
静态方法
成员方法- 同步块 使用synchronized修饰{}
synchronized(锁的对象内容){
排队执行的代码段…
}
锁的对象内容 : this | 类.class | 资源
4.2 线程通信
Object类中的方法:
- wait() 等待 : 当一个线程对象调用wait()方法,进入到相关对象的等待池中进行等待,等待阻塞状态,等待被唤醒
会释放cpu的资源,同时释放对象的锁 - notify() 唤醒 : 唤醒对应对象等待池中正在等待的线程,如果存在多个唤醒某一个
被唤醒的线程会恢复到就绪状态,到底是否能够执行需要cpu的调用,并且获取对象的锁资源
wait()与notify()方法必须存在同步环境内部使用,因为控制多线程之后共享数据存储安全问题
sleep 线程休眠 抱着资源睡觉 : 会让出cpu的资源但是不会让出对象的锁资源
4.3 死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在 互相等待的线程称为死锁线程。
public class Class006_MakeUp {
public static void main(String[] args) {
Makeup m1 = new Makeup(); m1.girl="大丫"; m1.flag=0;
Makeup m2 = new Makeup(); m2.girl="小丫"; m2.flag=1;
m1.start();
m2.start();
}
}
class Makeup extends Thread {
int flag;
String girl;
static Lipstick lipstick=new Lipstick(); //一个口红对象
static Mirror mirror= new Mirror(); //一个镜子对象
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup(){
if(flag==0){
synchronized (lipstick) {
System.out.println(girl+"拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(girl+"拿着镜子!");
}
}
}
else{
synchronized (mirror) {
System.out.println(girl+"拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(girl+"拿着口红!");
}
}
}
}
}
// 口红
class Lipstick{
}
// 镜子
class Mirror{
}