Java并发编程--并发编程线程基础(二)

1.关于多线程并发编程的问题

(1). 什么是多线程并发

​  并发是指同一个时间段内多个任务同时(宏观上的同时,微观上是时间片划分)都在进行,并且都没有执行结束.

​  并行是指在单位时间内多个任务同时(宏观微观上的同时)都在执行

(2). Java中的线程安全问题

​  共享资源:可被多个线程所持有或同时访问的资源

​  线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施时,导致出现脏数据或者其他不可预见的结果的问题

(3). Java中共享变量的内存可见性问题

​  Java内存规定,所有的变量都放在主内存,线程使用变量时从主内存复制一份放入工作内存,读写时使用的是自己工作内存里的变量。

​  当线程A与线程B同时处理共享变量X时则会出现问题,使用volatile关键字可以解决

(4). Java中的原子性操作

​  原子性操作,是指执行一系列操作是,这些操作要么全部执行要么全部不执行.(中间不能出现线程轮换)

​  最简单的方法就是使用synchronized关键字进行同步.

2.Java中关于多线程的关键字

(1). Java中的synchronized关键字

 1). synchronized关键字介绍

​  synchronized块是Java提供的一种原子性内置锁(是一种排它锁),Java中的每个对象都可以把它当做一个同步锁来使用(内部锁,监视器锁).线程的执行代码在进入synchronized代码块前会自动获取这个内部锁,这时其他线程访问synchronized代码块时就会得不到这个内部锁,就会被阻塞挂起,释放这个内部锁的三种方法:1.正常的退出synchronized代码块。2.抛出异常退出代码块。3.同步代码块内调用了同步锁的wait()系列方法.

 2). synchronized的内存语义

​  进入synchronized块会将代码块内用到的变量从工作内存清空,这样在块中使用变量时就会从主内存中获取

​  退出synchronized块会将工作内存中的变量值刷新到主内存

​  这也是加锁和释放锁的语义

(2). Java中的volatile关键字

​ 这是一种弱形式的同步,确保修改volatile变量时能对其他线程立即可见.

​ 写入volatile变量时立即刷新到主内存,读取volatile变量时先从主内存刷新工作内存的变量再读取.

什么时候使用volatile关键字?

  • 写入变量值不依赖变量的当前值时
  • 读写变量值时没有加锁时

3. Java中的CAS操作

​  使用锁的可以做到原子操作,但是当一个线程没有获取到锁时会挂起.而volatile只能保证共享变量的可见性,不能保证原子性问题.

​  CAS即Compare and Swap,是JDK提供的非阻塞原子性操作.

​  boolean compareAndSwap(Object obj, long valueOffset, long expect, long update):意思是比较并交换,如果obj对象中内存偏移量为valueOffset的变量值为expect,则使用update替换expect

​  JDK中的AtomicStampedReference类给每个变量的状态值都匹配了时间戳,从而避免ABA问题

4. Unsafe类

​  JDK的rt.jar包中的Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法.

5. Java指令重排序

​  Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在的数据依赖性的指令重排列.但是在多线程下就会出现问题.

​  单线程中,重排序对程序的运行结果一定是没有影响的,但是JVM仅仅会考虑运行线程的重排列,并不会考虑会不会对其他并发的线程产生的影响(也做不到这一点,并发都是随机的).

​  对于多线程下的指令重排序,只需要在关键的变量上声明为volatile就可以避免重排序和内存可见性问题.

​  volatile变量将指令重排序的指令序列进行分隔,指令重排序只能在分隔之间进行.

6. 伪共享

(1). 什么是伪共享

​  为了解决计算机内存和CPU直接运行速度差距过大的问题,CPU和主内存之间会设置一级或者多级高速缓存存储器(Cache).Cache内部是按行进行存储的,一行通常为2的幂次数字节.

​  当CPU访问某个变量时,先在Cache中查找,如果没有,去主内存中查找,并且将目标对象所在内存区域的一个Cache行大小的内存全部缓存到Cache中.

​  单线程状态下,这样可以减少对象的复制,可以提高运行速度.但是在**多线程状况下,多个线程可能同时访问Cache中的一行,但只有一个线程能操作缓存行.**这样就会让性能有所下降,这就是伪共享

(2). 为何会出现伪共享

​  因为多个变量被放入了一个缓存行中,并且多个线程同时去写入一个缓存行中的不同变量.

(3). 如何避免伪共享

​  一个变量一个缓存行:JDK 8之前手动填充字段(使一个对象刚刚好占用一个缓存行的内存),JDK 8提供了@Contended注解,但@Contended注解默认情况下只能用于Java核心类.

7. 锁的概述

(1). 乐观锁和悲观锁

​  悲观锁:指对数据被外界修改持保守态度,在数据被处理前先对数据进行加锁.实现往往依靠锁机制.

​  乐观锁:认为数据一般情况下不会造成冲突,所以访问前不会加锁.在数据提交时,对数据冲突进行检测,并进行处理.客观所不会使用锁机制,一般添加标记字段(版本号).不会产生任何死锁…

(2). 公平锁和非公平锁

​  根据线程获取锁的抢占机制分为公平锁和非公平锁.

​  区别就是公平锁在获取锁的时候是根据请求的顺序(挂起)来分配锁的,而非公平锁完全随机.

​  公平锁会带来性能开销.

(3). 独占锁和共享锁

​  区别在于能否被多个线程持有.

​  独占锁:只能有一个线程持有,是一种悲观锁.

​  共享锁:能被多个线程持有,是一种乐观锁.

(4). 可重入锁

​  当一个线程占有一个锁之后,如果还能再次获取同一个锁多次,那么这个锁是可重入锁.

​  synchronized内部锁是可重入锁(内部关联了一个计数器).

(5). 自旋锁

​  在获取锁时,发现被其他线程占有,并不会立即阻塞挂起,而是多次尝试获取锁,指定次数获取后还是没有获取到锁,才会被挂起.

发布了141 篇原创文章 · 获赞 47 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41596568/article/details/104055133