Android技术知识Android开发Android进阶之路

并发整理(二)— Java线程与锁

2017-05-18  本文已影响290人  kachidokima

现已全部整理完,其他两篇
并发整理(一)—Java并发底层原理
并发整理(三)— 并发集合类与线程池

本篇主要讲锁实现涉及到点线程

线程相关

优先级

通过setPriority(int newPriority)来设定,范围为1-10,默认是5。

更高优先级的线程优先运行,优先的意思是只是在分配cpu时间段的时候,得到的概率高一些。
当在某个线程创建一个新的线程,这个线程有与创建线程相同的优先级。

线程优先级不能作为程序正确性的依赖,因为部分操作系统不一定会理会Java线程对于优先级的设定

Daemon线程

一种支持线程,主要用作程序中后台调度以及支持性工作。

通过setDaemon(boolean on) 必须在start之前设置,不能在启动之后设置。

JVM中只剩下Daemon线程,JVM会退出,不会支持其运行。

所以,在构造Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

常用方法

Thread常用方法

各种状态

Jvm中的线程有以下状态(不是操作系统)

这个图还不错这个图还不错

锁相关

关于CAS

CAS是一种无锁并发技术,是并发中很重要的技术,用来原子的更新数据

简单说就是CAS(Compare and Swap)比较并替换,先获取旧值,然后修改,再替换,在替换的过程中如果发现旧值和原来不一样,则说明其他线程也在修改,自己已经脏读,所以本次失败

JavaCAS深度分析

隐式锁

Java中的指的就是synchronized。是一种可重入/非公平/悲观/独占锁。

它是由JVM实现的,基于面向对象中Monitor Object设计模式来实现的

关于这个模式可以看

从C++来理解Java中的Java 同步机制

对象头

Java对象头中的MarkWord储存锁标记位

MarkWord储存状态MarkWord储存状态

锁升级

很多人把synchronized叫重量锁,但是jdk6之后进行优化了其性能,通过不同状态来采取策略。可以看到总共有四种状态由低到高:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。并且采取只能升级不降级的策略。

锁升级如下:

  1. 当一个线程访问同步块,先看记录,如果没有储存线程ID则用CAS替换MarkWord并且置偏向锁。

    之后每次同一个线程进入只要检查MarkWord状态就行,没有其他额外开销。

    如果有其他线程进入,检测MarkWord是否有自己线程ID,发现没有采用CAS替换并且失败了,检查是否偏向线程还活着,不活着就置无锁,活动则遍历锁记录,然后决定是否升级。

  2. 到轻量锁后在锁记录添加,并且CAS替换指向锁指针,如果成功则没有竞争,并获得锁。

    失败则说明存在竞争,所以升级。

  3. 重量级锁就会用到Monitor

    waitnotify运行过程waitnotify运行过程

显式锁

虽然synchronized可以获取锁,但是其将锁固化了,有时不够灵活,所以JDK5后新增了Lock接口并且相关实现类,来方便我们实现更高的需求。

Lock接口

所有的锁实现都要实现这个接口符合规范

public interface Lock {
  //只有当锁获得后才会从该方法返回,否则阻塞
    void lock();
  //提供synchronized做不到的可中断获取锁
    void lockInterruptibly() throws InterruptedException;
  //尝试获取锁,不会阻塞
    boolean tryLock();
  //提供synchronized做不到的超时获取锁,可被中断
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
  //获取锁的协同条件变量,如obj.wait一样使用
    Condition newCondition();
}

AQS

AQS(AbstractQueuedSynchronizer)可以称作同步器,每个锁都会有一个其实现并作为内部类,用来管理线程来获取锁。

结构
public abstract class AbstractQueuedSynchronizer{
  private volatile int state;//控制同步的状态量
  
    //设计者希望其能够简化锁的实现,所以封装了大量的操作。主要提供了共享与非共享的同步状态管理。
    //采用模板方法的设计模式,其模板方法主要如下:
  
    //独占式获取同步状态的几个方法
    public final void acquire(int arg) {...}
    public final void acquireInterruptibly(int arg){...}//可中断
    public final boolean tryAcquireNanos(int arg, long nanosTimeout){...}//超时获取
    public final boolean release(int arg) {...}
  
    //共享式获取同步状态的几个方法
    public final void acquireShared(int arg) {...}
    public final void acquireSharedInterruptibly(int arg){...}
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout){...}
    public final boolean releaseShared(int arg) {...}
  
    //还有一些工具方法,返回等待线程情况
    public final Collection<Thread> getQueuedThreads() {...}
    
    //模板中调用的核心方法希望我们在子类中实现
    //重写的时候修改Status时要用AQS给我们提供的compareAndSetState方法来CAS设置
    protected boolean tryAcquire(int arg) {}
    protected boolean tryRelease(int arg) {}
    protected int tryAcquireShared(int arg) {}//共享式获取返回值>0代表成功
    protected boolean tryReleaseShared(int arg) {}
    protected boolean isHeldExclusively() {}//表示是否被当前线程独占
  
    //以下部分是AQS内部维护的CLH同步队列
    private transient volatile Node head;
    private transient volatile Node tail;
    static final class Node {
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
        ...
    }
  
    //锁内的条件变量也封装在AQS中
    public class ConditionObject implements Condition{...}
}
同步队列管理

可以看到AQS就是封装了这部分,这也是锁最重要的部分,因为其封装了独占式和共享式的同步方式,两者略有不同,现在看一下其实现。

锁获取

acquireacquireShared就是这两个方法,源码很少可以自己看

队列中等待
锁释放
支持超时获取同步状态

AbstractQueuedSynchronizer的介绍和原理分析

可重入锁

可重入锁ReentrantLock就是典型的Lock接口实现,继承了AQS并且实现tryAcquire还添加了nofairTryAcquire来支持公平与非公平锁的选择。代码非常少,可以自己看。

主要注意的:

Condition

可以看到ConditionObjective是AQS的内部类

与Obj监视器方法类似,通过Lock.newCondition()方法能够创建与Lock绑定的Condition实例。
await()对应于Object.wait()
signal()对应于Object.notify()
signalAll()对应于Object.notifyAll()
当然这几个方法与synchronized一样,需要先Lock.lock()获取锁。

注意的是:

ReentrantReadWriteLock

同样实现Lock接口,包含AQS,只是比ReentrantLock再复杂一些,是一个可重入共享锁,能够做到比排它锁更好的并发性和吞吐量

读写锁解析

工具类

CountDownLatch

可以实现类似计数器的功能,做到允许一个或多个线程等待其他线程完成操作

CyclicBarrier

是一种可循环使用的屏障,做到一组线程到达一个屏障是被阻塞,知道最后一个线程到达屏障,才会开门。常用于Fork/Join操作。

其与CountDownLatch最大区别在于,如果计算发生错误,可以重置计数器,让线程重新执行,所以用途更广泛

Semaphore

与操作系统中的信号量类似,通过协调各个线程,来保证合理的访问限定的资源数

其内部依然实现AQS来管理各个线程的同步状态

简单通过semp.acquire(),semp.release()来进行许可请求和释放

上一篇 下一篇

猜你喜欢

热点阅读