sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu调度,获取到cpu资源后就可以继续运行了,而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把这个锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁,也就是说无法执行程序.如果睡眠期间其他程序调用了这个线程的interrupt方法,那么这个线程就会抛出interruptexception异常返回,这点和wait是一样的
yield()执行后线程直接进入就绪状态,马上释放了cpu执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到线程A结束或中断线程
破坏占用并等待
不可强占
破坏循环等待
模拟所有资源的预定最大可能量的分配来测试安全性.
银行家算法要发挥作用,需要知道三件事:
只有当请求的资源量小于或等于可用的资源量时,才能将资源分配给进程;否则,该过程将一直等到资源可用。
jps命令定位进程号
jstack找到死锁查看
不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆,全局堆就是所有没有分配的额外空间,局部堆就是用户分配的空间,堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏
在Java中,堆是Java虚拟机所管理的内存最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建,堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
栈是每个线程独有的,保存其运行状态和局部自动变量的,栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的,操作系统在切换线程的时候会自动切换栈,栈空间不需要再高级语言里面显式的分配和释放
目前主流操作系统都是多任务的,即多个进程同时运行,为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存),进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因.
Thread和Runnable的实质是继承关系,没有可比性,无论使用Runnable还是Thread,都会new Thread,然后执行run方法,用法上,如果有复杂的线程操作需求,那就选择继承Thread,如果只是简单的执行一个任务,那就实现runnable
守护线程:为所有非守护线程提供服务的线程,任何一个守护线程都是整个JVM中所有非守护线程的保姆;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO,File等重要操作逻辑分给他,因为它不靠谱
守护线程的作用?
GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM仅剩的线程时,垃圾回收线程会自动离开,它始终在低级别的状态运行,用于实时监控和管理系统中的可回收资源
应用场景:
每一个Thread对象均含有一个ThreadLocalMap类型的成员变量ThreadLocal,它存储本线程所有的ThreadLocal对象及其对应的值
ThreadLocalMap由一个个的Entry对象构成
Entry继承自WeakReference
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中
get方法执行过程类似,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,获取对应的value
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器互相独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性
使用场景
Spring框架在事务开始时会给当前线程绑定一个JDBC Connection,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性,Spring框架里面就是用的ThreadLocal来实现这种隔离
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏
强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收,当内存不足,java虚拟机宁愿抛出OutOfMemeryError错误,使程序异常终止,也不回收这种对象.
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,在java中,用java.lang.ref.WeakReference类来表示,可以在缓存中使用弱引用
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中的key为null,而value还存在着强引用,只有thread线程推出以后,value的强引用链表才会断掉,但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链
key使用强引用
当ThreadLocalMap的key为强引用,回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
key使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收,当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用
ThreadLocal正确的使用方法
线程池所做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行
它的主要特点为:线程复用:控制最大并发数,管理线程
常用的线程池
//一池五个处理线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(1);
//一池一个处理线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
//一池n个处理线程
ExecutorService threadPool3 = Executors.newCachedThreadPool();
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心线程数
线程池维护的最小线程数量,核心线程创建后不会被回收(注意:设置allowCoreThreadTimeout=true后,空闲的核心线程超过存活时间也会被回收)。
大于核心线程数的线程,在空闲时间超过keepAliveTime后会被回收。
线程池刚创建时,里面没有一个线程,当调用 execute() 方法添加一个任务时,如果正在运行的线程数量小于corePoolSize,则马上创建新线程并运行这个任务。
maximumPoolSize:最大线程数
线程池允许创建的最大线程数量。
当添加一个任务时,核心线程数已满,线程池还没达到最大线程数,并且没有空闲线程,工作队列已满的情况下,创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。
keepAliveTime:空闲线程存活时间
当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收。
可被回收的线程:
(1)设置allowCoreThreadTimeout=true的核心线程。
(2)大于核心线程数的线程(非核心线程)
unit:时间单位
keepAliveTime的时间单位:
TimeUnit.NANOSECONDS
TimeUnit.MICROSECONDS
TimeUnit.MILLISECONDS // 毫秒
TimeUnit.SECONDS
TimeUnit.MINUTES
TimeUnit.HOURS
TimeUnit.DAYS
workQueue:工作队列
新任务被提交后,会先添加到工作队列,任务调度时再从队列中取出任务。工作队列实现了BlockingQueue接口。
JDK默认的工作队列有五种:
(1)ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO(先进先出),使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
(2)LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无界),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
(3)SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
(4)PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
(5)DelayQueue 延时队列:无界,元素有过期时间,过期的元素才能被取出。
threadFactory:线程工厂
创建线程的工厂,可以设定线程名、线程编号等。
默认线程工厂:
/**
* The default thread factory
*/
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
handler:拒绝策略
当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。
JDK默认的拒绝策略有四种:
(1)AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
(2)DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
(3)DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
(4)CallerRunsPolicy:由调用线程处理该任务
线程池将线程和任务解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start()来创建新线程,而是让每个线程去执行一个"循环任务",在这个"循环任务"中不停检查是否有任务需要被执行,如果有则执行,也就是调用任务中的run方法,将run方法当成一个普通方法执行,通过这种方式只需要固定的线程就将所有任务的run方法串联起来
使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题,如果不适用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者过度切换的问题
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
弊端如下
- FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM- CachedThreadPool和ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
当线程数达到最大线程数且都在工作,阻塞队列也已经满了的时候,如果再有新的任务进来,就会启用拒绝策略
深入Lock锁底层原理实现,手写一个可重入锁
深入理解synchronized底层原理,一篇文章就够了!
关于 锁的四种状态与锁升级过程 图文详解
尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3", 22);
User l4 = new User("l4", 25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
// atomicReference.set(l4);
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
}
}
true User{userName='l4', age=25}
false User{userName='l4', age=25}
修改版本号(时间戳)
通过时间戳原子类引用AtomicStampedReference来解决ABA问题
//ABA问题的解决 AtomicStampedReference
public class ABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===================以下是ABA问题的产生=======================");
new Thread(()->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"t1").start();
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
},"t2").start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=========================以下是ABA问题的解决=======================");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号 : " + stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号 : " + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号 : " + atomicStampedReference.getStamp());
},"t3").start();
new Thread(() ->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号 : " + stamp);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否 : " + result + "\t当前最新实际版本号 : " + atomicStampedReference.getStamp());
},"t4").start();
}
}
AQS抽象的队列同步器
AQS是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态
如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配,这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS的抽象表现,它将请求共享资源的线程封装成队列的节点(Node),通过CAS,自旋以及LockSupport.part()的方式,维护state变量的状态,使并发达到同步的控制效果
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改
AQS-hasQueuedPredecessors()解析_绅士jiejie的博客-CSDN博客_hasqueuedpredecessors
【深入AQS原理】我画了35张图就是为了让你深入 AQS - 一枝花算不算浪漫 - 博客园
volatile是Java虚拟机提供的轻量级的同步机制
JMM(保证线程安全性)
JMM(Java内存模型Java Memery Model,简称JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式
JMM关于同步的规定
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,**但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,**不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成
volatile的可见性代码实例
//验证volatile的可见性
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t come in");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t update number value : " + myData.num);
},"A").start();
//第二个线程
while (myData.num == 0){
//main线程就一直在这里等待循环,直到num值不再等于0
}
System.out.println(Thread.currentThread().getName() + "\t mission is over");
}
}
class MyData {
volatile int num = 0;
public void addTo60() {
this.num = 60;
}
}
加入volatile后,main线程感知到num值被更改
那么volatile关键字是怎么做到可见性的呢?
从语义的角度来看
从cpu角度来看
可见性不意味着线程安全了,因为还没有实现原子性
如果两个线程A和B,他们都读取了主存中的 i 值,A进行了 i+1 的操作,之后被阻塞,B又进行了 i+1 的操作,之后将新值赋值给 i,i 值被刷新回主存,此时由于A已经执行完了 i+1 操作,所以即使主存中的i值改变了,缓存一致性原则将A中的 i 变为新值,但是这个 i 值的改变也不会影响它将之前执行完的 i+1 得到的值赋给 i这一步,同样导致了最后的结果出错
volatile关键字怎么实现禁止指令重排序?
内存屏障
volatile底层实现原理
java中的atomicInteger等类是通过CAS实现的线程安全
CAS的全称为Compare-And-Swap,它是一条CPU并发原语
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法,调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令.这是一种完全依赖于硬件的功能,通过它实现原子操作.再次操作,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
// unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 获取对象var1,偏移量为var2地址上的值,并赋值给var5
var5 = this.getIntVolatile(var1, var2);
/**
* 再次获取对象var1,偏移量var2地址上的值,并和var5进行比较:
* - 如果不相等,返回false,继续执行do-while循环
* - 如果相等,将返回的var5数值和var4相加并返回
*/
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
// 最终总是返回对象var1,偏移量为var2地址上的值,即上述所说的V。
return var5;
}
跳出循环的条件就是CAS成功,期望值与真实值相同,这段代码表示程序会一直循环(读取最新值)直到两次读取到的var5相同
public class Singleton {
private Singleton(){
}
private static volatile Singleton singleton;
public static Singleton getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
因为有指令重排序的存在
某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化
instance = new Singeton();可以分为以下三步完成
步骤2步骤3不存在数据依赖关系,所以可以重排优化
区别于AQS,Condition维护自己的队列
代码示例(ABC三个线程轮流打印)
/**
* @author XQ
* @description 多线程之间按顺序调用,,实现A->B->C三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 共十轮
* @create 2021-07-03-11:52
*/
public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource s = new ShareResource();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
s.print5();
}
},"A").start();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
s.print10();
}
},"B").start();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
s.print15();
}
},"C").start();
}
}
class ShareResource{
private int number = 1;//A:1 B:2 C:3
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(){
lock.lock();
try {
// 1 判断
while (number != 1){
c1.await();
}
// 2 业务
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3 通知
number = 2;
c2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print10(){
lock.lock();
try {
// 1 判断
while (number != 2){
c2.await();
}
// 2 业务
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3 通知
number = 3;
c3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print15(){
lock.lock();
try {
// 1 判断
while (number != 3){
c3.await();
}
// 2 业务
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3 通知
number = 1;
c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
不管怎么重排序,单线程下的执行结果不能被改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
Java中的锁分类与使用 - hustzzl - 博客园
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发情况下,有可能会造成优先级反转或者饥饿现象
并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁和非公平锁,默认是非公平锁
关于两者区别
公平锁: Threads acquire a fair lock in the order in whick they requested if
公平锁,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后按照FIFO的规则从队列中取到自己(synchronized也是非公平锁)
非公平锁:非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁的方式(吞吐量大)
指的是同一层线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码
在同一个线程的外层方法获取锁的时候,在进入内层方法会自动获取锁
也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块
可重入锁最大的作用就是避免死锁
synchronized是一个典型的可重入锁
代码示例
public class ReetrantLockTest {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
class Phone{
public synchronized void sendSMS() throws Exception{
System.out.println(Thread.currentThread().getId() + "\t" + "invoked sendSMS()");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getId() + "\t" + "invoked sendEmail()");
}
}
输出结果
14 invoked sendSMS()
14 invoked sendEmail()
15 invoked sendSMS()
15 invoked sendEmail()
ReentrantLock也是可重入锁
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
模拟实现自旋锁
public class T2 {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null, thread)){
System.out.println(Thread.currentThread().getName() + " : 尝试获取锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t lock");
}
public void myUnlock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t unlock");
}
public static void main(String[] args) {
T2 t = new T2();
new Thread(() ->{
t.myLock();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.myUnlock();
},"A").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
t.myLock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.myUnlock();
},"B").start();
}
}
A lock
B : 尝试获取锁
B : 尝试获取锁
A unlock
B lock
B unlock
核心就是CAS
ReadWriteLockDemo
独占锁:该锁一次只能被一个线程所持有,对ReentrantLock和Synchronized而言都是独占锁
共享锁:指该锁可被多个线程所持有
对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁
读锁的共享锁可保证并发度是非常高效的,读写,写读,写写过程都是互斥的
代码演示
public class T3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 5; i++) {
int index = i;
new Thread(()->{
System.out.println(index + " 线程执行");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("最终线程执行");
}
}
0 线程执行
3 线程执行
4 线程执行
1 线程执行
2 线程执行
线程会阻塞,因为达不到计数器的个数,但是0-4线程会执行
把i改成6,则最终线程会执行
public class T3 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {//改动此处
int index = i;
new Thread(()->{
System.out.println(index + " 线程执行");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("最终线程执行");
}
}
0 线程执行
3 线程执行
4 线程执行
5 线程执行
1 线程执行
2 线程执行
最终线程执行
CyclicBarrier是加,CountDownLatch是减
CyclicBarrier会阻塞其他线程,像一道大门,当指定条件个数的线程到达门口时,一起打开
public class T3 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("最终线程执行");
});
for (int i = 0; i < 6; i++) {
int index = i;
new Thread(()->{
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(index + " 线程执行");
},String.valueOf(i)).start();
}
}
}
最终线程执行
0 线程执行
3 线程执行
2 线程执行
5 线程执行
1 线程执行
4 线程执行
这里如果线程数不足,就不会执行任何线程的的run方法
public class T3 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("最终线程执行");
});
for (int i = 0; i < 5; i++) {//这里改动了
int index = i;
new Thread(()->{
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(index + " 线程执行");
},String.valueOf(i)).start();
}
}
}
信号量
用来控制同时访问某个特定资源的操作数量,或者同时执行某个制定操作的数量。计数信号量还可以实现某种资源池,或者对容器施加边界。
public static void main(String[] args) {
//模拟有3个空车位
Semaphore semaphore = new Semaphore(3);
//6个线程来抢3个资源
for (int i = 1; i <= 6; i++) {
int num = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "个车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开了");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
1抢到了第1个车位
2抢到了第2个车位
5抢到了第5个车位
2离开了
1离开了
5离开了
3抢到了第3个车位
6抢到了第6个车位
4抢到了第4个车位
6离开了
3离开了
4离开了
/**
* 题目: 一个初始值为零的变量,两个线程对其交替操作
*
* @author xq
* @create 2021-07-03-10:08
*/
public class ProdConsumer_TraditionDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "BB").start();
}
}
class ShareData {//资源类
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
//1 判断
while (number != 0) {
//等待.不能生产
condition.await();
}
//2 增加
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3 通知唤醒
this.condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
lock.lock();
try {
//1 判断
//多线程中,if被while替代是有原因的:比如有个线程进方法了,发现条件不满足,就wait,
//然后再次被唤醒的时候,不会进行二次判断的,而是直接向前走!
//使用while则不一样,每次都会判断,不满足条件就继续等待!
while (number == 0) {
//等待.不能生产
condition.await();
}
//2 增加
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
//3 通知唤醒
this.condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource resource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(() ->{
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
resource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(() ->{
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
try {
resource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
},"Consumer").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.stop();
}
}
class MyResource {
private volatile boolean FLAG = true;//默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void setFLAG(boolean FLAG) {
this.FLAG = FLAG;
}
public void myProd() throws Exception {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t插入队列 " + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列 " + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t生产结束");
}
public void myConsumer() throws Exception {
String result = null;
while (FLAG) {
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null == result || result.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 超过两秒没有取到蛋糕,消费退出");
System.out.println();
System.out.println();
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列 " + result + "成功");
}
System.out.println(Thread.currentThread().getName() + "\t消费结束");
}
public void stop(){
this.FLAG = false;
}
}
结果
Prod 生产线程启动
Consumer 消费线程启动
Prod 插入队列 1成功
Consumer 消费队列 1成功
Prod 插入队列 2成功
Consumer 消费队列 2成功
Prod 插入队列 3成功
Consumer 消费队列 3成功
Prod 插入队列 4成功
Consumer 消费队列 4成功
Prod 插入队列 5成功
Consumer 消费队列 5成功
Prod 生产结束
Consumer 超过两秒没有取到蛋糕,消费退出
Java多线程带返回值的Callable接口 - 知乎
线程等待唤醒机制的改良加强(wait->notify)
传统等待唤醒,线程先要获得并持有锁,必须在锁块中,而且必须要先等待后唤醒,线程才能够被唤醒
LockSupportAPI调用示例
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(() ->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t------come in");
LockSupport.park();//被阻塞....等待通知等待放行,它要通过需要许可证
System.out.println(Thread.currentThread().getName() + "\t------被唤醒");
},"A");
a.start();
Thread B = new Thread(() ->{
LockSupport.unpark(a);
System.out.println(Thread.currentThread().getName() + "\t" + "通知了");
},"B");
B.start();
}
}