之前我们说了volatile,但是volatile和sychronized有着本质的区别,volatile能保证内存的可见性,sychronized可以保持原子性
线程的安全性问题:
什么是线程安全性:在一段代码中,多线程并发执行,产生的bug
产生原因:1.操作系统对线程的调度是随机的,遵循抢占式执行,
2.多个线程同时修改一个变量
3.修改操作不是原子的
4.内存的可见性问题->编译器优化
5.指令重排序
原子性:一个操作要么全部执行,要么完全不执行,不会被分割,中途不会被其他线程插队,这叫做原子性。
死锁产生的原因:
1.互斥
2.不可剥夺/不可抢占
3.请求和保持
4.循环等待
解决死锁的方法,只能是从3和4进行下手,避免锁嵌套,约定加锁的顺序
JMM(java内存模型)
是多线程读写内存的规则,多线程之间如何进行读写,内存什么时候对其他线程可见,编译器优化什么时候进行,什么时候是允许的,什么时候是被禁止的
线程的工作内存
线程的工作内存,就相当于线程自己的小仓库,用来保存该线程使用的数据副本,每个线程都有一个主内存,当进行读取变量操作的时候,会把主内存中的数据拷贝自己的工作内存中,后续其他线程在自己的工作内存在修改变量的时候,也是先修改自己的工作内存中的变量,再更新主内存中的变量,但是第一个线程中仍然在读自己工作内存中的变量,不知道主内存中该变量已经被修改
wait / notify
使用来协调线程之间调度的执行顺序的
join和wait的区别
join是等该线程完全执行完后才结束等待
wait是等到另一个线程执行完逻辑,执行到 notify 才结束等待,不需要另一个线程完全执行完
public class Demo21 {
public static void main(String[] args) {
Object locker=new Object();
Thread t1=new Thread(()->{
System.out.println("wait之前");
synchronized (locker){
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("wait之后");
});
Thread t2=new Thread(()->{
Scanner scanner=new Scanner(System.in);
System.out.println("请输入你想打印的内容");
System.out.println(scanner.nextInt());
synchronized (locker){
locker.notify();
}
});
t1.start();
t2.start();
}
}

注:wait和notify是针对的同一个对象,并且保证wait的执行逻辑在notify之前


并且有多个线程在wait,notify只能随机的唤醒一个线程,一个notify只能唤醒一个wait
public class Demo23 {
public static void main(String[] args) throws InterruptedException {
Object locker=new Object();
Thread t1=new Thread(()->{
synchronized (locker){
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t1 执行完wait");
});
Thread t2=new Thread(()->{
synchronized (locker){
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 执行完wait");
});
Thread t3=new Thread(()->{
synchronized (locker){
locker.notify();
}
System.out.println("解除第一个wait");
synchronized (locker){
locker.notify();
}
System.out.println("解除第二个wait");
});
t1.start();
t2.start();
Thread.sleep(1000);
t3.start();
}
}
也可以使用notifyAll,释放所有关于该锁对象的wait
synchronized (locker){
locker.notifyAll();
}
System.out.println("释放完所有的锁");
wait也可以设置等待时间
![]()
练习一下,一共有三个线程,分别只能打印A,B,C
输出案例:
ABC
ABC
.....
public static void main(String[] args) throws InterruptedException {
Object locker1=new Object();
Object locker2=new Object();
Object locker3=new Object();
Thread t1=new Thread(()->{
for (int i = 0; i < 10; i++) {
synchronized (locker1){
try {
locker1.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("A");
synchronized (locker2){
locker2.notify();
}
}
});
Thread t2=new Thread(()->{
for (int i = 0; i < 10; i++) {
synchronized (locker2){
try {
locker2.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("B");
synchronized (locker3){
locker3.notify();
}
}
});
Thread t3=new Thread(()->{
for (int i = 0; i < 10; i++) {
synchronized (locker3){
try {
locker3.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("C");
synchronized (locker1){
locker1.notify();
}
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
// 确保三个线程都已经进行等待
synchronized (locker1){
locker1.notify();
}
}

单例模式
单例模式是确保一个类在整个系统中只有一个实例对象,,通过私有的构造方法,静态实例,静态的工厂方法来构建,通常使用的创建模式有饿汉式,懒汉式+双重检查锁,优点,节省资源,只创建一个实例对象,避免重复创建,缺点:延展性不好,很难拓展功能,如果变量不小心被修改,能难找出变量被修改的位置,可能导致全局变量被污染
使用饿汉模式实现单例模式
//饿汉模式创建单例模式
class Singleton{
// 在启动的时候就创建实例对象
private static Singleton instance=new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}
}
public class Demo24 {
public static void main(String[] args) {
Singleton singleton=Singleton.getInstance();
Singleton singleton2=Singleton.getInstance();
System.out.println(singleton2==singleton);
}
}
使用懒汉模式+双重检查锁
//使用懒汉模式创建单例模式
class Singleton1{
private static volatile Singleton1 instance=null;
public static Singleton1 getInstance(){
if (instance==null){
synchronized (Singleton1.class){
if (instance==null){
return instance=new Singleton1();
}
}
}
return instance;
}
public synchronized static Singleton1 getInstance2(){
if (instance==null){
return instance=new Singleton1();
}
return instance;
}
private Singleton1(){
}
}
public class Demo25 {
public static void main(String[] args) {
Singleton1 singleton1=Singleton1.getInstance();
Singleton1 singleton2=Singleton1.getInstance();
System.out.println(singleton2==singleton1);
}
}


volatile可以确保我们每次操作,都是都内存
确保我们的读取或者修改的操作,不会触发重排序
阻塞队列
其性质就是一种更加复杂的队列
当队列为空,进行取操作,就会进行阻塞,直到其他线程往里面添加元素为止
当队列为满,进行加操作,就会进行阻塞, 直到其他线程在队列中取出元素为止
public class Demo26 {
public static void main(String[] args) throws InterruptedException {
BlockingQueue queue=new ArrayBlockingQueue(100);
for (int i = 0; i < 100; i++) {
queue.put("aaa");
}
System.out.println("队列已经满了");
queue.put("bbb");
System.out.println("测试是否已经阻塞了");
}
}

当前已经队列满了,如果再加就会进行阻塞不会执行
System.out.println("测试是否已经阻塞了");

减低耦合度


削峰填谷



但是引入了阻塞队列也会付出很大的代价,导致整体的结构变的更加的复杂,效率会收到影响,需要更多的机器进行部署
简单实现一下削峰填谷的模式
//设置两个线程,一个是生产者线程,一个是消费者线程,一个阻塞队列
public class Demo27 {
public static void main(String[] args) {
BlockingQueue queue=new LinkedBlockingQueue(100);
Thread producer=new Thread(()->{
int n=0;
while (true){
try {
queue.put(n);
// Thread.sleep(1000);
System.out.println("放入元素:"+n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread customer=new Thread(()->{
while (true){
try {
Thread.sleep(1000);
Integer n=(Integer) queue.take();
System.out.println("取出的元素:"+n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
producer.start();
customer.start();
}
}
生产者流量激增存储到阻塞队列中,然后消费者按自己的速度慢慢的进行消费

使用数组模拟实现一个阻塞队列的简单功能
//使用数组模拟实现一个阻塞队列
class MyBlockingQueue{
private String[] data=null;
private int head=0;
private int tail=0;
private int size=0;
// 设置队列的容量
public MyBlockingQueue(int capacity){
data=new String[capacity];
}
// 放元素
public void put(String elem) throws InterruptedException {
while (size>=data.length){
// 元素满了,需要进行阻塞
this.wait();
}
data[tail]=elem;
tail++;
if(tail>=data.length){
tail=0;
}
// tail=(tail+1)%data.length
size++;
this.notify();
}
// 拿元素
public String take() throws InterruptedException {
while(size==0){
// 队列中没有元素了,需要进行阻塞
this.wait();
}
String ret=data[head];
head++;
if(head>=data.length){
head=0;
}
size--;
this.notify();
return ret;
}
}
public class Demo28 {
public static void main(String[] args) {
MyBlockingQueue queue=new MyBlockingQueue(100);
Thread producer=new Thread(()->{
int n=0;
while (true){
try {
queue.put(n+"");
Thread.sleep(1000);
System.out.println("放入元素:"+n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread customer=new Thread(()->{
while (true){
// Thread.sleep(1000);
String take = null;
try {
take = queue.take();
System.out.println("取出的元素:"+take);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
producer.start();
customer.start();
}
}
阻塞的逻辑:
当数组满了,直接wait,有到take的中取出元素,执行到notify,解除wait
当数组为空,直接wait,有到put的中添加元素,执行到notify,解除wait

在这里的设计条件用while不用if的原因:
正常来说wait会被notify唤醒,但是在特殊的情况下,wait也会被interrupt给唤醒中断,如果使用if作为判断,就会有被提前唤醒导致继续往下执行时候出现bug,所以我们使用while,是为了二次判断,判断这里条件是否满足,如果满足继续进行等待,wait唤醒前判断一次,wait唤醒后判断一次


被折叠的 条评论
为什么被折叠?



