温江 网站建设,让别人做网站的步骤,网站图片管理系统,开发区人才目录 Day 6#xff1a;多线程#xff08;4#xff09;1. 线程不安全的原因2. 锁3. synchronized Day 6#xff1a;多线程#xff08;4#xff09;
前序#xff1a;针对Day 5结尾的count 多线程的执行#xff0c;是随机调度抢占式的执行模式#xff0c;某个线程执行指… 目录 Day 6多线程41. 线程不安全的原因2. 锁3. synchronized Day 6多线程4
前序针对Day 5结尾的count 多线程的执行是随机调度抢占式的执行模式某个线程执行指令过程中当它执行到任何一个指令的时候都有可能被其他线程把它的CPU抢占走 实际并发执行由于上述原因以及count本质是CPU的三个指令两个线程执行指令的相对顺序就可能会存在多种可能不同的执行顺序得到的结果就可能会存在差异
1. 线程不安全的原因
1线程在系统中是随即调度的抢占式执行的这是线程不安全的罪魁祸首万恶之源
2当前代码中多个线程同时修改同一个变量
3线程针对变量的修改操作不是“原子”的count这种操作不是原子的是包含了三个指令
4内存可见性问题后续介绍
5指令重排序后续介绍
针对上述原因进行问题解决 原因1无法干预属于内核设计无法改变 原因2是一个切入点但是在Java中并不普适针对特定场景可以使用例如String是不可变对象 一个线程修改同一个变量ok多个线程读取同一个变量ok多个线程修改不同的变量ok String为不可变对象很好的保证线程安全有稳定的哈希值方便在常量池中缓存 原因3是解决线程安全问题最普适的方案可以通过一些操作把“非原子”操作打包成一个“原子”操作例如加锁 如果某个代码操作对应到一个CPU指令就是原子的对应到多个就不是原子的每个代码最终变成哪些指令需要对芯片手册CPU指令集要有比较深入的理解
2. 锁
锁本质上是操作系统提供的功能内核提供的功能同过api给应用程序了JavaJVM对于这样的系统api又进行了封装其他的语言同样也可以封装/调用这样的系统api来完成加锁操作
锁的操作主要是两个方面
加锁t1加锁之后t2也尝试加锁就会阻塞等待系统内核控制的在Java中就能看到BLOCKED状态解锁直到t1解锁之后t2才有可能拿到锁加锁成功体现了锁的互斥
锁的主要特性互斥一个线程获取到锁之后另一个线程也尝试加这个锁就会阻塞等待也叫做锁竞争/锁冲突 代码中可以创建多个锁只有多个线程竞争同一把锁才会产生互斥针对不同的锁则不会 3. synchronized
synchronized (locker){.......
}synchronized是Java中的关键字指的是同步的此处谈到的同步指的是互斥/独占反义词可以理解为共享synchronized (locker)()里面就是写的“锁对象” 锁对象的用途有且只有一个就是用来区分两个线程是否是针对同一个对象加锁如果是就会出现锁竞争/锁冲突/锁互斥就会引起阻塞等待和对象具体是什么类型有什么属性或者方法没有任何关系 {}中进入到代码块就是给上述锁对象进行了加锁操作当出了代码块就是给上述锁对象进行了解锁操作
package thread;public class Demo20 {private static int count 0;public static void main(String[] args) throws InterruptedException {Object locker 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;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count count);}
}这两个线程中每次进行count是存在锁竞争的会变成串行执行但是执行for循环中的条件以及i仍然是并发执行的 package thread;class Counter {private int count 0;//synchronized修饰普通方法就相当于针对this加锁了public void add() {synchronized (this){count;}}//上述方法也可以写成如下形式synchronized public void add() {count;}public int get(){return count;}//synchronized修饰static方法相当于针对该类的类对象加锁public static void func() {synchronized(Counter.class){//.....}}//上述方法也可以写成如下形式synchronized public static void func(){//......}}
public class Demo20 {public static void main(String[] args) throws InterruptedException {Counter counter new Counter();Thread t1 new Thread(() -{for (int i 0; i 50000; i) {counter.add();//counter.func();}});Thread t2 new Thread(() -{for (int i 0; i 50000; i) {counter.add();//counter.func();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count counter.get());}
}synchronized(Counter.class)中的Counter.class是反射即程序运行时能够拿到类一些属性信息包括不限于
类的名字继承自哪个类实现了哪些interface类提供了哪些方法每个方法叫什么每个方法有什么参数参数是什么类型类提供了哪些属性每个属性叫什么每个属性是什么类型public/private…)
上述信息最初都是程序员自己写的.java源代码中提供的
java编译之后.java形成了.class字节码上述信息转化为二进制java运行.class字节码就会读取这里的内容 加载到内存中给后续使用这个类提供基础所以JVM中在内存里保存上述信息的对象就是类对象后续想创建这个类的实例就需要依照上述信息在Java中可以通过类名.class来拿到这个类对象一个java进程中某个类只能有唯一一个类对象
所以一旦多个线程调用func则这些线程都会触发锁竞争