Java 并发编程

一、并发编程三要素

1.原子性
原子,即一个不可再被分割的颗粒。java中指一个或多个操作要么全部执行成功,要么全不成功。

2.有序性
程序执行的顺序按代码的先后顺序(编译器等会对指令重排序)。

3.可见性
当多个线程同时对一个对象访问时,一个线程对对象做了修改,其他线程能立即获得最新的值。

二、线程的五大状态

1.创建状态
当使用new操作符创建一个线程的时候。

2.就绪状态
调用start方法,此时线程还未执行run方法,还在等待CPU的调度。

3.运行状态
CPU开始调度线程,线程开始执行run方法。

4.阻塞状态
线程由于一些原因进入阻塞状态。
例如:调用sleep方法,尝试得到一个锁。

5.死亡状态
run方法执行完或者执行中出现异常。

三、乐观锁和悲观锁

1.乐观锁
每次操作不加锁假设没有冲突去执行某个操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。

2.悲观锁
每次操作都加锁,会造成线程阻塞。

四、线程之间的协作

1.wait/notify/notifyAll
这是Object类的一组方法。
需要注意的是:这三个方法都必须在同步的范围内调用。

  • wait
    阻塞当前线程,等待notify或者notifyAll来唤醒。

  • notify
    只能唤醒一个处于wait的线程。

  • notifyAll
    唤醒所有处于wait的线程。

2.sleep/yield/join
这是一组Thread类的方法。

  • sleep
    让当前线程暂停指定时间,让出CPU使用权,并不会释放锁。
  • yield
    暂停线程,不能指定时间,让出CPU使用权,使线程从运行状态转为就绪状态。
  • join
    等待调用join的线程执行完毕,才执行后面的代码。
    注意必须在start方法后才能调用join方法。

五、Volatile

1.定义
java编程语言允许多个线程共享变量,为了确保变量能准确和一致地更新,线程应通过排它锁单独得到这个变量。java提供了Volatile关键字,在某些情况下比锁更加方便。如果一个字段被声明成Volatile,java内存线程模型保证所有线程看到的变量的值是一致的。

2.原理

  • 使用volatile修饰的变量在汇编阶段,会多出一个lock前缀指令。
  • 它确保指令重排序的时候不会把前面的指令放到内存屏障后;也不会把后面的指令放到内存屏障钱。即:在执行到内存屏障时,确保前面的指令都执行完了。
  • 它会强制的将对缓存的修改立即写入主存中。
  • 如果是写操作,它会导致其他CPU中缓存了该内存地址的数据失效。

3.作用

  • 内存可见性
    多线程操作共享变量时,一个线程修改了值,其他线程能立即看到修改后的值。
  • 防止重排序
    即程序的执行顺序按代码的顺序执行。

六、Synchronized

1.定义
synchronized是JVM的一种锁,其中获取和释放锁的指令是分别是monitorenter和monitorexit,该锁在实现上分为偏向锁、轻量级锁和重量级锁,其中偏向锁在java1.6中是默认开启的,轻量级锁在多线程竞争的时候回膨胀成重量级锁,有关锁的机制都保存在对象头中。

2.原理
加了synchronized关键字的代码段,生成的字节码文件会多出 monitorenter 和monitorexit 两条指令。
加了synchronized关键字的方法,字节码文件会多出一个ACC_SYNCHRONIZED 标志位,当方法被调用时,指令会检查ACC_SYNCHRONIZED 标志位有没有被设置,如果设置了,将会先获取monitor,获取成功后才能执行方法体,访问结束后释放monitor,在方法执行期间其他线程将不能获取同一个monitor对象。其实本质上没什么区别,只是方法的同步是一种隐式的方式来实现。

3.使用说明

  • 修饰普通方法
    同步对象是实例对象

  • 修饰静态方法
    同步的是类本身

  • 修饰代码块
    可以自己设置同步对象

4.缺点
会让没有得到锁的资源进入Block状态,争夺到资源之后又转为Running状态,这个过程涉及到操作系统用户模式和内核模式的切换,代价比较高。Java1.6为 synchronized 做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

七、CAS

1.什么是CAS?
CAS全称是Compare And Swap,即比较替换,是实现并发应用到的一种技术。操作包含三个操作数——内存位置(V),
预期原值(A)、新值(B)。如果预期原值和新值相等,处理器就会将地址自动更新为新值。否则,处理器不做处理。

2.为什么会有CAS?
如果只是用 synchronized 来保证同步会存在以下问题:
synchronized是一种悲观锁,会引起很大的性能问题。
多个线程竞争时,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题。
一个线程得到资源会导致其他线程挂起。

3.实现原理
java不能直接访问操作系统底层,是通过native方法来访问。CAS底层是通过Unsafe类来实现原子性操作。

4.存在的问题

  • ABA问题

什么是ABA问题?比如有一个 int 类型的值 N 是 1
此时有三个线程想要去改变它:
线程A ​​:希望给 N 赋值为 2
线程B: 希望给 N 赋值为 2
线程C: 希望给 N 赋值为 1​​
此时线程A和线程B同时获取到N的值1,线程A率先得到系统资源,将 N 赋值为 2,线程 B 由于某种原因被阻塞住,线程C在线程A执行完后得到 N 的当前值2
此时的线程状态
线程A成功给 N 赋值为2
线程B获取到 N 的当前值 1 希望给他赋值为 2,处于阻塞状态
线程C获取当好 N 的当前值 2 ​​​​​希望给他赋值为1
​​
然后线程C成功给N赋值为1
​最后线程B得到了系统资源,又重新恢复了运行状态,​在阻塞之前线程B获取到的N的值是1,执行compare操作发现当前N的值与获取到的值相同(均为1),成功将N赋值为了2。

在这个过程中线程B获取到N的值是一个旧值​​,虽然和当前N的值相等,但是实际上N的值已经经历了一次 1到2到1的改变
上面这个例子就是典型的ABA问题​
怎样去解决ABA问题
给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值 还需要比较当前变量的版本号。Java中AtomicStampedReference 就解决了这个问题

  • 循环时间长开销大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

八、AbstractQueuedSynchronizer(AQS)

AQS抽象的队列式同步器,是一种基于状态(state)的链表管理方式。state 是用CAS去修改的。它是 java.util.concurrent 包中最重要的基石,要学习想学习 java.util.concurrent 包里的内容这个类是关键。 ReentrantLock​、CountDownLatcher、Semaphore 实现的原理就是基于AQS。
想知道他怎么实现以及实现原理 可以参看这篇文章https://www.cnblogs.com/waterystone/p/4920797.html

九、Future

在处理并发时我们一般使用Runnable区执行异步任务,然而这样我们是拿不到返回值的,但是Future可以。
使用Future很简单,只需要把Runnable改成FutureTask。

十、线程池

java中的线程池实现类是ThreadPoolExecutor。说说它的几个参数:

  • corePoolSize:核心线程数即一直存在线程池的线程数,即使处于空闲状态也不会被销毁。
    只有设置allowCoreThreadTimeOut 为true时,才能销毁。
  • maximumPoolSize:线程池中允许的最大线程数。
  • keepAliveTime 非核心线程允许的最大空闲时间,超过就会销毁。
  • workQueue:用来存放任务的队列。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值