面试笔记-8.Java多线程与并发原理


1.线程安全的问题

图:线程安全的问题.png
在这里插入图片描述

1.线程安全性主要体现在三个方面:原子性、可见性、有序性

1.原子性:所有的指令要么全部执行,要么一个都不执行。提供互斥访问,同一时刻只能有一个线程对数据进行操作(CAS算法(Atomic)、synchronized、Lock)
A.保证原子性的三种方式:
-------1.Atomic:基于CAS算法实现,竞争激烈时能维持常态,比Lock性能好.
-------2.synchronized:不可中断锁,适合竞争不激烈,可读性好的情况
-------3.Lock:可中断锁,多样化同步,竞争激烈时能维持常态

2.可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
A.volatile不保证原子性

3.有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)

2.如何保证有序性

1.指令重排序:Java内存模型允许编译器和处理器对指令重排序以提高运行性能,并且只会对不存在数据依赖性的指令重排序。在单线程下重排序可以保证最终执行的结果与程序顺序执行的结果一致。
图:JVM指令重排序.png

2.as-if-serial:单线程执行程序时,即使发生重排序,程序的执行结果不能被改变。

3.happens-before原则:
图:happens-before概念.png 在这里插入图片描述
指令重排序时,不能违反happens-beofre原则。happens-before的前后两个操作不会被重排序且后者对前者内存可见(但是如果重排序之后的执行结果与按照原来那种happens-before关系执行的结果一致,是可以允许这种重排序的)。
happens-before的八大原则:
图:happens-before.png
在这里插入图片描述

4.内存屏障;通过添加屏障来阻止屏障两边的指令重排序来避免编译器和硬件的不正确优化。 定义:
LoadLoad:禁止读和读的重排序
StoreStore:禁止写与写的重排序
LoadStore:禁止读和写的重排序
StoreLoad:禁止写和读的重排序
-------1.volatile:
在每个volatile写操作的前面插入一个StoreStore屏障。保证volatile写操作执行时前面的写操作已经执行完成。
在每个volatile写操作的后面插入一个StoreLoad屏障。保证volatile写操作执行完成后,后面才能进行读操作。
在每个volatile读操作的后面插入一个LoadLoad屏障。保证volatile读操作执行完成后,后面才能进行读操作。
在每个volatile读操作的后面插入一个LoadStore屏障。保证volatile读操作执行完成后,后面才能进行写操作。
-------2.final:
1.final 域的写之后,构造函数 return 之前,插入一个 StoreStore 障屏。
2.读 final 域的操作前面插入一个 LoadLoad 屏障。

2.互斥锁的特性

图:互斥锁的特性.png
在这里插入图片描述

3.获取对象锁和获取类锁

图:获取对象锁和获取类锁.png
在这里插入图片描述
图:对象锁和类锁的总结.png
在这里插入图片描述

1.对像锁:
-------1.对象锁是用在实例synchronized方法或者实例方法中的synchronized代码快上。一个类的不同的对象的锁是不同的。
-------2.当某个线程进入了一个对象的 实例synchronized方法 或者 实例方法中的synchronized代码快,其他线程只能访问此对象的 非 实例synchronized方法 或者实例方法中的synchronized代码快。(使用对象锁)

2.类锁:
-------1.也称为该类的Class的对象锁,类锁是用在静态的synchronized方法或者静态或实例方法中synchronized代码上。因为一个类只有一个Class对象,所以同一个类的相同或不同实例的类锁是相同的。
-------2.当某个线程进入了一个对象的 静态的synchronized方法或者静态或实例方法中synchronized代码快中,其他线程只能访问此类所有对象的 非 静态的synchronized方法或者静态或实例方法中synchronized代码快。

4.synchronized同步代码快和同步方法

1.同步方法默认使用this(非静态方法)或者此类的Class对象(静态方法)的内部锁。
2.同步代码块可以任意指定其他对象或者其他类的Class对象的内部锁。

5.cas(Compare And Swap:比较并交换)算法高效实现线程安全性的方法

1.定义

CAS的比较、替换操作是非阻塞操作, 它有3个参数分别为内存值、预期值和更新值。 当内存值和预期值匹配时则更新, 不匹配时直接返回。 CAS虽然能高效的实现原子操作。

缺点:

1.ABA:ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
-------从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2.循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。 自旋:
-------1.获取主内存的值。
-------2.发现主内存的值和预期值不同,不能更新。
-------3.继续从第1步开始。如此循环。

3.只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。
-------从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

6.Atomic包

java.util.concurrent.atomic包下,基本使用Unsafe实现的一些类,基于CAS算法实现原子操作。

1.原子方式更新基本类型
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。

2.以原子方式更新数组
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。

3.原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量
AtomicReference:原子更新引用类型。
AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
AtomicMarkableReference:原子更新带有标记位的引用类型。

以原子方式更新字段
AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
AtomicLongFieldUpdater:原子更新长整型字段的更新器。
AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。

二.
1.synchronized的底层实现原理

图:对象如何与Monitor关联.png
在这里插入图片描述

2.实现synchronized的基础

图:实现synchronized的基础.png
在这里插入图片描述

3.对象的结构

在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(InstanceData)和对齐填充(Padding)
图:javad对象结构.png
在这里插入图片描述

2.对象头的结构

图:对象头的结构.png
在这里插入图片描述

1.markword
markword是一个非固定的数据结构,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“MarkWord”。

2.Klass Word
对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.

3.数组长度(只有数组对象有)
如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度.

1.对象头的MarkWord

markword是一个非固定的数据结构,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit,官方称它为“MarkWord”。Mark Word里存储的数据会随着锁标志位(类型)的变化而变化。
变化规则:图:对象头的MarkWord.png
在这里插入图片描述

2.Monitor

图:Monitor.png
在这里插入图片描述

1.每个对象都存在着一个monitor与之关联,但当一个monitor被某个线程持有后,它便处于锁定状态。在HotSpot中,monitor是由ObjectMonitor实现的。ObjectMonitor中有几个关键属性:
-------1._owner:指向持有ObjectMonitor对象的线程
-------2._WaitSet:存放处于wait状态的线程队列
-------3._EntryList:存放处于等待锁block状态的线程队列
-------4._recursions:锁的重入次数
-------5._count:用来记录该线程获取锁的次数

3.Monitor锁的竞争,获取和释放

图:Monitor锁的竞争、获取与释放.png
在这里插入图片描述

1.当多个线程同时访问一段同步代码时,首先会进入_EntryList队列中,当某个线程获取到对象的monitor后进入_Owner区域并把monitor中的_owner变量设置为当前线程,同时monitor中的计数器_count加1。即获得锁。

2.若持有monitor的线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count自减1,同时该线程进入_WaitSet集合中等待被唤醒。

3.若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)

4.synchronized代码块通过monitorenter与monitorexit的jvm操作指令来实现,控制线程持有和抛弃对象Monitor的指令。(上1、2、3)

5.synchronized同步方法是通过中设置ACC_SYNCHRONIZED标志来实现。
-------1.当线程执行有ACC_SYNCHRONI标志的方法,需要获得monitor锁。
-------2.然后开始执行方法,方法执行之后再释放monitor锁,当方法不管是正常return还是抛出异常都会释放对应的monitor锁。
-------3.在这期间,如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。
-------4.如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

6.锁的重入

图:锁的重入.png
在这里插入图片描述

4.自旋锁

图:自旋锁.png
在这里插入图片描述

5.自适应自旋锁

图:自适应自旋锁.png
在这里插入图片描述

6.锁消除

锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。
图:锁消除.png
在这里插入图片描述

7.锁粗化

图:锁粗化.png
在这里插入图片描述

8.synchronized的四种状态以及变化方向

图:synchronized的四种状态.png
在这里插入图片描述

9.偏向锁

看此文章
图:偏向锁.png
在这里插入图片描述

10.轻量级锁

看此文章

图:轻量级锁.png
在这里插入图片描述

12.偏向锁、轻量级锁、重量级锁的使用场景

图:偏向锁、轻量级锁、重量级锁.png
在这里插入图片描述

13:公平锁,非公平锁

1.公平锁保证了线程获取锁的顺序,等到最久的线程先获得锁。
2.非公平锁不保证顺序,可能导致某个线程永远获取不到锁。
3.Synchronize、Lock都是非公平

14.读写锁

将一个资源分两个锁,一个读锁和一个写锁

13.java的锁

看此文章


1.java内存模型JMM

图:java内存模型JMM.png
在这里插入图片描述

1.JMM中的主内存

图:JMM种的主内存.png
在这里插入图片描述

2.JMM的工作内存

图:JMM的工作内存.png
在这里插入图片描述

3.JMM内存和java内存的区别

图:JMM内存和java内存的区别.png
在这里插入图片描述
1.主内存主要对应java堆中的对象实例数据部分。
2.工作内存则对应虚拟机栈中的部分区域。

4.主内存与工作内存

图:主内存与工作内存.png
在这里插入图片描述

7.volatile

图:volatile.png
在这里插入图片描述

1.可见性:当一条线程修改了volatitle变量的值,其他线程是可以立即得知的。(修改值以后,会使别的线程的工作内存无效。)
图:volatile变量立即可见.png
在这里插入图片描述

2.禁止指令重排序。
图:volatile如何禁止重排优化.png
在这里插入图片描述

问题:
volatile不能保证原子性,当两个线程A和B同时操作被volatile修饰的变量时,A和B先后读取此变量,然后A线程+1并立即更新到主内存中,这时候B变量的工作内存的变量为无效状态,当B还没将此变量加载到寄存器时,会读取自己工作内存数据,发现无效,会从主存读。但如果B的状态是已将此变量加载到寄存器,就不会在进行工作内存的读取,便不会管自己工作内存中的无效值,那么A的操作相当于丢失了。

8.单例的双层检测实现

class Singleton{
    private static Singleton singleton;
    private Singleton(){}
---------------1.多线程时,多个线程执行到if里面后挂起,然后在执行,会出现多个实例----------------
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton(); // 创建实例
        }
        return singleton;
    }
---------------2.效率低----------------------------------------------------------------
    public static synchronized Singleton getInstance(){
		if(singleton == null){
			singleton = new Singleton(); //创建实例
		}
		return singleton;
	}
---------------3.初始化一个对象的时候,会经历内存分配-初始化-返回对象引用,但java的重排序可能会造成顺序的颠倒,既先返回对象引用,此时对象为null,使用时会出问题。---------
public static Singleton getInstance(){
	if(singleton == null){
		synchronized(Singleton.class){
		if(singleton == null)
			singleton = new Singleton(); //创建实例
		}
	}
	return singleton;
}	
---------------4.双重检测单例模式,完美----------------------------------------------------------------
public static Singleton getInstance() {
	if (instance == null) {
		synchronized (Singleton.class) {
			if (instance == null) {
				instance = new Singleton();
			}
		}
	}
	return instance;
}

9.volatile和synchronized的区别

图:volatile和synchronized的区别.png
在这里插入图片描述


1.线程池优点:

1.降低资源消耗。通过重复利用已创建的线程降低在频繁创建和销毁线程上所带来的性能损耗。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

2.线程池分类

图:线程池分类.png
在这里插入图片描述

1.使用的ScheduledThreadPoolExecutor的构造方法:return new FinalizableDelegatedExecutorService(ExecutorService executor) ;

1.newSingleThreadExecutor()||(ThreadFactory);
创建一个使用单个线程处理任务的Executor,如果因为在关闭动作发生前执行任务出现失败而终止了此单个线程,那么一个新线程将代替它执行后续的任务。
corePoolSize:1,maximumPoolSize:1,keepAliveTime:0L,unit:TimeUnit.MILLISECONDS,workQueue:new LinkedBlockingQueue(),threadFactory:Executors.defaultThreadFactory(),handler:new AbortPolicy()
设置了corePoolSize=maxPoolSize=1单线程,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入。当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常。

2.使用的ThreadPoolExecutor的构造方法:return new ThreadPoolExecutor((int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue) ;

2.newFixedThreadPool(int)||(int,ThreadFactory);
创建一个可重用固定线程数的线程池。
corePoolSize:用户传入的int参数,maximumPoolSize:用户传入的int参数,keepAliveTime:0L,unit:TimeUnit.MILLISECONDS,workQueue:new LinkedBlockingQueue(),threadFactory:Executors.defaultThreadFactory(),handler:new AbortPolicy()
设置了corePoolSize=maxPoolSize=用户传入的int参数,keepAliveTime=0(此时该参数没作用),无界队列,任务可以无限放入。当请求过多时(任务处理速度跟不上任务提交速度造成请求堆积)可能导致占用过多内存或直接导致OOM异常。

3.newCachedThreadPool()||(ThreadFactory);
创建一个可根据需要创建新线程的线程池。
corePoolSize:0,maximumPoolSize:Integer.MAX_VALUE,keepAliveTime:60L,unit:TimeUnit.SECONDS,workQueue:new SynchronousQueue(),threadFactory:Executors.defaultThreadFactory(),handler:new AbortPolicy()
corePoolSize=0,maxPoolSize为很大的数,同步移交队列,也就是说不维护核心线程,每次来请求直接创建新线程来处理任务,也不使用队列缓冲,列队长度为0,会自动回收多余线程(闲置超过60秒),当请求很多时就可能创建过多的线程,可能导致资源耗尽OOM

3.使用的ScheduledThreadPoolExecutor的构造方法:return new ScheduledThreadPoolExecutor(corePoolSize)。

4.newScheduledThreadPool(int)||(int,ThreadFactory);
创建一个在给定延迟后运行或者定期地执行的线程池。
corePoolSize:用户传入的int参数,maximumPoolSize:Integer.MAX_VALUE,keepAliveTime:0,unit:NANOSECONDS,workQueue:new DelayedWorkQueue(),threadFactory:Executors.defaultThreadFactory(),handler:new AbortPolicy()
使用DelayedWorkQueue延时列队,添加到队列中的任务,会按照任务的延时时间进行排序

newSingleThreadScheduledExecutor()||(ThreadFactory);
创建一个在给定延迟后运行或者定期地执行的线程池,只有单一的核心线程。
corePoolSize:1,maximumPoolSize:Integer.MAX_VALUE,keepAliveTime:0,unit:NANOSECONDS,workQueue:new DelayedWorkQueue(),threadFactory:Executors.defaultThreadFactory(),handler:new AbortPolicy()
只有单一的核心线程,但是可以创建新线程,但不给空闲时间。使用DelayedWorkQueue延时列队,添加到队列中的任务,会按照任务的延时时间进行排序

4.使用的ForkJoinPoolr的构造方法:return new ForkJoinPooll(int parallelism,ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler,boolean asyncMode)

5.newWorkStealingPool()||(int);java8新增。
parallelism:Runtime.getRuntime().availableProcessors()(cpu核心数)或用户指定,factory:ForkJoinPool.defaultForkJoinWorkerThreadFactory,handler:null,asyncMode:true

2.线程池创建的方法:

1.通过Executors工厂方法创建带默认参数的线程池
2.通过ThreadPoolExecutor的四个构造方法创建自定义参数的线程池

3.ThreadPoolExecutor()构造方法参数详解:

1.corePoolSize:核心线程数量,即在没有任务需要执行的时候线程池中线程的数量。刚创建ThreadPoolExecutor的时候,线程并不存在,等到有任务提交的时候才会创建。当任务来临,发现有大于corePoolSize个线程在忙,则会先把任务放入工作列队,当列队满了以后才会创建新的线程,但不能大于maximumPoolSize。考虑到allowCoreThreadTimeOut超时参数(executor.allowCoreThreadTimeOut(true))和keepAliveTime的影响,在没有任务需要执行的时候,线程池的大小不一定是corePoolSize。

2.maximumPoolSize:允许创建的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。

3.keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,当corePoolSize=maximumPoolSize时,并且allowCoreThreadTimeOut=false,keepAliveTime参数就不起作用了(因为不存在非核心线程);

4.unit:keepAliveTime的时间单位。

5.workQueue:用于保存任务的队列。
-------1.SynchronousQueue:(同步移交队列):队列不作为任务的缓冲方式,可以简单理解为队列长度为零。
-------2.LinkedBlockingQueue(无界队列):队列长度不受限制,当请求越来越多时(任务处理速度跟不上任务处理速度造成请求堆积)可能导致内存占用过多或OOM
-------3.ArrayBlockintQueue(有界队列):队列长度受限,当队列满了就需要创建多余的线程来执行任务
-------4.DelayedWorkQueue(优先级队列):保证添加到队列中的任务,会按照任务的延时时间进行排序,延时时间少的任务首先被获取。

6.threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建。

7.handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略
-------1.AbortPolicy:中断抛出异常
-------2.DiscardPolicy:默默丢弃任务,不进行任何通知
-------3.DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
-------4.CallerRunsPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

3.线程池的状态

1.RUNNING
(1). 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2). 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!

2.SHUTDOWN
(1). 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2). 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3.STOP
(1). 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2). 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4.TIDYING
(1). 状态说明:当所有的任务已终止,线程数为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2). 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN ->TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5.TERMINATED
(1). 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2). 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

3.线程池的关闭

shutdown():
-------1.调用之后不允许继续往线程池内继续添加线程;
-------2.线程池的状态变为SHUTDOWN状态;
-------3.所有在调用shutdown()方法之前提交到ExecutorSrvice的任务都会执行;
-------4.一旦所有线程结束执行当前任务,ExecutorService才会真正关闭。

shutdownNow():
-------1.该方法返回尚未执行的 task 的 List;
-------2.线程池的状态变为STOP状态;
-------3.阻止所有正在等待启动的任务, 并且停止当前正在执行的任务。

2.fork/join

图:forkIjoin.png
在这里插入图片描述

1.定义

把任务分割为若干互不依赖的子任务,把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应。
-------1.工作窃取(work-stealing):列队任务执行完成的线程,去其他忙碌线程的队列里窃取一个任务来执行。
-------2.双端队列:被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

2.关联类

1.ForkJoinWorkerThread:执行ForkJoin任务的线程。
2.ForkJoinPool:管理执行ForkJoin任务线程的线程池,默认线程数为cpu核心数,可自己指定数量
3.ForkJoinTask:ForkJoinPool中的任务
-------1.RecursiveAction 一个递归无结果的ForkJoinTask(没有返回值)
-------2.RecursiveTask 一个递归有结果的ForkJoinTask(有返回值)

3.例子

看此文章(Tiglle)


1.Lock

1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,并且在发生异常时,不会自动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

3)java.util.concurrent.locks包下

1.常用方法:

1.lock():加锁,执行后,后面的代码都会加锁

2.tryLock():尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

3.tryLock(long time, TimeUnit unit):和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

4.lockInterruptibly():lock()阻塞等待锁时不可被中断。入interrupt()。而lockInterruptibly()阻塞等待锁时可被中断,中断后抛出异常:InterruptedException

5.unLock();解锁

2.Lock的实现分类

1.ReentrantLock:可重入锁,一个线程可以多次获得该锁。如不可重入,一个锁调用同锁的另一个加锁方法会阻塞。

2.ReadWriteLock:读写锁,读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。

readLock():获取读锁
writeLock():获取写锁

3.CountDownLatch

这个类能够使一个线程等待其他线程完成各自的工作后再执行。

CountDownLatch cnt=new CountDownLatch(3);
指定执行数量,直到其他线程将执行数量变为0时,等待线程执行任务。其他线程和等待线程使用的是同一个CountDownLatch实例
-------1.await()方法:一直等待,直到指定线程执行完成
-------2.countDown()方法:执行完成的线程调用countDown()告诉等待线程自己执行完成,等待数量减1

2.Lock和synchronized的选择
总结来说,Lock和synchronized有以下几点不同:

1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,没有主动释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock使用乐观锁,可能使用CAS原理。可以提高多个线程进行读操作的效率。Synchronize使用悲观锁

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

3.FutureTask
1.通过实现Callable接口达成,需要搭配FutureTask或者 线程池
FutureTask

1.重写call()方法,相当于run方法,线程执行的内容。

2.需要依靠FutureTask类,FutureTask类实现了Runnable接口。将实现了Callable接口的类的实现类传入FutureTask的构造器中

3.将FutureTask类的实例传入Thread的构造器。

4.futureTask.isDone():判断执行call()的线程是否执行完成

5.futureTask.get():等待执行call()的线程执行完成在执行futureTask.get()方法,获取call()的返回值。

public static void main(String[] args) throws ClassNotFoundException, InterruptedException, ExecutionException {
	MyCallable myCallable = new MyCallable();
	FutureTask<String> futureTask = new FutureTask<String>(myCallable);
	Thread thread = new Thread(futureTask);
	thread.start();
	if(!futureTask.isDone()){
    	System.out.println("还没执行完成");
	}
	System.out.println(futureTask.get());

4.Semaphore:可控制同时获得锁的线程个数。

acquire();获取许可
release();释放许可
通过获取和释放许可控制线程的进入和退出。

5.AQS

看此文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值