一.线程的状态
1.1 观察线程的所有状态
- NEW:安排工作,还未开始行动 (Thread对象创建好了,但是还没有调用start方法)
- (就绪状态)RUNNABLE:可工作的,又可以分为正在工作和即将开始工作
- (阻塞状态)BLOCKED:这几个都表示排队等着其他事情(因为锁产生了阻塞)
- (阻塞状态)WAITING:这几个都表示排队等着其他事情(因为调用了wait产生了阻塞了)
- (阻塞状态)TIME_WAITING:这几个都表示排队等着其他事情(因为sleep产生阻塞)
- TERMINATED:工作完成了
就绪状态:可理解成两种情况
1)线程正在CPU上运行
2)线程在这里排队,随时都可以去排队
- sleep阻塞前:
package thread;
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(() ->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.getState());
//获取当前线程之前的状态
t.start();
t.join();//等待上述t线程执行结束
System.out.println(t.getState());
//获取当前线程结束过后的状态
}
}
- sleep阻塞后:
package thread;
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(() ->{
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(t.getState());
//获取当前线程之前的状态
t.start();
Thread.sleep(1000);
System.out.println(t.getState());
//获取当前线程结束过后的状态
}
}
1.2 线程安全
例:两个线程,针对同一个变量,进行循环自增,各自自增5万次,预期最终为十万次,但实际上并不是(产生bug)
在多线程下,发生由于多线程执行导致的bug,统称为“线程安全问题”,如果某个代码在单线程下执行也没有问题,多个线程下执行也没有问题,则称为“线程安全,反之也可以称为“线程不安全”
有问题的代码
package thread;
class Counter{
public int count=0;
public void increase(){
count++;
}
}
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread t1=new Thread(() ->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
Thread t2=new Thread(() ->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
}
第一次运行结果
第二次运行结果
第三次运行结果
线程安全的根本原因:
1.【根本原因】:多个线程之间的调度顺序是“随机的”,操作系统使用“抢占式”执行的策略来调度线程
和单线程不同的是,多线程下,代码的执行顺序产生了更多的变化
以往只需要考虑代码在一个固定的顺序下执行,执行正确即可,现在则需要考虑多线程下,N种执行顺序下,代码执行结果都得正确
2.多个线程同时修改同一个变量,容易产生线程安全问题
一个线程改变一个变量,没事
多个线程读取同一个变量,没事
多个线程修改多个变量,没事
3.进行的修改,不是“原子的”,即不可拆分的,例count++就不是原子的
如果修改操作,能够按照原子的方式来完成,此时也不会有线程安全问题
=直接赋值,视为原子 if=先判定,再赋值,也不是原子
解决线程问题,最主要的切入手段:
加锁 => 相当于是把一组操作,给打包成一个“原子”的操作
事务的原子操作,主要是靠回滚
此处的原子则是通过锁,进行“互斥”
这个线程进行工作时,其他线程无法进行工作
4.内存可见性,引起的线程安全问题
5.指令重排序,引起的线程安全问题
解决方案
在对count++的时候进行加锁操作
java引入了一个synchronized关键字
使用方法:直接在方法前加关键字
进入方法就会加锁,结束方法就会解锁
加锁的目的是为了互斥使用资源(互斥的修改变量)
package thread;
class Counter{
public int count=0;
synchronized public void increase(){
count++;
}
}
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread t1=new Thread(() ->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
Thread t2=new Thread(() ->{
for(int i=0;i<50000;i++){
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
}