线程安全是什么?什么条件下的东西是线程安全的?不安全会带来什么后果?
- 线程安全是怎么一回事?
- 为什么会有这样的一些线程安全的问题?
比如现在有个线程A 线程B 变量X =1 Y=1,AB同时读取 x为1 y为1 后续 A把x改为5 B把Y改为6 但是这个时候 A以为y还是为1 后续在做业务处理的就会产生计算错误!
多个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。 不进行恰当的控制,会导致线程安全问题。
根据并发性质来分析一下:
-
原子性:对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
-
可见性: 对于可见性,Java提供了volatile关键字来保证可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。另外,通过synchronized和Lock 也能够保证可见性synchronized 和 Lock 能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。
3.有序性: Java 允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执 行,却会影响到多线程并发执行的正确性。可以通过 volatile 关键字来保证一定的“有序 性”(synchronized 和Lock也可以)。
- 那些手段可以解决这些线程安全的问题?
-
多个线程过来 one by one 的处理 这种效率最高 但是中间会有脏数据 线程不安全
-
或者是串行化
以上两点肯定是不可行的
在这个两点直接 正好是我们要解决的问题!
示例代码:1
public class Counter { //多线程计算问题 private int sum = 0; private Object lock = new Object(); public void incr() { // synchronized(lock) { sum = sum + 1; // } } public int getSum() { return sum; } public static void main(String[] args) throws InterruptedException { int loop = 10_0000; // test single thread Counter counter = new Counter(); for (int i = 0; i < loop; i++) { counter.incr(); } System.out.println("single thread: " + counter.getSum()); // test multiple threads final Counter counter2 = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < loop / 2; i++) { counter2.incr(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < loop / 2; i++) { counter2.incr(); } }); t1.start(); t2.start(); Thread.sleep(1000); System.out.println("multiple threads: " + counter2.getSum()); } }输出的结果可以看到这是不安全的
single thread: 100000 //单线程下可以正常计算 multiple threads: 70909 //多线程下就不是安全的
示例代码 2
package java0.conc0301.sync; public class Counter { private int sum = 0; private Object lock = new Object(); //加入同步代码块 public synchronized void incr() { // synchronized(lock) { sum = sum + 1; // } } public int getSum() { return sum; } public static void main(String[] args) throws InterruptedException { int loop = 10_0000; // test single thread Counter counter = new Counter(); for (int i = 0; i < loop; i++) { counter.incr(); } System.out.println("single thread: " + counter.getSum()); // test multiple threads final Counter counter2 = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < loop / 2; i++) { counter2.incr(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < loop / 2; i++) { counter2.incr(); } }); t1.start(); t2.start(); Thread.sleep(1000); System.out.println("multiple threads: " + counter2.getSum()); } }
-
加入同步代码块之后 ,相当于排队一个一个的来 类似于数据库的串行化 但是这样无非就是分成了两个线程 效率又会变得跟单线程一样
输出的结果:
single thread: 100000 multiple threads: 100000
synchronized 的实现(同步代码块)
锁的几种状态:
-
偏向锁:就是该线程占有的锁 还是该线程来做使用的话 会更快一点 因为内存中有了锁的结构 指针。 比如一个人干的很久了,再让他干这个工作会更块一点!
-
轻量级锁:对synchronized 的优化 ,先不悲观的去操作这个锁 而是采用一种Cas方式去获取这个锁,如果发现这个锁被其他的线程占用,再去排队获取这个锁 给他设置为重量级的锁:(乐观锁机制)
-
重量级锁:原本之前的jdk版本 直接给标识 为已占有的标识 (开销比较大一点)
可以放在方法上 可以放在对象上
区别:锁的粒度不同
比如咱们这个业务里面 10行代码 三行需要做同步执行,其他行就可以不做锁 做--锁分离--的操作 目的减少 锁的粒度!
比如代码示例 3:
package java0.conc0301.sync;
public class Counter {
private int sum = 0;
private Object lock = new Object();
//加入同步代码块
public void incr() {
synchronized(lock) {
sum = sum + 1;
}
}
public int getSum() {
return sum;
}
public static void main(String[] args) throws InterruptedException {
int loop = 10_0000;
// test single thread
Counter counter = new Counter();
for (int i = 0; i < loop; i++) {
counter.incr();
}
System.out.println("single thread: " + counter.getSum());
// test multiple threads
final Counter counter2 = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < loop / 2; i++) {
counter2.incr();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < loop / 2; i++) {
counter2.incr();
}
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("multiple threads: " + counter2.getSum());
}
}
再描述一下volatile关键字
-
每次读取数据都强制从主内存中获取
-
使用的场景:单个线程写,多个线程读
-
原则:能不用就不用
-
替代的方案:Atomic原子操作类



