-
Thread内部常见的方法
1. 启动一个线程:t.start()
t.run():只是描述了要做的任务
t.start(): 真正开始执行任务(启动线程)
2.中断(终止)一个线程:让一个线程停止运行
方法一:手动创建标志位
存在的不足:当t线程内部在sleep时,主线程改变变量时,t线程不能及时响应
//线程终止
public class Demo8 {
//手动创建一个标志位,来作为run()的执行结束的条件
private static boolean isQuit = false;//作为成员变量时,lambda访问不再是变量捕获,而是内部类访问外部类的属性,不受final的限制
public static void main(String[] args) throws InterruptedException {
// final boolean isQuit = false;//作为局部变量,lambda表达式应为final,变量捕获,改为final后下面isQuit无法改为true
Thread t = new Thread(() -> {
while(!isQuit) {
// 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
System.out.println("线程进行中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程工作完毕!");
});
t.start();
Thread.sleep(5000);
isQuit = true;
System.out.println("设置isQuit为true");
}
}
方法二:t.interrupt()
//线程终止
public class Demo9{
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
//Thread内部,有一个现成的标志位,可以用来判定线程的结束
while(!Thread.currentThread().isInterrupted()) {//获取当前Thread对象
//哪个线程调用currentThread方法,就返回哪个线程的对象
//Thread.currentThread就是获取到当前线程的实例(对象 -> t)
System.out.println("线程进行中!");
try {
Thread.sleep(1000);
//正常来说,sleep()会休眠到时间到,才能唤醒
//但t.interrupt()这个操作会使sleep()内部触发一个异常,提前唤醒线程,sleep()方法抛出下面那个异常的同时,还会自动清除前面设置的标志位
} catch (InterruptedException e) {
//因为有sleep触发的异常,让咱们有更多的可操作性空间
//没有异常,目的明确
//收到要中断的信号时,有以下三种处理方式:
// //1.坐视不管,继续执行循环
// e.printStackTrace();
// //2.执行中断
// break;
//3.先完成手头的工作之后再去执行中断
System.out.println("待我完成手头的工作");
break;
}
}
System.out.println("线程工作完毕!");
});
t.start();
Thread.sleep(5000);
System.out.println("让t线程终止");
t.interrupt();//Thread内部的标志位设置为true,线程内部出现阻塞也可通过此方法唤醒
}
}
3.等待一个线程:t.join(),本质上控制线程结束的顺序
1)若t线程已经执行结束,那么主线程就不会发生阻塞,直接返回
2)若t线程正在运行中/还未运行,调用t.join(),主线程发生阻塞,一直到t线程执行结束为止
//线程等待
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("线程进行中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动t线程
t.start();
//主线程
System.out.println("join 等待开始");
t.join();//线程等待:一旦调用join(),主线程触发阻塞,等待t线程执行结束后,触发解除,再执行后面主线程的内容
//t.join(2000);//可以设置等待的时间(超时时间)
System.out.println("join 等待结束");
}
}
调度开销:
有些场景对时间精度要求很高:发射卫星、导弹拦截
用到的工具:VxWorks->往往需要实时操作系统+任务调度的开销在一定时间范围内
windows和linux调度开销大(实时系统的实时效果相对来说会比他们好,但功能上受到了限制)
//阻塞状态到就绪状态的转换之间有一个调度的开销
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
long beg = System.currentTimeMillis();//时间戳
Thread.sleep(1000);//1s之后系统会唤醒线程,由阻塞状态变为就绪状态,但并不能马上回到cpu上运行,有一个调度
long end = System.currentTimeMillis();
System.out.println("时间:" + (end - beg) + " ms");
}
}
4.获取当前对象的引用:Thread.currentThread()
public static void main(String[] args) {
Thread t = Thread.currentThread();
System.out.println(t.getName());//此线程为主线程
}
5.休眠线程:Thread.sleep()
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(System.currentTimeMillis());
}
-
线程的状态
NEW : 安排了工作(Thread对象已有,run方法已重写),但未行动 (正处于调用start方法之前)---> 找王五等人,给他安排任务,但还未行动起来
RUNNABLE :就绪状态,线程已经在cpu上执行或者正在排队上cpu执行 --->王五等人去银行窗口办理服务,有的正在被服务,有的还在排队
//这三种阻塞状态类比 --> 王五等人有一些其他事情要做(证件忘拿回去拿,填写信息等)
//当发生“线程卡死”问题时,可通过状态来初步确定卡死的原因
WAITING:阻塞,由于wait()这种不固定时间的方式产生的阻塞。
BLOCKED:阻塞,由于锁竞争产生的阻塞。
TIMED_WATING:阻塞,由于sleep()这种固定时间的方式产生的阻塞。
TERMINATED:任务完成,Thread对象还在,内核中的线程没了。
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//start()之前的状态为NEW
System.out.println(t.getState());
t.start();
//RUNNABLE是就绪状态,线程已经在cpu上执行或者正在排队上cpu执行
for (int i = 0; i < 5; i++) {
//由于sleep()这种固定时间的方式产生的阻塞
//当执行到t线程里的sleep(),当时的状态为TIMED_WAITING
System.out.println(t.getState());
Thread.sleep(1000);
}
t.join();
//线程执行结束之后,线程的状态为TERMINATED
System.out.println(t.getState());
}
}
-
线程安全问题
单个线程执行一份代码,达到预期效果;->线程安全
多个线程执行同样的代码,实际与预期不符,出现bug ->线程不安全
//线程安全的问题
//下面代码预期结果:count=100000
public class Demo13 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
count++;
}
});
// t1.start();
// t2.start();//两个线程同时执行(并发),会随机出现很多种情况,也就出现了线程安全问题
//必须要有下面的join(),因为线程是随机的,可能t1,t2都没执行完,就开始执行主线程. 很可能打印出来的 count 就是个 0
// t1.join();
// t2.join();
t1.start();
t1.join();
//一个线程执行完再执行另一个线程不会出现问题,不是并发
t2.start();
t2.join();
System.out.println(count);
}
}
线程不安全的原因:通过原因可解决线程安全问题
1.操作系统中,线程的调度顺序是随机的(抢占式执行),根源
2.两个线程,对同一个变量进行修改。
1)一个线程针对一个变量修改;
2)两个线程对不同变量修改;
3)两个线程对一个变量读取。
有时候可通过调整代码结构,规避问题(比如先执行t1,再执行t2)
3.修改操作不是原子的
非原子:先读再修改
假如count++是原子的(比如有一个cpu指令,可一次完成load,add,save这三步操作)
那么我们可以用加锁(Mysql并发执行事务,隔离性)来实现:加了一个锁之后,只有等一个线程执行完之后才能执行另一个线程。
4.内存可见性问题
5.指令重排序问题
-
用synchronized关键字加锁
//用synchronized加锁
public class Demo14 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Object locker1 = new Object();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
//两个线程对同一个对象加锁,出现“锁冲突/锁竞争”,当前面线程执行的时候,这个线程处于等待(阻塞)。
count++;
}
// synchronized (locker1) {//对不同对象加锁,仍是并发执行
// count++;
// }
}
});
t1.start();
t2.start();//两个线程同时执行,会随机出现很多种情况,也就出现了线程安全问题
t1.join();
t2.join();
System.out.println(count);
}
}