文章目录
前言
一、线程的各种状态
1.NEW:安排了工作,还未开始行动
Thread 对象创建好了.但是还没有能用start方法
public class Tread {
public static void main(String[] args) {
Thread t=new Thread(()->{
while (true){
//System.out.println("dddd");
}
});
System.out.println(t.getState());
t.start();
}
}
2.TERMINATED:工作完成了.
public class Tread {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.getState());//t还没运行
t.start();
t.join();
System.out.println(t.getState());//t一定结束了
}
}
3.RUNNABLE:可工作的.又可以分成正在工作中和即将开始工作.
可理解成两种情况:1)线程正在cpu上运行;2)线程在这里排队,随时都可以去cpu上执行.
public class Tread {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// System.out.println(t.getState());
t.start();
//t.join();
System.out.println(t.getState());
}
}
4.TIMED_WAITING:这几个都表示排队等着其他事情
因为sleep产生阻塞了-
public class Tread {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
try {
Thread.sleep(90000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// System.out.println(t.getState());
t.start();
//t.join();
Thread.sleep(1000);
System.out.println(t.getState());
}
}
5. BLOCKED:这几个都表示排队等着其他事情
因为锁产生阻塞了
6. WAITING:这几个都表示排队等着其他事情
因为wait()产生阻塞了
7.各种状态之间的关系:
二、线程安全问题
1.啥叫bug?
只要实际运行效果和预期效果(需求中的效果)不一致,就可以称为是一个bug !
2.线程安全问题
在多线程下,发现由于多线程执行,导致的bug,统称为“线程安全问题”。
如果某个代码,在单线程下执行没有问题,多个线程下执行也没问题,则称为“线程安全",反之也可以称为“线程不安全"。
1.count++引起的线性安全问题(实例)
两个线程,针对同一个变量,进行循环自增.各自自增5w次,预期最终应该是10w:
class Counter{
public int count=0;
public void increase(){
count++;
}
}
public class Tread07 {
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();//涉及变量捕获,此处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);
}
}
实际上并不是!!
结果!=100000,多线程计算出现问题,为啥?
因为count++:
虽然一个cpu核心上,击存器就这么—组.
但是两个线程,可以视为是各自有各自的一组奇存器.本质上是“分时复用"的.
线程的执行是随机的,抢占式执行的过程.
如:此处这里的结果就会出现问题,自增两次,结果为1
虽然是自增两次。但是由于两个线程并发执行,就可能在一定的执行顺序下,导致运算的中间结果就被覆盖了。
在这5w 次的过程中有多少次会出要覆盖结果的?不确定!而且得到的这个错误值,—定是小于10w。
2.线程安全问题的原因
1.多个线程之间的调度顺序是“随机的”,
操作系统使用“抢占式"执行的策略来调度线程。(罪魁祸首,万恶之源)(无法使线程在固定顺序下执行,无力改变)
2.多个线程同时修改同一个变量,容易产生线程安全问题.
3.进行的修改,不是"原子的”。
如果修改操作,能按照原子的方式来完成此时也不会有线程安全问题
count++就不是原子的。
4.内存可见性,引起的线程安全问题.
5.指令重排序,引起的线程安全问题.
3.加锁synchronized
在count++引起线程安全问题的实例中主要是由1,2,3导致的,其中1和2无法更改,因此解决3.
利用加锁=>相当于是把一组操作,给打包成一个“原子”的操作
代码中的锁,就是让多个线程同一时刻只有一个线程能使用这个变量
Java引入了一个synchronized关键字加锁。
直接给方法进行加锁,进入方法就会加锁,出了方法就会解锁。
当t1加锁了之后,t2进行阻塞等待,这个阻塞直持续到t1把锁释放之后,t2才能够加锁成功!!
此处t2的阻度等待,就把t2的针对count++操作推迟到了后面完成,直到完成了count ++,t2才能够真正进行count++,把“穿插执行”变成了串行执行了。
synchronized每次加锁,也是针对某个特定的对象加锁。synchronized修饰方法,此时就相当于是针对this 加锁.
此处this针对counter对象加锁。
所以此处是两个线程针对同一个对象进行加锁:
就会出视锁竞争争/冲突(一个线程能加锁成功.另一个线程阻塞等待)
如果是两个线程针对的不同对象加锁?
不会产生锁竞争.也就不存在锁冲突等一系列的操作了。
例如—个线程加锁了,一个线程没加锁此时会存在线程安全问题,不存在锁竞争。
注意:synchronized给静态方法加锁:
3.内存可见性,引起的问题
1.编译器优化问题
t1始终在进行 while循环,t2则是要让用户通过控制台输入一个整数,作为isQuit的值。
当用户输入的仍然是0的时候, t1线程继续执行.
如果用户输入的非0, t1线程就应该循环结束.
public class Tread08 {
public static int isQuit=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(isQuit==0){
}
System.out.println("t1执行结束");
});
Thread t2=new Thread(()->{
Scanner scanner=new Scanner(System.in);
System.out.println("修改t1的值:");
isQuit=scanner.nextInt();
});
t1.start();
t2.start();
}
}
此题中虽然修改了t1的值但是t1线程并没有结束,这也是不符合预期的,也是bug !!
编译器优化:
程序猿负责写代码的.当写好一个代码之后,人家开发java编译器,开发jvm的大佬,可能会认为你这个代码写的不够好
当你的代码实际执行的时候,编译器/jvm就可能把你的代码给改了,保持原有逻辑不变的情况下,提高代码的执行效率.
本质上是两个指令:1.load(读内存)(读内存操作,速度非常慢)
2.jcmp(比较,并跳转)(寄存器操作,速度极快)
此时,编译器JVM就发现,这个逻辑中,代码要反复的,快速的读取同一个内存的值。并且,这个内存的值,每次读出来还是一样的.
此时,编译器就做出一个大胆的决策,直接把 load 操作优化掉了.只是第一次执行load . 后续都不再执行load,直接拿寄存器中的数据进行比较了。
但是,程序猿在另一个线程中,修改了isQuit的值!!所以出现了bug。
注意:编译器什么时候,对什么进行优化是一个玄学。
2.volatile 关键字
把volatile用来修饰一个变量之后,编译器就明白,这个变量是"易变"的,就不能按照上述方式,把读操作优化到读寄存器中.(编译器就会禁止上述优化)
于是就能保证t1在循环过程中,始终都能读取内存中的数据
public class Tread08 {
public volatile static int isQuit=0;
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(isQuit==0){
}
System.out.println("t1执行结束");
});
Thread t2=new Thread(()->{
Scanner scanner=new Scanner(System.in);
System.out.println("修改t1的值:");
isQuit=scanner.nextInt();
});
t1.start();
t2.start();
}
}
volatile 本质上是保证变量的内存可见性.(禁止该变量的读操作被优化到读寄存器中)
三、wait和notify
wait(等待)和notify(通知),都是Object提供的方法.
随便找个对象,都可以使用wait和notify~~
1.wait
wait在执行的时候,会做三件事:
1.解锁. object.wait,就会尝试针对object 对象解锁.
⒉.阻塞等待.
3.当被其他线程唤醒之后,就会尝试重新加锁,加锁成功, wait执行完毕,继续往下执行其他逻辑.
public class Tread09 {//当wait引起线程阻塞之后,可以使用interrupt方法,把线程给唤醒.打断当前线程的阻塞状态的.
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
object.wait();
}
}
报错:
wait要解锁,前提就是先能加上锁
核心思路:先加锁,在synchronized里头再进行wait!!
public class Tread09 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
synchronized (object) {
object.wait();
}
System.out.println("wait结束");
}
}
结果:一直阻塞等待。一直会阻塞到其他进程进行notify。
2.wait和notify
public class Tread09 {
public static Object locker=new Object();
public static void main(String[] args){
Thread t1=new Thread(()->{
while (true){
synchronized (locker){
System.out.println("t1 wait开始");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 wait结束");
}
}
});
Thread t2=new Thread(()->{
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker){
System.out.println("t2 notify开始");
locker.notify();
System.out.println("t2 notify结束");
}
}
});
t1.start();
t2.start();
}
}
几个注意事项:
1.要想让notify能够顺利唤醒wait就需要确保wait和notify 都是使用同一个对象调用的
.2. wait和notify 都需要放到 synchronized之内的.
虽然 notify不涉及"解锁操作"但是java 也强制要求notify要放到synchronized中
(系统的原生api中,没有这个要求)
3.如果进行notify的时候,另一个线程并没有处于wait状态.此时,notify相当于“空打一炮".不会有任何副作用~~
可以实现:如果就想唤醒某个指定的线程,就可以让不同的线程,使用不同的对象来进行wait 。想唤醒谁,就可以使用对应的对象来notify
notifyAll()
线程可能有多个
比如,可以有N个线程进行wait,一个线程负责notify
notify操作只会唤醒一个线程.具体是唤醒了哪个线程?是随机的!!
notifyAll 唤醒全部处于waiting中的线程
3.wait和sleep之间的区别
sleep是有一个明确的时间的. 到达时间,自然就会被唤醒.也能提前唤醒,使用interrupt就可以
wait 默认是一个死等,一直等到有其他线程notify.(顺理成章的唤醒.唤醒之后该线程还需要继续工作后续还会进入到wait 状态.)
wait也能够被interrupt提前唤醒.(很少用)