多线程高并发学习(上)

1、Java中线程的6大状态

  • New状态:刚被创建出来时的状态
  • Runnable状态分为以下两种状态:

    • ReadyY状态:待运行状态/就绪状态
    • Running状态:运行状态
  • Teminated状态:消亡/结束状态,线程执行完毕后的状态,不能继续调用start()方法
  • TimeWaiting状态:等待(时间)状态,等过了某个设定的时间后进入Runnable状态
  • Waiting状态:在运行时如果调用了wait()、join()、park()方法,将会进入等待状态,调用ontify()、ontifyAll()、unpark()方法进入Runnable状态
  • Blocked状态:阻塞状态,进入同步代码块时没有得到锁而进入的阻塞状态,当获得锁时进入Runnable状态

2、Synchronized

  • jdk早期的Synchronized实现是OS重量级锁
  • 经改造后Synchronized有以下锁升级的概念(Hotspot实现)

    • 无锁状态:没有任何线程访问时是处于无锁状态
    • 偏向锁:如果只有第一个线程访问,此时还是不加锁的,只在这个对象头(markword)上记录一下线程ID,称为偏向锁
    • 自旋锁(CAS):偏向锁此时如果有线程征用,锁升级为自旋锁,占用CPU死循环(默认自旋10次)尝试得到锁
    • 重量级锁(OS锁):自旋10次后还未获取到锁的情况下,升级为重量级锁,所有未获取到锁的线程进入等待队列
  • 锁只能升级,不能降级,Synchronized是可重入锁
  • 当同步代码块中出现异常,当前线程将释放掉锁
  • Synchronized的优化:根据情况将锁力度变粗或变细

    • 如果一段代码中有很多锁,可以尝试用一把锁将整段需要同步的代码控制住
    • 如果所需同步的代码较少的情况下就没有必要使用方法锁,可以减少锁中代码的数量

3、Volatile关键字

  • Volatile三个特点:

    • 保证线程之间可见性:多个线程之间共享统一数据源时,当某个线程改变数据时,其他线程可以很快的获取到该数据的最新值
    • 禁止指令重排序:CPU是流水线式的工作,我们的程序被CPU执行时,可能会有一个指令重新排序的可能,比如我们给一个变量赋值,本来的步骤是:

      • 1、先初始化,赋一个初始值
      • 2、然后是将成员变量设置成真真正正的初始值
      • 3、真正的进行赋值,将值复制给我们的Instance
      • 指令重排序导致步骤发生改变,有可能会出现刚初始化,就直接将值赋值给了instance,有可能会出问题
    • 但不能保证原子性

4、AtomicXXX、LongAdder

  • AtomicInteger(基于CAS来实现的):自增、获取都是线程安全的
  • AtomicLong(基于CAS来实现的):自增、获取都是线程安全的
  • 等等等等很多可以及进行原子操作的类,命名都为AtomicXXX
  • LongAdder的原理:

    • LongAdder在高并发时对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作(分段CAS锁),对取值时进行多段求和
    • LongAdder在高并较低时与AtomicLong原理相同,都是对单一变量的CAS操作

5、CAS(Compare And Set)无锁操作

  • Compare-比较 And-并且 Set-设置
  • 自旋锁的的实现就是基于CAS来实现的
  • CAS的几个参数

    • V:当前内存中的值,是通过修改之前get获取到的
    • expect:当前读取到的值,也叫做期望值
    • update:变更。当V与expect相同时,会进行数据的变更,如果不相同则不进行变更
  • CAS常见的ABA问题:当A线程get到V值(假设数据为1)后对数据进行变更的过程中,B线程先一步使用CAS的方式将数据改变成2,又将数据改回成1,那么对于A线程来说,数据没有发生改变,可以进行update变更,但是其实中间数据已经发生改变。

    • CAS操作除了V、expect、update参数外,还可以再加一个Version(版本号)参数,数据每发生一次改变,version版本号都会发生一次变更,除了V与expect对比外还会对比版本号是否相同

6、Lock锁

  • Lock锁常用的实现类:

    • ReentrantLock

      • 构造方法入参true(公平锁)/false(非公平锁),默认是非公平锁
      • 获取锁:lock()
      • 释放锁:unlock()
      • 尝试获取锁:trylock(时间,时间单位),在规定时间内尝试获取锁,返回true(获取成功)/false(获取失败)
      • 获取可以被打断的锁:lockInterruptibly(),使用该方法获取的lock锁,可以被别的线程调用interrupt()方法打断,并释放锁
    • ReentrantReadWriteLock

      • readLock() 方法:返回一个Lock类型的读锁(共享锁)

        • 读锁(共享锁):当其中一个线程持有这把锁的时候,其他的所有的读线程也可以共享这把锁
      • writeLock() 方法:返回一个Lock类型的写锁(排他锁)

        • 写锁(排他锁):当其中一个线程持有这把锁的时候,其他任何线程,不论是读、写线程都不可以共享这把锁

7、同步工具

7.1、CountDownLatch门栓

  • 构造方法传入一个int值,返回一个CountDownLatch对象
  • 常用方法:

    • await():当前线程阻塞住等待被唤醒
    • countDown():将初始化时传入的int类型的参数进行自减的原子性操作
    • getCount():获取当前门栓数,也就是初始化时传入的int类型的值
    • await(时间,时间单位):阻塞当前线程,给定一个时间,如果超过该时间,则自动唤醒,继续执行
  • 当调用countDown()方法将初始化时传入的参数自减为0时,所有调用await()方法的线程被唤醒继续执行

7.2、CyclicBarrier栅栏

  • 构造方法传入一个int值,返回一个CyclicBarrier对象

    • 传入一个int值和一个线程,返回CyclicBarrier对象
  • 常用方法:

    • await():当前线程阻塞住,当线程调用await()方法次数达到初始化CyclicBarrier对象传入的int类型的值时,所有调用该对象的await()方法的线程解除阻塞,继续执行
  • CyclicBarrier与CountDownLatch不同点在于,CountDownLatch可以由一个线程循环调用countDown()方法来解除调用await()而阻塞的线程

7.3、Semaphore信号灯

  • 构造方法传入一个int值,返回一个Semaphore对象这个 int 值决定了有多少线程可以同时调用 Semaphore 的 acquire() 方法,当调用 acquire() 方法的线程超过了这个初始化值的时候,其他线程调用 acquire() 就会被阻塞
  • 初始化除了可以给定一个int类型的值之外,还可以规定其是公平锁还是非公平锁
  • acquire():获取到Semaphore的锁,此时初始化传入的int类型的值会原子性的自减,当减到0时,其它线程就无法调用 acquire() ,否则就会被阻塞
  • release():调用该方法,初始化传入的int类型的值就会原子性的自增

7.4、Exchanger线程通讯(交换数据)

  • Exchanger 用于两线程之间交换数据,初始化之后调用 exchange() 方法,传入要交换的数据,必须是两两配对交换
  • exchange( 要交换的数据 ):

    • 当只有一个线程调用该方法时,此线程会因为没有其他线程与其配对交换数据而阻塞住,直到有线程与其配对并交换数据
    • 当有两个线程同时调用此方法时,两个线程就会交换数据,此方法的返回值就是交换后的数据
    • 只能是两两配对

7.5、LockSupport

  • park():调用此方法使当前线程进入等待队列,等待其他线程唤醒
  • unpark(Thread t1):唤醒指定线程,可以提前唤醒

    • 提前唤醒:线程B首先调用unpark( A ),线程A调用park时自己不会被阻塞

你可能感兴趣的:(java)