Thread是Runable的实现类
继承Thread和实现Runable都可以创建线程对象, 两种方式对比如下
继承Thread可以使用Thread的内部方法, Runable中只有run方法
实现Runable避免单继承的局限性
解耦, 增强了程序的健壮性
线程池只能放入Runable或Callable类, 不能放入继承Thread的类
public interface Executor { void execute(Runnable command); }每个线程有自己独立的栈区域, 共用一个堆区域, 操作共享变量时会先拷贝一个变量副本到自己的工作内存(里面有线程自己的局部变量等), 执行共享变量操作时 操作的是这个变量副本, 所以高并发情况下可能会造成可见性的问题
[线程对变量的所有的操作(读, 取)都必须在工作内存中完成, 而不能直接读写主内存中的变量, 不同线程之间也不能直接访问, 对工作内存中的变量, 线程间变量的值的传递需要通过主内存完成]
ABA问题(狸猫换太子问题)
java.util.concurrent.atomic.AtomicStampedReference //底层使用了时间戳, 就和我们添加一个version字段一样的效果 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }CountDownLatch
底层是通过一个计数器来控制的, 每当一个线程完成了自己的任务后 , 可以调用countDown()方法让 计数器-1,当计数器到达0时, await()方法的线程阻塞状态解除, 线程继续执行与join区别, 使用join的线程将被阻塞, 使用countDown的线程不受影响, 只有调用await()的时候才会阻塞 public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象 public void await() throws InterruptedException// 让当前线程等待 public void countDown() // 计数器进行减1CyclicBarrier
让一组线程到达一个屏障(也可以叫同步点)时被阻塞, 直到最后一个线程到达屏障时, 屏障才会打开, 所有被屏障拦截的线程才会继续运行 (使用场景:可以用于多线程计算数据, 最后合并计算结果) public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时, 优先执行barrierAction,方便处理更复杂的业务场景 public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障, 然后当前线程被阻塞Semaphore
控制线程的并发数量对于Semaphore来说, 它要保证的是资源的互斥而不是资源的同步, 在同一时刻是无法保证同步的, 但是却可以保证资源的互斥. 只是限制了访问某些资源的线程数, 其实并没有实现同步. public Semaphore(int permits, boolean fair) //fair 表示公平性, 如果这个设为 true 的话, 下次执行的线程会是等待最久的线程, 默认为false public void acquire() throws InterruptedException 表示获取许可 public void release() release() 表示释放许可Exchanger
两个线程间的数据交换 public V exchange(V x) //等待另一个线程到达此交换点(除非当前线程被中断), 然后将给定的对象传送给该线程, 并接收该线程的对象 V exchange(V v, long timeout, TimeUnit unit) //等待另一个线程到达此交换点(除非当前线程被中断或超出了指定的等待时间), 然后将给定的对象传送给该线程, 并接收该线程的对象HashTable底层使用synchronized对整个数组进行锁定, 而ConcurrentHashMap使用CAS+局部锁定(分段锁)
// 常用并发容器 CopyOnWriteArrayList CopyOnWriteArraySet ConcurrentHashMap分段锁
分段锁其实是一种锁的设计, 并不是具体的一种锁, ConcurrentHashMap并发的实现就是通过分段锁的形式来实现高效的并发操作 ConcurrentHashMap中的分段锁称为Segment, 它类似于HashMap(JDK7与JDK8中HashMap的实现)的结构, 即内部拥有一个Entry数组, 数组中的每个元素又是一个链表; 同时又是一个ReentrantLock(Segment继承了ReentrantLock). 当需要put元素的时候, 并不是对整个hashmap进行加锁, 而是先通过hashcode来知道他要放在哪一个分段中, 然后对这个分段进行加锁, 所以当多线程put的时候, 只要不是放在一个分段中, 就实现了真正的并行的插入 但是, 在统计size的时候, 就是获取hashmap全局信息的时候, 就需要获取所有的分段锁才能统计 分段锁的设计目的是细化锁的粒度, 当操作不需要更新整个数组的时候, 就仅仅针对数组中的一项进行加锁操作java.util.concurrent.locks.AbstractQueuedSynchronizer
参见:
https://www.cnblogs.com/waterystone/p/4920797.htmlhttps://blog.csdn.net/mulinsen77/article/details/84583716 AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架. 这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类. 比如 CountDownLatch 类, 其内部有一个继承了 AbstractQueuedSynchronizer 的内部类 Sync. 可见 CountDownLatch 是基于AQS框架来实现的一个同步器. 类似的同步器在JUC下还有不少(如 Semaphore) AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中. CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系.CLH锁是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性; 也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋. AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配. 简单来说, AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒. 以ReentrantLock为例, state初始化为0,表示未锁定状态,A线程lock()时,会调用tryAcquire()独占锁并将state+1.之后其他线程再想tryAcquire的时候就会失败,直到A线程unlock()到state=0为止,其他线程才有机会获取该锁.A释放锁之前,自己也是可以重复获取此锁(state累加),这就是可重入的概念. 注意:获取多少次锁就要释放多少次锁,保证state是能回到零态的. 以CountDownLatch为例,任务分N个子线程去执行,state就初始化为N,N个线程并行执行,每个线程执行完之后countDown()一次,state就会CAS减一.当N子线程全部执行完毕,state=0,会unpark()主调用线程,主调用线程就会从await()函数返回,继续之后的动作. AQS 定义了两种资源共享方式: 1.Exclusive: 独占, 只有一个线程能执行, 如ReentrantLock 2.Share: 共享, 多个线程可以同时执行, 如Semaphore, CountDownLatch, ReadWriteLock, CyclicBarrie 实现不同的方法, 以决定锁策略: tryAcquire(int):独占方式.尝试获取资源,成功则返回true,失败则返回false. tryRelease(int):独占方式.尝试释放资源,成功则返回true,失败则返回false. tryAcquireShared(int):共享方式.尝试获取资源.负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源. tryReleaseShared(int):共享方式.尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false. 一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire/tryRelease || tryAcquireShared/tryReleaseShared中的一种即可.但AQS也支持自定义同步器同时实现独占和共享两种方式, 如ReentrantReadWriteLock. ... 自己去看上面两个链接吧 ...