当前位置:   article > 正文

JavaEE初阶——线程安全

JavaEE初阶——线程安全

目录

一.引言

二.线程安全

2.1  什么是线程安全?

2.2 线程不安全的原因

1的解释:

2的解释:

3的解释:

4的解释:

三.如何解决线程不安全

3.1  synchronized关键字

​编辑 3.2  synchronized关键字特性

1.互斥

2.可重入

3.3  synchronized关键字使用操作

1.修饰代码块

锁任意对象

2.锁当前对象 

2.直接修饰普通方法

3.修饰静态方法 

四.Java标准库中的线程安全类

五.总结


一.引言

  在Java中,线程安全是十分重要的。因为我们要让代码编写出来后锁产生的结果与我们的想法是一致的。所以本篇文章就为大家讲解一下线程安全的相关内容。

二.线程安全

2.1  什么是线程安全?

  如果多线程环境下代码运行的结果是符合我们的预期的,即在单线程环境下应该获得的结果,则说这个程序是线程安全的。

2.2 线程不安全的原因

1.线程的调度是随机的,抢占式执行

2.当前代码,多个线程同时修改一个变量

3.线程对于变量的修改不是“原子”

4.内存可见性问题

5.指令重排序问题(4,5之后会学习到,这里就暂且不讨论)

1的解释:

在程序运行的过程中,如果不加其他代码,计算机是不会知道哪个线程先进行,哪个后进行,执行的顺序是随机的,这就是抢占式执行。

2的解释:

对于一个变量例如count,线程1对其++,线程2也对其++,这样会导致返回值出错

  1. public class Demo6 {
  2. private volatile static int count=0;
  3. public static void main(String[] args) throws InterruptedException {
  4. Thread t1=new Thread(() ->{
  5. for (int i = 0; i < 50000; i++) {
  6. count++;
  7. }
  8. });
  9. Thread t2=new Thread(()->{
  10. for (int i = 0; i < 50000; i++) {
  11. count++;
  12. }
  13. });
  14. t1.start();
  15. t2.start();
  16. t1.join();
  17. t2.join();
  18. System.out.println("count="+count);
  19. }
  20. }

按理说结果应该是100000,但执行后结果: 

这就是多个线程改变同一个变量的结果,造成的线程不安全。

3的解释:

对于n++其实是三步操作:

1.从内存把数据读到CPU

2.进行数据更新

3.将更新的数据写回到CPU

原子性就是说当一个线程在执行的过程中,其他线程不能中途插进来。

4的解释:

假设有以下三件事要做:

1.去楼下拿快递

2.去客厅接杯水

3.去楼下拿外卖

如果按照1-2-3执行,效率会变低,所以JVM可以优化此操作,变为1-3-2,少去一次楼下,这就是指令重排序。

编译器对于指令重排序的前提是 "保持逻辑不发⽣变化". 这⼀点在单线程环境下⽐较容易判断, 但是
在多线程环境下就没那么容易了, 多线程的代码执⾏复杂程度更⾼, 编译器很难在编译阶段对代码的
执⾏效果进⾏预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价.

三.如何解决线程不安全

3.1  synchronized关键字

synchronized的作用就是加锁,解锁

上面count++的例子,如果加上了锁,那么结果就是正确的。

  1. public class Demo6 {
  2. private volatile static int count=0;
  3. public static void main(String[] args) throws InterruptedException {
  4. Object object=new Object();
  5. Thread t1=new Thread(() ->{
  6. synchronized (object){
  7. for (int i = 0; i < 50000; i++) {
  8. count++;
  9. }
  10. }
  11. });
  12. Thread t2=new Thread(()->{
  13. synchronized (object){
  14. for (int i = 0; i < 50000; i++) {
  15. count++;
  16. }
  17. }
  18. });
  19. t1.start();
  20. t2.start();
  21. t1.join();
  22. t2.join();
  23. System.out.println("count="+count);
  24. }
  25. }

代码结果:

 3.2  synchronized关键字特性

1.互斥

synchronized会起到互斥的效果,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待。

进入synchronized修饰的代码块,相当于加锁。

推出synchronized修饰的代码块,相当于解锁。

“阻塞等待”的定义:

针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试进⾏加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程, 再来获取到这个锁。

注意:

假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B ⽐ C 先来的, 但是 B 不⼀定就能获取到锁, ⽽是和 C 重新竞争, 并不遵守先来后到的规则.

2.可重入

synchronized同步块对同一线程来说是可重入的。

不可重入是什么意思?

简单来说就是一个线程已经加上锁了,但是它又加上了同一个锁,它既释放锁,又要再一次加锁,就导致成为了死锁,但synchronized没有这样的问题,所以它是可重入锁。

3.3  synchronized关键字使用操作

1.修饰代码块

锁任意对象
  1. public class Demo7 {
  2. Object object=new Object();
  3. public void method(){
  4. synchronized (object){
  5. }
  6. }
  7. }
2.锁当前对象 
  1. public class Demo8 {
  2. public void method(){
  3. synchronized (this){
  4. }
  5. }
  6. }

2.直接修饰普通方法

  1. public class Demo9 {
  2. public synchronized void method(){
  3. }
  4. }

3.修饰静态方法 

  1. public class Demo10 {
  2. public synchronized static void method(){
  3. }
  4. }

四.Java标准库中的线程安全类

 Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, ⼜没有任何加锁措施。

不安全 如:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StirngBuilder

安全 如:Vector,HashTable,ConcurrentHashMap,StringBuffer(都涉及带锁操作)

虽然存在没有加锁的,但是不涉及“修改”,仍然是线程安全的:

String

五.总结

本篇文章简单地给大家介绍了线程安全的意义,线程不安全的原因,以及其中的一种解决方法,欢迎大家在评论区讨论,感谢大家观看!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号