前言
本篇博客博主将详细地介绍线程安全产生的原因及怎么解决
一.线程安全产生的原因
1.1线程安全的概念
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,那么我们说这个程序是安全的
1.2线程不安全的原因
1.线程调度是随机的,这是线程安全问题的罪魁祸首,随机调度使一个程序在多线程环境下,执行顺序存在很多变数;
2.修改共享数据,多个线程修改同一个变量;
3.原子性。
注:什么是原子性呢?
注:不保证原子性会给多线程带来什么问题?
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果可能就是错误的。
1.3线程安全的实例
代码如下:
// 此处定义⼀个 int 类型的变量
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
count++;
}
});
t1.start();
t2.start();
// 如果没有这俩 join, 肯定不⾏的. 线程还没⾃增完, 就开始打印了
t1.join();
t2.join();
// 预期结果应该是 10w
System.out.println("count: " + count);
}
结果显而易见的不是,结果会随机产生,我们接着往下看。
二.线程不安全问题的解决方案
加锁
代码如下:
// 此处定义⼀个 int 类型的变量
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
// 对 count 变量进⾏⾃增 5w 次
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
t1.start();
t2.start();
// 如果没有这俩 join, 肯定不⾏的. 线程还没⾃增完, 就开始打印了.
t1.join();
t2.join();
// 预期结果应该是 10w
System.out.println("count: " + count);
}
2.1 synchronized关键字
synchronized关键字也被称为 监视器锁 monitor lock
2.1.1synchronized的特性
1.互斥
synchronized会起到互斥效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待
进入synchronized修饰的代码块,相当于加锁;
退出synchronized修饰的代码块,相当于解锁。
注:
synchronized用的锁是存在Java对象里面的;
Java中的 synchronized是可重入锁,因此没有上面的问题
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
synchronized (locker) {
count++;
}
}
}
在可重入锁的内部,包含了“线程持有者”和“计数器”两个信息:
2.1.2 synchronized使用实例
synchronized本质上要修改对象的“对象头”,从使用角度来看,synchronized也势必要搭配一个具体的对象来使用
1)修饰代码块:明确指定锁哪个对象
锁任意对象
public class SynchronizedDemo {
private Object locker = new Object();
public void method() {
synchronized (locker) {
}
}
}
锁当前对象
public class SynchronizedDemo {
public void method() {
synchronized (this) {
}
}
}
2)直接修饰普通方法:锁的SynchronizedDemo对象
public class SynchronizedDemo {
public synchronized void methond() {
}
}
3)修饰静态方法:锁的SynchronizedDemo类的对象
public class SynchronizedDemo {
public synchronized static void method() {
}
}
注:我们重点理解,synchronized锁的是什么,两个线程竞争同一把锁,才会产生阻塞等待
两个线程分别尝试获取不同的锁,不会产生竞争。
尾语
这篇博客到这里就结束啦,希望可以给大家带来帮助~~