java并发编程艺术

synchronized:jvm实现,重量级锁

  • 对于普通同步方法,锁对象为当前实例对象
  • 对于静态同步方法,所对象为当前类对象
  • 对于同步代码块,所对象为括号内配置的对象

jvm基于进入和退出实现同步方法和同步代码块

  • 同步代码块的实现方式为monitorenter和monitorexit实现:每个对象都有一个monitor与之关联,monitorenter指令在编译后被插入到同步代码块的开始位置,monitorexit指令被插入到代码块的结束位置和异常处,当线程执行到monitorenter指令时,会获得当前指定对象的monitor,即获得锁

锁的四种状态:按照级别由低到高

  • 无锁

  • 偏向锁

    对象头中存储线程的ID,下次指定线程进入时就不用再进行获得锁和释放锁,当有其他线程竞争当前锁时,会触发锁撤销,其他线程才可以获得锁

  • 轻量级锁

    线程获得锁时,会在线程的栈帧创建存放锁记录的空间,复制对象头的Mark Word信息到锁记录空间,并将对象的Mark Word指向当前线程的锁记录空间地址,如果其他线程竞争当前锁,,其他线程会自旋方式获得锁,并且当线程释放锁时其他有线程竞争锁时,该所会升级为重量级锁

  • 重量级锁

    当其他线程需要获取该锁时,该所已被其他线程持有,会导致当前线程阻塞,其他线程释放锁时会通知其他要获得该锁的线程,让他们进行竞争

原子操作

  • 处理器层面实现

  • java实现:

    JDK1.5之后的并发包提供一系列的原子操作类,AtomicInteger.AtomicBoolean等,调用compareAndSet可以在设置数据时与原数据进行比较,相同则进行设置,但是出现的问题就是ABA问题,当一个值被从A修改为B,B再被修改为A,下一次compareANdSet会认为当前值没有改变过,有一种狸猫换太子的感觉,所以为了解决ABA问题,可以添加版本号,每次修改,都将版本号+1,当compareAndSet时对比版本号和原始值是否一致,一致再进行替换,JDK就提供了AtomicStampedReference类,调用该类compareAndSet需要设置4个参数(预期的引用值,更新后的引用值,预期的标记,更新后的标记)

重排序

  • 编译器优化重排序:编译器级别
  • 指令集并行重排序:处理器级别,如果不存在依赖性,可以将机器指令并行执行
  • 内存系统重排序:处理器级别

如果不希望在处理器编译器优化,可以通过编译器插入内存屏障,达到禁止特定类型的指令重排

线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        10, // 核心线程数
        20, // 最大线程数= 核心线程数+工作线程数
        30, // 工作线程保活时间
        TimeUnit.MINUTES, // 保活时间单位
        new ArrayBlockingQueue<>(10), // 任务队列:当核心线程和工作线程都在处理任务时,其他任务放到该队列
        new MyThreadFactory(), // 创建线程的工场:实现ThreadFactory接口进行实现
        new ThreadPoolExecutor.DiscardPolicy() // 饱和策略
);
class MyThreadFactory implements ThreadFactory{

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r,"线程-"+r.toString());
        }
    }
// 线程池创建构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  1. corePoolSize:核心线程数,不受保活时间影响
  2. maximumPoolSize:最大线程数=核心线程数+工作线程数,当核心线程数和最大线程数设置一样,表示工作线程数为0,即保活时间参数没用
  3. keepAliveTime:工作线程的保活时间
  4. unit:保活时间的单位
  5. workQueue:任务队列,当工作线程和核心线程都在处理任务时,还有新任务,就会添加到这个队列
    • ArrayBlockingQueue:基于数组实现的先进先出队列
    • LinkedBlockingQueue:基于链表的先进先出队列
    • SynchronousQueue:不存储任务的队列,当任务要添加时,必须等到一个线程调用了移除操作,否则添加操作一致阻塞
    • PriorityBlockingQueue:一个具有优先级的无限阻塞队列
  6. threadFactory:线程创建工场
  7. handler:饱和策略,当队列和线程都满了的时候,再添加任务如何进行处理
    • AbortPolicy:直接抛出异常
    • CallRunsPolicy:使用调用者线程来进行处理线程
    • DiscardOldestPolicy:丢弃最旧的任务,也就是队列最前面的任务
    • DiscardPolicy:直接丢弃过来的任务

几种JDK实现的线程池

  • FixedThreadPool

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue());
    }
    
    1. 核心线程数和最大线程数都设置为传入的值,即线程池只有核心线程

    2. 线程无过期时间

    3. 使用链表阻塞队列

  • SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue()));
    } 
    
    1. 核心线程数和最大线程数都为1
    2. 无过期时间
    3. 使用链表阻塞队列
  • CachedThreadPool

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue());
    }
    
    1. 核心线程数为0,对大线程数无限
    2. 过期时间60秒,即如果线程空闲60s就自动销毁
    3. 不存储任务的队列,每次添加任务就是用之前的线程,要是之前的线程都不空闲,就创建新的线程
  • ScheduledThreadPool

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
    
    1. 核心线程数为传入参数,最大线程数无限
    2. 当工作线程执行完任务后便进行销毁
    3. 使用延时队列

你可能感兴趣的:(java并发编程艺术)