这里写自定义目录标题
引言
🍊线程安全产生的原因
🍊什么是线程安全
在操作系统中,因为线程的调度是随机的(抢占式执行),正是因为这中随机性,才会让代码中产生很多bug 如果认为是因为这样的线程调度才导致代码产生了bug,则认为线程是不安全的, 如果这样的调度,并没有让代码产生bug,我们则认为线程是安全的
这里的安全指代的是代码中有没有产生bug,与我们平常认为的安全是两种截然不同的概念,我们所熟知的安全是由黑客造成的,他们会不会侵入你的电脑💻,攻击你的计算机,这是我门不能够制止的,我们所要做的就是让代码不会产生bug.
🍊线程安全实例
栗子 🌰 :使用两个线程,对同一个整型变量进行自增操作,每个线程自增五万次,看最后的结果,代码如下
class Counter{
int count = 0;
public void increase(){
count++;
}
}
public class Demo1 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.count);
}
}
运行结果如下
从 ⬆️ 面的运行结果,我们不难发现,每次的运行结果都是不同的值都是在50000~100000之间的值,这是为什么呢?
在此,我们需要了解一下count++,是如何在CPU上运行的?
🍊产生线程不安全的原因总结
- 线程是抢占式的,线程之间的调度充满随机性.[线程不安全的万恶之源,而且这是无法避免的,程序猿无可奈何😮💨]
- 多个线程对同一个变量进行修改~~(多个线程对不同的变量进行修改是没问题的,多个线程对同一个变量进行读操作也是没问题的),这是可以控制的,通过对代码的结构进行修改,使不同线程操作不同的变量
- 针对变量的操作不是原子性的,这就是上面的load->add->save等指令,这些操作是绑定的,原子性的,把多个指令看成一个.这需要加锁才能解决
- 内存可见性(编译器优化)
这是很难理解的,我们需要举一个具体的🌰
对于这种情况我们可以使用synchronized和volatile关键字解决,我们在此先不做详解介绍,一会在分析
- 指令重排序
系统不按照我们所给的命令执行程序,而是为了提高效率,改变指令的执行顺序,这样就会产生bug,这也是编译器认为程序猿瞎b写代码的案列(编译器优化)
🍉线程不安全的解决方案(加锁)
我们主要对第2,3条原因进行干涉,这就需要加锁的方法
🍉加锁的概念
加锁
那么,讲到这里就会有人问,那么,上了锁之后,并发编程不就是很鸡肋了吗?
这是不对的!!!
🍉加锁的好处与意义
🍉怎么进行加锁
🍉 synchronized关键字
1. 使用关键字synchronized****(一定要记住怎么读和拼写!!!)
使用🌰
class Counter{
int count = 0;
synchronized public void increase(){
count++;
}
}
public class Demo1 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.count);
}
}
运行结果
- 如何使用synchronized
synchronized的本质操作就是修改了Object对象中的对象头里的一个标记
synchronized public void increase(){
count++;
}
class Counter{
int count = 0;
public void increase(){
synchronized(this){
count++;
}
}
}
synchronized public static void func(){}
相当于
public static void func(){
synchronized (Counter.class){
}
}
以上就是synchronized的使用方法!!!(单词一定要会读,会拼)
🍉volatile关键字
class Counter{
volatile int count = 0;
public void increase(){
count++;
}
public static void func(){
synchronized (Counter.class){
}
}
}
public class Demo1 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.count);
}
}
从上面的代码中,我们得知,volatile不能保证线程的安全性.
🍉synchronized与volatile的区别
那么,有人会问,既然synchronized的功能更全,直接无脑用就好了?
❌ 这是不对的!!!
所以说具体问题具体分析,需要知道你需要干什么,在选择相应的关键字来加锁