首页 > 编程语言 > 详细

Java并发基础

时间:2020-05-13 21:09:10      阅读:42      评论:0      收藏:0      [点我收藏+]

@

1.Volatile

volatile是一个关键字,用于在并发编程中修饰变量

volatile:java提供的一种同步机制

  • 轻量的同步机制,用来确保将变量的更新通知到其他线程
  • 保证可见性(禁止指令重排)、不保证原子性

如何保证可见性

  • 变量声明为volatile类型后,编译器与运行时都会注意到这个变量时共享的,不会将该变量
    上的操作和其他内存操作仪器重排序
  • volatile变量不会被缓存在寄存器

指令重排
技术分享图片

多线程环境中,线程交替运行,编译器优化重排的存在,两个线程中使用的变量无法保持一致性

JMM(Java内存模型)

JMM描述的是一组规范,通过规范来定义程序中各个变量的访问方式

jmm同步规定:

  • 1.线程解锁前,必须把共享变量的值刷新回主内存
  • 2.线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 3.加锁解锁是同一把锁

不保证原子性

原子性:不可分割、完整性,即某个线程正在做某个具体业务,中间不可以被加塞或者被分割,需要整体完整,
要么同时成功,要么同时失败

使用AtomicInteger,synchronized 可以保证原子性

2.CAS

CompareAndSet-- 比较并设置(交换)

CAS是一条CPU并发原语,体现在sun.misc.Unsafe类中各个方法

public final boolean CompareAndSet(int expectedValue,int newValue){
    return U.CompareAndSetInt(this,VALUE,expectedValue,newValue);
}

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();

原子变量是一种更好的“volatile”

原子变量比锁的力度更细,量级更轻

AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference

CAS的缺点

  • 1.循环时间长,开销大(CAS长时间不成功)
  • 2.只能保证一个共享变量的原子操作
  • 3.ABA问题

解决ABA问题

AtomicStampedReference

并发容器类(ConcurrentHashMap,CopyOnWriteArrayList)代替同步容器类(Hashtable,Vector)

CopyOnWriteArrayList的操作,读写分离


public boolean add(E e){
    final ReentrantLock lock=this.lock();
    lokc.lock();
    try{
        Object[] elements=getArray();
        int len=elements.length;
        Object[] newElements=Arrays.copyof(elements,len+1);
        newElements[len]=e;
        setArray(newElements);
        return true;
    }finally{
        lock.unlock();
    }
}

3.锁

公平锁,非公平锁

ReentrantLock 默认是非公平锁,但是可以设置称为公平锁
synchronized 是非公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

// 通过两个静态内部类来实现
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

可重入锁(递归锁)

递归锁是指同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码

线程可以进入任何一个它已经拥有的锁所同步着的代码块

ReentrantLock/Synchronized 就是典型的可重入锁

重入锁进一步提升了加锁行为的封装性

独占锁(写锁)/共享锁(读锁)/互斥锁

独占锁:该锁一次只能被一个线程持有(ReentrantLock,Synchronized)
共享锁:该锁可以被多个线程所持有(ReentrantReadWriteLock的读锁)

自旋锁(spinlock)

尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁

synchronized lock
原始构成 JVM,底层使用monitor对象完成 具体类(java.util.concurrent.locks)
中断 不可中断 可以中断
加锁公平性 非公平锁 默认是非公平,构造传入true可以实现公平
条件 无条件 Condition

4.AQS

AbstractQueuedSynchronizer

在java.util.concurrent

  • ReentrantLock

  • ReentrantReadWriteLock

  • SynchronousQueue

  • FutureTask

  • CountDownLatch

  • Semaphore

  • CyclicBarrier

CountDownLatch

  • 1.它允许一个或多个线程一直等待,知道其他线程的操作执行完后再执行。例如,应用程序的主线程希望在负责 启动框架服务的线程已经启动所有的框架服务之后再执行
    1. CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,调用线程会被阻塞。其他线程调用 countDown()方法会将计数器减1,当计数器的值变为0时,因调用await()方法被阻塞的线程才会被唤醒,继续执行

Semaphore

可以代替Synchronize 和Lock

用于多个共享资源的互斥作用,另一个用于并发线程数的控制

CyclicBarrier

可循环(Cyclic)使用的屏障。让一组线程到达一个屏障(也可叫同步点)时被阻塞,知道最后一个线程到达屏 障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CycliBarrier的await()方法

可以设置循环使用

5.ThreadPool

阻塞队列

  • ArrayBlockingQueue,一个基于数组结构的有界阻塞队列,FIFO
  • LinkedBlockingQueue,个基于链表结构的阻塞队列,FIFO,吞吐量高于ArrayBlockingQueue
  • SynchronousQueue,一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量较高

技术分享图片

在多线程领域:所谓阻塞,在某些情况下会挂起线程,一旦满足条件,被挂起的线程又会自动被唤醒

使用阻塞队列BlockingQueue时我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都可自行处理

阻塞队列的各种方法

方法类型 阻塞 抛出异常 特殊值 超时
插入 put(e) add(e) offer(e) offer(e,time,unit)
移除 take() remove() poll() poll(time,unit)
检查 ~ element() peek() ~

线程池

线程池主要是用来控制线程的数量,处理过程中将任务放进队列,然后在线程创建后启动给这些任 务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来 执行

  • 线程复用,降低线程创建和销毁造成的的资源消耗
  • 控制最大并发数
  • 管理线程,提升响应速度,任务到达不需要等待线程创建的过程

几个线程池的实现

  • Executors.newFixedThreadPool(int)
  • Executors.newSingleThreadExecutor()
  • Executors.newCachedThreadPool()

技术分享图片

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
  • corePoolSize,线程池中常驻核心线程数
    在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务
    当线程池的线程数达到corePoolSize后,就会把到达的任务放到缓存队列当中

  • maximumPoolSize,线程池能够容纳同时执行的最大线程数,必须大于等于1

  • keepAliveTime,多余空闲线程的存活时间

  • unit, 存活的时间单位 (TimeUnit.)

  • workQueue,任务队列,被提交但是尚未被执行的任务

  • threadFactory,表示生成线程池中工作线程的线程工厂,默认即可

  • handler,拒绝策略

RejectedExecutionHandler 拒绝策略

当线程全部占用,等待队列已经全部排满,无法接纳新的任务,需要拒绝策略机制来合理的处理这个问题

  • AbortPolicy 默认,直接抛出RejectedExecutionException异常阻止系统正常运行

  • CallerRunsPolicy,将任务回退到调用者

  • DiscardOldestPolicy,抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务

  • DiscardPolicy,直接丢弃任务

线程池创建

  • 线程池不允许>使用Executors创建
  • 通过ThreadPoolExecutor的方式,规避资源耗尽风险
  • FixedThreadPool和SingleThreadPool允许请求队列长度为Integer.MAX_VALUE可能会堆积大量请求
  • CachedThreadPool和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,导致OOM

线程池的工作流程

技术分享图片

配置线程池

  • CPU密集型,大量的运算,基本不会出现阻塞,线程池=CPU核心数+1
int num_cpu=Runtime.getRuntime().availableProcessors();  // cpu的核心数
  • IO密集型,会出现阻塞,尽可能多的配置多的线程
    估算出任务的等待时间与计算时间的比值

CPU核心数/(1-阻塞系数) ,阻塞系数0.8~0.9之间

Java并发基础

原文:https://www.cnblogs.com/GeekDanny/p/12884843.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!