并发编程(多线程,JMM,可见性,有序性,原子性,volatile关键字,锁,JUC)

回顾线程基本内容

程序:用某种语言编写的,为实现特定功能的静态程序,安装在硬盘上

进程:运行中的程序,是操作系统分配内存空间的单位

线程:线程是进程中的最小执行单元,是CPU调度单位,线程依赖于进程

创建线程

1.继承Thread类,重写run()

2.类 实现Runable接口,重写run()

创建Thread类的对象,为其分配任务

3.类 实现Callable接口,重写call() 有返回值,可以抛出异常

创建Thread类的对象,为其分配任务

常用方法

run() call() start()

设置名字 设置优先级

线程状态 start() 就绪状态

​ join() sleep() 阻塞状态

​ yield() 线程让步 运行状态,主动让出----> 就绪状态
stop() 强行中断线程(此方法为作废过期方法)
interrupt() 中断线程

在这里插入图片描述

多线程

Java 是最先支持多线程的开发的语言之一,

Java 从一开始就支持了多线程

能力。由于现在的 CPU 已经多是多核处理器了,是可以同时执行多个线程的.

多线程优点

多线程技术使程序的响应速度更快 ,可以在进行其它工作的同时一直处于活动

状态,程序性能得到提升.

性能提升的本质 就是榨取硬件的剩余价值(硬件利用率).

多线程问题

多线程带来的问题是什么?

安全性(访问共享变量),性能(cpu切换开销等)

java并发编程

并发与并行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0nJZR6x-1642328944308)(C:\Users\云\AppData\Roaming\Typora\typora-user-images\1642326319274.png)]

java内存模型(JMM)

注意JMM是java内存模型不是jvm模型

java多线程在工作时,先将主内存中的数据读到线程工作内存(缓存),然后在工作内存中对数据进行操作,操作完成后将数据写回到主内存


产生一个可见性问题?

B线程中看不到A线程中操作过的数据
在这里插入图片描述

操作系统可能会对指令的执行先后顺序进行重新排列执行

带来有序性问题
在这里插入图片描述

线程切换到来原子性(不可拆分)问题

i=1; i=1;

i++ -->i=i+1 i++ 2

2

++ 分为先计算后赋值 ++操作不是原子操作,CPU在执行时可能会分为两步执行
在这里插入图片描述

并发编程核心问题

并发编程核心问题–可见性,原子性,有序性

线程本地缓存,导致可见性问题

编译优化,重排指令,带来有序性问题

线程切换执行,会导致原子性问题

怎样解决问题?

volatile关键字

被volatile修饰后的共享变量:1.在一个线程中操作后可以保证在另一个线程中立即可见2.禁止优化指令重新排序3.不能保证对变量操作的原子性

i++不是原子操作 怎么解决 加锁解决

如何保证原子性

“同一时刻只有一个线程执行”我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的那么就都能保证原子性了。

加锁

Lock ReentrantLock

synchronized 也可以保证可见性和有序性

原子类

java.util.concurrent包

AtomicInteger 通过volatile+CAS实现原子操作的

getAndIncrement(); 代替i++是安全的

CAS可理解为一种自旋思想

CAS(Compare-And-Swap)比较并交换 适用于低并发情况下

CAS是乐观锁的一种实现方式,他采用的是自旋的思想,是一种轻量级的锁机制

乐观锁指的是一种不加锁就可以解决的方式(即没有锁)

自旋锁(轮询)一直不停的循环检查

CAS 包含了三个操作数:

①内存值 V

②预估值 A (比较时,从内存中再次读到的值)

③更新值 B (更新后的值)

当且仅当预期值 A==V,将内存值 V=B,否则什么都不做。

这种做法的效率高于加锁,当判断不成功不能更新值时,不会阻塞,继续获得 cpu

执行权,继续判断执行

这种做法不会导致线程阻塞,因为没有加锁

缺点:

在高并发情况下,采用自旋方式不断循环,对CPU占用率高

可能会产生ABA问题 可以为类添加版本号的方式区别值是否被改变过

ABA 问题

ABA 问题,即某个线程将内存值由 A 改为了 B,再由 B 改为了 A。当另外一个

线程使用预期值去判断时,预期值与内存值相同,误以为该变量没有被修改过而

导致的问题。

解决 ABA 问题的主要方式,通过使用类似添加版本号的方式,来避免 ABA 问题。

如原先的内存值为(A,1),线程将(A,1)修改为了(B,2),再由(B,2)

修改为(A,3)。此时另一个线程使用预期值(A,1)与内存值(A,3)进行

比较,只需要比较版本号 1 和 3,即可发现该内存中的数据被更新过了。

JUC 类

hashMap是线程不安全的不能并发操作

hashTable是线程安全的有synchronized修饰,将锁直接加到put()上,效率低,相当于吧整个hash表锁住了,用在低并发情况下可以,独占锁

ConcurrentHashMap

ConcurrentHashMap 是线程安全的,采用锁分段机制,并没有将整个hash表锁住,但是jdk8之后没有使用分段锁(给每个位置创建一个锁标志对象),采用的是CAS思想+synchronized来实现

插入时检测hash表对应的位置是否是第一个节点,如果是,采用CAS机制(循环检测)向第一个位置插入数据

如果此位置已经有值,那么就以第一个Node对象为锁标志进行加锁,使用的是synchronized实现

public class HashMapDemo {
    
    

    /*
       HashMap是线程不安全的,不能并发操作的
       ConcurrentModificationException  并发修改异常   遍历集合,并删除集合中的数据

       Hashtable 是线程安全的 public synchronized V put(K key, V value)-->独占锁
            锁直接加到了put方法上,锁粒度比较大,效率比较低
            用在低并发情况下可以

       Map<String,Integer> map = Collections.synchronizedMap(new HashMap<>());
       ConcurrentHashMap
     */
    public static void main(String[] args) {
    
    

        ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        //模拟多个线程对其操作
        for (int i = 0; i < 20; i++) {
    
    
                 new Thread(
                     ()->{
    
    
                       map.put(Thread.currentThread().getName(), new Random().nextInt());
                         System.out.println(map);
                     }
                 ).start();
        }

    }
}

猜你喜欢

转载自blog.csdn.net/crraxx/article/details/122526635
今日推荐