视频网站开发步骤,做网站竞价是什么意思,公司建网站怎么建,熊岳网站怎么做这里写目录标题 1.线程安全1.1 什么是线程安全1.2 原子性1.3 线程不安全的原因1.4 通过synchronized进行加锁解决线程安全问题1.5 可重入锁1.6 死锁1.7 Java标准库中的线程安全类1.8 通过volatile关键字解决内存可见性引起的线程安全问题 2. wait 和notify2.1 wait() --使当前线… 这里写目录标题 1.线程安全1.1 什么是线程安全1.2 原子性1.3 线程不安全的原因1.4 通过synchronized进行加锁解决线程安全问题1.5 可重入锁1.6 死锁1.7 Java标准库中的线程安全类1.8 通过volatile关键字解决内存可见性引起的线程安全问题 2. wait 和notify2.1 wait() --使当前线程进行等待释放当前锁满足一定条件时被唤醒重新尝试获取这个锁2.2 notify()--是唤醒等待的线程2.3 wait 和sleep直接的区别 1.线程安全
1.1 什么是线程安全
某个代码在单线程下执行没有任何问题在多线程下执行出现bug。
1.2 原子性
把代码比如一个房间每个线程就是要进入这个房间的人。如果没有任何保护机制A进入之后进行一些列隐私操作然后B也可以进入房间从而打断A这样就是不具备原子性的。把线程A进入房间的一系列操作进行打包成一个整体进行上锁其他线程就进不来这样就保证了代码的原子性。
1.3 线程不安全的原因
根本原因操作系统上的线程是随即调度、抢占式执行的线程之间执行的顺序带来了很多变数。代码结构代码中多个线程同时修改一个变量一个线程修改一个线程读也可能存在问题。直接原因多线程的修改操作下本身不是原子的一条Java语句不一定是原子的也不一定只是一条指令比如 i有三步操作1.从内存把数据读到CPU2.进行数据更新3.把数据写回到CPU多个CPU指令一个线程执行这些指令执行到一半被调度走从而其他线程可能会被调度每个CPU指令都是原子的要么执行完要么不执行。内存可见性问题指令重排序问题
1.4 通过synchronized进行加锁解决线程安全问题
synchronized关键字随便放Object对象都行两个线程之间是否使用的是同一个对象是同一个会产生竞争反则是不会。进入代码块加锁出代码块是解锁。synchronized 修饰普通方法相当于给this加锁锁对象是this修饰静态方法相当于给类对象加锁。
public class ThreadDemo19 {private static int count 0;private static int count2 0;public static void main(String[] args) throws InterruptedException {// 随便创建个对象Object locker new Object(); //两个线程之间是否使用的是同一个对象是同一个会产生竞争反则是不会Object locekr2 new Object();// 创建两个线程每个线程都针对上述count变量循环5w次 循环自增的代码存在线程安全问题int tmp 0;Thread t1 new Thread(() - {for (int i 0; i 50000; i) {/*// 1一下线程修改一个变量没有影响count;*/// 加锁 synchronizedsynchronized(locker) { //进入大括号 就会加锁count;} // 出大括号 就会解锁// 2多个线程读取同一个变量不会影响//System.out.println(count); //只是读取变量变量的内容是固定不变的}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {//3多个线程修改不同的变量没有影响//count2;//System.out.println(count);//两个线程针对不同对象加锁存在不了锁竞争就会出现线程安全问题/*synchronized (locekr2) {count;}*/synchronized (locker) {count;}}});t1.start();t2.start();t1.join();t2.join();//打印count的结果System.out.println(count count);}
}synchronized 加锁的效果也称为 互斥性。
class Test {public int count;// synchronized 是加到static方法上就等价于给 类对象加锁/*synchronized public static void func() {}public static void func() { // 使用较少synchronized (Test.class) {}}*/// 等同于下面 synchronized (this)的代码/*synchronized public void add() {count;}*/public void add() {/*synchronized (this) {count;}*/// 获取Test类的对象 (在一个java进程中一个类的类对象都是只有一个)// 所以在这里第一线程和第二个线程拿到的类对象是同一个类对象因此锁竞争仍然存在能保证线程安全synchronized (Test.class) {count;}}}
public class ThreadDemo20 {public static void main(String[] args) throws InterruptedException {Test test new Test();Thread thread1 new Thread(() - {for (int i 0; i 50000; i) {test.add();}});Thread thread2 new Thread(() - {for (int i 0; i 50000; i) {test.add();}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count test.count);}
}1.5 可重入锁
可重入锁一共就只有一把锁同一个线程此时锁对象就知道第二次加锁的线程就是持有锁的线程第二次进行加锁的发现加锁线程和持有锁线程是同一个线程即能加锁。 判定当前加锁线程是否是加锁的线程如果不是同一个线程阻塞如果是同一个线程计数器。
public class ThreadDemo21 {public static void main(String[] args) {Object locker new Object();Thread t new Thread(() - {synchronized (locker) { //可重入锁在最外层的{进行加锁 真正加锁同时把计数器1(初始是01//之后就变成了1说明当前这个对象被该线程加锁一次 )同时记录线程是谁//再加一个锁当前由于是同一个线程此时锁对象就知道了第二次加锁的线程就是持有锁的线程// 第二次操作就可以直接放行通过不会出现阻塞 ——》这个特性 称为“可重入”synchronized (locker) { //第二次加锁的时候发现加锁线程和持有锁线程是同一个线程即能加锁//成功计数器如果不是同一个线程阻塞System.out.println(hello);} // 把计数器-12-1》1不为0.不会真的解锁} // 在最外层的}进行解锁 1-10》 进行解锁});t.start();}
}1.6 死锁
加锁是解决线程安全问题但是加锁方式不当就可能产生死锁。 死锁三种典型场景
一个线程一把锁如果锁不是可重入锁并且是一个线程对这把锁加锁两次就会出现死锁。钥匙锁屋里了两个线程两把锁线程1获取到 锁A线程2 获取到锁B然后线程1 尝试获取B线程2 尝试获取 锁A就会出现死锁。钥匙锁车里车钥匙锁房间里解决方案约定加锁顺序先对A进行加锁再对B进行加锁。
public class ThreadDemo22 {public static void main(String[] args) {Object A new Object();Object B new Object();Thread t1 new Thread(() - {synchronized (A) {//sleep一下给t2时间让t2也能拿到Btry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试获取B并没有释放Asynchronized (B) {System.out.println(t1拿到了两个线程);}}});/*Thread t2 new Thread(() - {synchronized (B) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//尝试获取A并没有释放Bsynchronized (A) {System.out.println(t2拿到了两个线程);}}});*/Thread t2 new Thread(() - {synchronized (A) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 解决方案先对A进行加锁再对B进行加锁synchronized (B) {System.out.println(t2拿到了两个线程);}}});t1.start();t2.start();}
}N个线程M把锁哲学家就餐问题解决方案指定加锁顺序针对五把锁都进行编号约定每个线程获取锁的时候一定要先获取编号小的锁后获取编号大的锁。
产生死锁的四个必要条件
互斥使用获取锁的过程是互斥的一个线程拿到了这把锁另一个线程也想获取就需要阻塞 等待锁最基本的特性不太好破坏不可抢占一个线程拿到了这把锁只能主动解锁不能让别的线程强行把锁抢走。也是锁最基本的特性请求保持一个线程拿到了锁A之后在持有锁A的前提下尝试获取锁B。根据代码结构看实际情况可破坏循环等待/环路等待 代码结构最容易破坏的 解决死锁问题核心思路就是破坏上述的必要条件破坏其一就能解决死锁问题。只要指定一定的规则就可以有效的避免循环等待。
1.7 Java标准库中的线程安全类
Java标准库中很多都是线程不安全的这些类可能会涉及到多线程修改共享数据又没有任何加锁措施。
ArrayListLinkedListHashMapTreeMapHashSetTreeSetStringBuilder
还有一些是线程安全的使用了一些锁机制来控制
Vector(不推荐使用)HashTable(不推荐使用)ConcurrentHashMapStringBuffer 没加锁不涉及修改仍然是线程安全的String
1.8 通过volatile关键字解决内存可见性引起的线程安全问题
volatile能够保证内存可见性另一个功能禁止指令重排序。什么叫内存可见性就是高度依赖编译器的优化的具体实现如果一个线程写一个线程读也可能存在线程安全问题
设计一个预期通过 t2 线程输入的整数只要输入的不为0就可以使t1线程结束。
下面代码中 t2修改了内存但是 t1没有看到这个内存的变化即内存可见性问题。在这里的内存可见性问题 在多线程下编译器对代码的优化出现了误判本来编译器期望 把读内存操作给优化成读寄存器中缓存的值这样优化有助于我们提高循环的执行效率并且编译器发现没有谁来修改flag从而进行了错误的判断在后续通过scanner用户输入来修改flag导致这边 t2 线程修改了而上面 t1 线程判断flag是否为0这边没有生效因此出现了这边不能让t1结束的bug。
原理 解决方案给判断的变量 添加volatile关键字在写入的时候
改变线程工作中内存volatile变量副本的值将改变后的副本的值从工作内存刷新到主存中
在读取volatile修饰的变量的时候
从内存中读取volatile变量的最新值到线程的工作内存中从工作内存中读取volatile变量的副本
import java.util.Scanner;
public class ThreadDemo23 {// t2修改了内存但是t1没有看到这个内存的变化就称为 内存可见性 问题//volatile关键字 核心功能保证内存可见性 另一个功能禁止指令重排序private volatile static int flag 0;public static void main(String[] args) {// 预期通过t2线程输入的整数只要输入的不为0就可以使t1线程结束Thread t1 new Thread(() - {while (flag 0) {// 循环体里没有内容/*try {// 不加sleep一秒循环上百亿次load操作的整体开销非常大优化的迫切程度就更高// 加了之后一秒循环1000次load整体开销就没这么大优化的迫切程度就降低了Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}*/}System.out.println(t1 线程结束);});Thread t2 new Thread(() - {System.out.println(请输入flag的值);Scanner scanner new Scanner(System.in);flag scanner.nextInt();});t1.start();t2.start();}
}volatile和synchronized区别
volatile保证的是内存可见性synchronized保证的是原子性也可以保证内存可见性
会出现线程安全
// 会出现线程安全无法保证最后结果是 100000
static class Counter {volatile public int count 0;void increase() {count;}}public static void main(String[] args) throws InterruptedException {final 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);
}使用synchronized加锁去掉volatile给t1的循环内部加锁并借助counter对象加锁
static class Counter {public int flag 0;
}public static void main(String[] args) {Counter counter new Counter();Thread t1 new Thread(() - {while (true) {synchronized (counter) {if (counter.flag ! 0) {break;}}// do nothing}System.out.println(循环结束!);});Thread t2 new Thread(() - {Scanner scanner new Scanner(System.in);System.out.println(输入一个整数:);counter.flag scanner.nextInt();});t1.start();t2.start();
}2. wait 和notify
由于线程之间是抢占式执行的因此线程之间执行的先后顺序是随机的。
wait() / wait(long timeout): 让当前线程进入等待状态.notify() / notifyAll(): 唤醒在当前对象上等待的线程.这些都是Object方法
2.1 wait() --使当前线程进行等待释放当前锁满足一定条件时被唤醒重新尝试获取这个锁
wait做的事情
使当前执行代码的线程进行阻塞等待把线程放到等待队列中释放当前锁满足一定条件时被唤醒重新尝试获取这个锁
wait() 要搭配synchronized来使用脱离synchronized使用wait会直接抛出异常。即要先有锁才能调用wait且对象必须是同一对象。 wait结束等待的条件
其他线程调用该对象的notify方法wait等待时间超时 wait(long timeout)来指定等待时间其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
wait 被唤醒后也要重新参与锁竞争。
public class ThreadDemo24 {public static void main(String[] args) throws InterruptedException {Object object new Object();synchronized (object) {System.out.println(wait 之前);object.wait();System.out.println(wait 之后);}}
}2.2 notify()–是唤醒等待的线程
使用的哪个对象就是唤醒哪个对象的wait如果两个wait是同一个对象调用的随机唤醒其中一个而notifyAll 唤醒这个对象所有等待的线程。
下面代码执行的过程
t1 执行起来之后就会立即拿到锁并且打印t1 wait之前进入wait方法释放锁阻塞等待t2 执行起来之后先进行sleep这个sleep作用是让 t1 能够先拿到锁t2 sleep结束之后由于t1是wait状态锁是释放的t2 就能拿到锁接下来打印 t2 notify之前执行notify操作即唤醒t1此时t1就从WAITING状态恢复回来但是由于t2 此时还没有释放锁t1 WAITING恢复之后尝试获取锁就可能会出现一个小小的阻塞这个阻塞时由于锁竞争引起的。t2 执行完t2 notify之后释放锁t2 执行完毕t1 的wait就可以获取锁继续执行打印t1 wait之后。
public class ThreadDemo25 {public static void main(String[] args) {Object locker new Object();Thread t1 new Thread(() - {synchronized (locker) {System.out.println(t1 wait之前);try {// 释放锁阻塞等待locker.wait(); // 死等//locker.wait(100); 带有超时的等待ms 如果这个时间内没有进行notify就不等} catch (InterruptedException e) {e.printStackTrace();}System.out.println(t1 wait之后);}});Thread t2 new Thread(() - {try {// 如果sleep写到synchronized外面的话由于t1和t2执行顺序不确定就可能t2先拿到锁// t1 没执行到 wait t2就先notifyThread.sleep(5000); // 让t1先拿到锁// 由于locker.wait()锁是释放的t2就能拿到锁synchronized (locker) {System.out.println(t2 notify 之前);// 唤醒t1t1从WAITING 状态恢复过来// 由于t2此时还没有释放锁t1恢复之后尝试获取锁就可能出现锁竞争从而导致阻塞locker.notify();System.out.println(t2 notify 之后);}} catch (InterruptedException e) {e.printStackTrace();}});t1.start();t2.start();}
}2.3 wait 和sleep直接的区别
wait提供了一个带有超时的版本sleep也是能指定时间都是时间到就继续执行解除阻塞。wait 和sleep 都可以被提前唤醒wait通过notifysleep通过interrupt唤醒使用wait 在不知道要等多久的前提下使用所谓超时时间即兜底的而使用sleep要知道具体等多久能提前唤醒但是是异常唤醒。