并发的学习

多线程

@TOC


并发可能发生内存泄漏,上下文切换,线程安全,死锁等问题。
并发安全三要素:原子性,可见性,有序性(线程切换带来的原子性问题—CAS原子操作,synchronized,lock),缓存导致的可见性问题(synchronized,volatile,lock),编译优化带来的有序性问题(Happens-Before)
并发与并行:并发是在一定时间内执行多个任务,CPU轮转时间片运行,逻辑上看是并行的 并行是真正的同时执行
进程与线程:进程是操作系统分配资源的最小单位,线程是操作系统调度的最小单位 一个程序至少有一个进程,一个进程可以有多个线程 同一进程的线程共享进程的地址空间和资源
多线程好处:提高CPU利用率,在一个线程等待资源的时候,CPU可以运行其他的线程 坏处:需要去调度线程,还有资源竞争问题
死锁:由于资源竞争导致线程之间相互等待而导致的僵局 死锁产生的四个必要条件:互斥条件(一个资源只允许一个线程占用),不剥夺条件(不能强制夺走线程的资源),请求和保持条件(当前线程持有一个资源,还要去请求另一个被占用的资源导致阻塞时,对当前持有的资源保持不放),循环等待条件(进程资源的循环等待链
创建线程的四种方式:继承Thread类重写run方法,实现Runnable接口或者Callable接口(callable接口可以有返回值,用futuretask接收),通过Executors框架来创建线程池(定长线程池,可缓存线程池,可定时任务任务线程池,单线程池
runnable 和 callable 有什么区别?都是接口,重写run方法来编写多线程代码,通过Thread.start方法调用。但是callable接口的润方法有返回值,通过futuretask可以得到结果【Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。】
线程的 run()和 start()有什么区别?start方法用于启动线程,run方法是线程的运行时执行的代码,start只能执行一次,而run可以重复调用。 如果直接调用run方法相当于调用普通方法。
为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?只有调用start方法会启动线程,使得线程进入就绪状态,等待CPU去分配时间片,会自动运行run放大,而run方法直接运行相当于普通方法
什么是 FutureTask?表示异步执行的任务结果,与callable接口一起使用,可得到异步运算的返回结果
线程的状态和基本操作?线程的状态:创建(new 一个线程),就绪(调用start方法处于就绪状态,等待获取CPU的时间片),运行(得到时间片,执行代码),阻塞(暂时放弃对CPU的获取,等待阻塞,同步阻塞,其他阻塞),死亡。
请说出与线程同步以及线程调度相关的方法?wait(),sleep(),notify()
sleep() 和 wait() 有什么区别?一个是object类的方法,一个是Thread类的方法,wait会释放对象的锁,与线程的同步有关,而sleep仅仅只是让出CPU。wait方法被调用后直到被其他线程调用该对象的notify方法时,获得该对象的锁后才继续执行
你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?使用while循环,防止过早唤醒,因为如果有两个取钱线程,一个存钱线程…巴拉巴拉
为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?因为wait是锁级别的操作,而每个对象都是监视器锁,如果放在Thread类里面,一个线程可以有多个锁,当要释放锁的时候不知道该释放那个锁
为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?因为使用方法要释放锁,所以在释放前要获取到这个对象的锁,synchronized保证一定获取到这个对象的锁
Thread 类中的 yield 方法有什么作用?让线程从运行状态变为就绪状态
线程的 sleep()方法和 yield()方法有什么区别?sleep方法会让出时间片,装入阻塞状态。而yeiled只是变为就绪状态,下一次当前线程还有可能抢到时间片继续执行
如何停止一个正在运行的线程?1.等它正常执行完代码 2.stop方法强行停止 ,不建议使用 3.interrupt方法中断线程
Java 中 interrupted 和 isInterrupted 方法的区别?前者用于中断线程,调用该方法的线程的状态为将被置为”中断”状态。isInterrupted:查看当前中断信号是true还是false
notify() 和 notifyAll() 有什么区别?一个会唤醒所有线程,而一个只会唤醒一个线程。唤醒所有线程后重新开始锁的竞争,而唤醒一个线程由虚拟机来控制
如何在两个线程间共享数据?共享变量
Java 如何实现多线程之间的通讯和协作?线程通信可以通过1.synchronized获取对象的锁,使用wait和notify进行通信 2.使用lock锁,condition类的await和signal进行通信
同步方法和同步块,哪个是更好的选择?同步块,同步块不会锁住整个对象,虽然它可以做到,范围越小,对程序的影响越小
什么是线程同步和线程互斥,有哪几种实现方式?(解决线程冲突问题?)1.synchronized修饰的同步代码方法和块 2.volatile修饰的变量 3.可重入锁reentrantlock
什么叫线程安全?servlet 是线程安全吗?当多个线程执行方法时,都能正确的处理调用。不是,servlet是单例模式,需要考虑线程安全问题(可以使用CAS原子操作或者锁(synchronized或者lock
线程类的构造方法、静态块是被哪个线程调用的?线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
Java 线程数过多会造成什么异常?内存开销大,消耗过多CPU,线程生命周期开销高

Java中垃圾回收有什么目的?什么时候进行垃圾回收?垃圾回收是为了清除已经死亡的对象来释放内存资源,回收时间不确定,有虚拟机决定
如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?不会,等待下次垃圾回收
finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?当要标记该线程是否真的死亡要被回收时会被调用…
为什么代码会重排序?为了优化性能,在不影响执行结果的前提下会对代码运行顺序进行重排序
as-if-serial规则和happens-before规则的区别?as-if-serial规则保证单线程内程序执行结果正确,happens-before规则保证多线程同步执行的结果正确。

synchronized 的作用?synchronized是一个线程同步的关键字,使用时要获取对象的锁,不能主动释放锁,当线程长沙获取锁时,会一直阻塞,无法中断线程,保证了原子性和可见性。原子性就是只有一个线程能执行被synchronized修饰的代码块,可见性是指一个线程在修改完的结果对其他线程都可见
说一下 synchronized 底层实现原理?synchronized是java的关键字,它要获取对象的锁,每个对象都是监视器锁,在字节码指令有两个monitor指令,获取锁就是monitorer,执行完代码块释放锁就要退出
什么是自旋?当一个线程尝试获取锁,却发现该锁已被占用时不进入阻塞状态,而是一直循环检测锁是否被释放
什么是锁膨胀?多线程中 synchronized 锁升级的原理是什么?锁分为偏向锁,轻量级锁,重量级锁。锁对象的对象头有一个持有锁线程的字段ID,如果为空就持有偏向。锁,并设置ID,再次进入就判断id,一直就直接使用对象,否则就升级为轻量级锁,使用自旋来获取锁,一定次数后还没成功就升级为重量级锁
synchronized、volatile、CAS 比较?synchronized是悲观锁,抢占式获取锁。volatile提供多线程共享变量的可见性,以及进制指令重排优化,CAS是乐观锁
synchronized 和 Lock 有什么区别?一个是关键字,一个是接口。lock要手动获取释放锁,可以有选择性获取锁,还可以如果一段时间内获取不到就放弃。而synchronized只能在代码块执行完后自动释放锁,而且也不能放弃获取,synchronized是非公平锁,lock可以设置
synchronized 和 ReentrantLock 区别是什么?一个是关键字,一个是类,都是可重入锁, ReentrantLock 更加灵活…
volatile 关键字的作用?修饰多线程共享变量,保证可见性和有序性,当变量被修改时立刻更新到主存,而当读取时会先检查自己缓存的值是否过期,然后去主存里读取。关键字volatile的主要作用是使变量在多个线程间可见
synchronized 和 volatile 的区别是什么?.修饰…可见性…volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。 线程是否被阻塞 volatile是轻量级的线程同步,性能更好,但是只能用于变量

乐观锁和悲观锁的理解及如何实现,有哪些实现方式?乐观锁:版本号机制///CAS(compare and swap,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。否则处理器不做任何操作。
CAS 的会产生什么问题?1.ABA的问题(从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。) 2.循环时间长,CAS自旋久,浪费CPU资源 3.智能包装一个共享变量的原子操作

AQS:构建锁和同步器的框架,使用一个FIFO的队列表示排队等待锁的线程和一个表示状态的字段state(使用volatile修饰保证线程可见性)
什么是可重入锁?当前线程可以对资源进行重复加锁。可重入锁ReentrantLock,分为公平锁和非公平锁,可重入锁的实现依赖于AQS的一个volatile变量state来维护同步状态,非公平锁会检查state字段,如果state=0,说明锁未被占用,就尝试去获取,若不为0,说明已被占用,就检查是不是自己占用,如果是则更新state字段,表示重入锁的次数。如果以上两点都没有成功,则获取锁失败,返回false。
读写锁ReentrantReadWriteLock?是ReadWriteLock 的一个具体实现,实现了读写分离,读锁共享,写锁独占 。特性:可重入,支持公平和非公平。写锁能降为读锁

ThreadLocal 是什么?底层实现原理?有哪些使用场景?线程本地存储,是一种自动化机制。为每个线程都创建一个threadlocalMap对象,threadlocalMap是ThreadLocal的静态内部类,每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题?Synchronized是通过线程等待,牺牲时间来解决访问冲突。ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。经典使用场景是连接数据库时,为每个线程分配一个JDBC连接connection
什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型?使用经典场景?阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析。

什么是线程池?Executors类创建四种常见线程池?有哪几种创建方式?为了减少不断创建和销毁线程带来的资源损耗,使用线程池,实现创建若干个线程,对此进行调度和管理。单线程的线程池,可缓存的线程池,固定大小的线程池,无限大小支持定时任务的线程池。通过Executors框架进行创建线程池
什么是 Executor 框架?为什么使用 Executor 框架?调度控制执行异步任务的框架,利用它创建线程池
线程池都有哪些状态?1.运行状态,就受信任我,处理等待队列的任务 2.不接受新任务提交,但是会处理等待队列的任务 3.不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。 4.销毁所有任务 5.结束状态
线程池中 submit() 和 execute() 方法有什么区别?submit()可以执行 Runnable 和 Callable 类型的任务,execute()只能执行 Runnable 类型的任务。submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
Executors和ThreaPoolExecutor创建线程池的区别?规避资源耗尽的风险,Executors 各个方法的弊端:newFixedThreadPool 和 newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,newCachedThreadPool 和 newScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程
你知道怎么创建线程池吗?使用ThreadPoolExecutor 自行指定核心线程数量,允许存在的最大数量工作线程等参数
线程池的关闭方式有几种,各自的区别是什么?一种是调用其shutdown()方法,另一种是调用shutdownNow()方法。这两者是有区别的。shutdown()调用后,不可以再 submit 新的 task,已经 submit 的将继续执行
shutdownNow()调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list

AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作
在 Java 中 CycliBarriar 和 CountdownLatch 有什么区别?CountDownLatch是用于某个线程要等待其他线程执行完后才执行,而CycliBarriar 是一组线程互相等待直到所有线程都到达某个状态,在继续执行,同步屏障的await方法会阻塞线程,而CountDownLatch的countDown方法后当前线程不会被阻塞。CountDownLatch不能重新初始化或者修改计数器的值,而同步屏障的计数器可以重置
Semaphore 有什么作用?是一个信号量,来控制并发访问资源的线程数量。( synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。)

用三个线程按顺序循环打印ABC三个字母?1.利用synchronized和wait,notify方法 2.ReentrantLock+condition 3.Semaphore信号量
Java中如何安全终止一个线程?stop方法太暴力,常用Thread.interrupt() 中断线程。使用Thread.interrupt()方法处理现场中断,需要使用Thread.isInterrupted()判断线程是否被中断,然后进入中断处理逻辑代码。
如何避免死锁?1.加锁顺序 2.加锁时限 3.死锁检查


并发是为了提高运行在单处理器上的程序的性能。
上下文切换是指从当前线程切换到下一个线程时要保存当前线程的状态
阻塞:程序中的某个任务因为某种条件(比如等待I/O资源)而不能继续执行
当main()创建Thread对象时,并没有捕获任何对这些对象的引用,使用普通对象时,对于垃圾回收来说是公平的,但使用Thread时,每个Thread都“注册”了自己,因此确实有一个对它的引用,而且在它的任务退出其run()并死亡之前,垃圾回收器无法清除它。因此一个线程会创建一个独立的执行线程,在对其start()的调用完成之后,它依旧会继续存在。

javaSE5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化并发编程.Executor允许管理异步任务的执行,无需显示的管理线程的生命周期。
ExecutorService(具有生命周期的Executor)能构建恰当的上下文来执行Runnable对象。
通常单个的Executor被用来创建和管理系统中的所有任务,对shutdown()方法的调用可以放在新任务被提交给这个Executor。

不能捕获从现场中逃逸的异常,一旦异常逃出任务的run(),就会向外传播到控制台,除非你采用特殊的步骤捕获这些错误的异常。
Thread.UncaughtExceptionHander是java SE5的新接口,它允许你在每个Thread对象上都捕获一个异常处理器
我们创建一个新类型的ThreadFactory,它将在每个新创建的Thread对象上附着一个Thread.UncaughtExceptionHander


解决线程冲突问题,可以采用序列化访问共享资源的方案,意味着在给定时间只允许一个任务访问共享资源(synchronized // Lock //volatile // 原子类AtomiccInteger,AtomicLong,AtomicReference /)
防止任务在共享资源上产生冲突的第二种方案是根除对变量的共享,线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程创建不同的存储,可以由ThreadLocal

有四种线程池:可缓存线程池newCachedThreadPool,如果线程池长度超过处理需要,可以灵活回收空闲线程,若果无可用线程,则创建新线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

定长线程池newFixedThreadPool,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool,创建一个定长线程池,支持定时及周期性任务执行。Timer和ScheduledExecutorService区别
newSingleThreadExecutor,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。



死锁

死锁的几种形式,以及各自的解决方法?
1.锁顺序死锁—按照相同的顺序依次获取锁
在这里插入图片描述

2.动态的锁顺序死锁—按照相同的顺序依次锁
在这里插入图片描述

3.在协作对象间发生死锁—开放调用

4.资源死锁:线程相互等待对方的锁,线程饥饿死锁

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主存中读取共享变量

as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程结果不被改变

双重检查锁有问题
在这里插入图片描述


什么叫上下文切换//为什么并发比串行慢?

由于线程有创建和上下文切换的开销,所以并发比较慢。
因为并发执行多个线程时,CPU轮转时间片来执行线程,当前线程时间片用完后要切换到下一个线程,切换前要保存当前的状态信息,以便下次切换回来时可以继续执行。
上下文切换回影响多线程执行的速度。

如何减少上下文切换?

1.无锁并发编程     可以将数据的ID按照hash算法取模分段,不同线程处理不同段的数据。
2.CAS算法     java的Atomic包(原子类)使用CAS算法来更新数据,而不需要加上
3.使用最少线程和使用协程     避免创建不必要的线程,协程就是在单线程实现多任务的调度,并在单线程里维持多个任务间的切换

什么是死锁?死锁怎么产生的?如何避免死锁?

死锁是多个线程竞争资源而造成相互等待的僵局
死锁产生的原因:资源竞争导致资源不足或者分配不配不当,请求和释放资源的顺序不当
死锁产生的必要条件是:互斥条件,互斥条件就是指一个资源只允许一个线程占用,不剥夺条件就是不能被其他线程强制夺走资源,请求和保持条件:当前线程已经持有一个资源,又要请求另一个资源,但是另一个资源已经被其他线程占用,此时请求线程被阻塞,但是对已持有的资源保持不放,循环等待条件:存在一种进程资源的循环等待链

为什么访问同步块要获取锁?

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
因为每个对象都是一个监视器锁,当monitor被占用时就会处于锁定状态,Synchronized的语义底层是通过一个monitor的对象来完成

什么是synchronized的原子性和可见性?

原子性是指在某一时刻,只有一个线程能执行被synchronized修饰的代码
可见性是指保证一个线程在修改完的结果对之后获取锁的其他线程可见

synchronize有什么缺点?

synchronized不能主动释放锁,只能等代码块执行完或者抛出异常才能释放锁
当线程尝试获取锁的时候,如果获取不到锁会一直阻塞, 它无法中断一个正在等候获得锁的线程

在这里插入图片描述
在这里插入图片描述

讲讲volatile

volatile只保证可见性和有序性,不保证原子性
可见性是指一个线程修改了volatile修饰的值,另一个线程能读到最后的写入值
原子性:任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile的值在一个线程被修改后,会立马写入主存,其他线程如果要读这个值会先检查自己缓存的值是否过期,然后去主存里读取

自增操作不具备原子性
它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,

为什么wait() 要放在while里面?

如果使用if,设定有两个取钱线程,一个存钱线程,由于都使用了synchronized修饰,每一次都只有一个线程进行if判断,两个取钱线程一开始都走到wait()方法,然后释放锁。在存钱线程存了一次钱后,唤醒所有线程,第一个取钱线程抢到锁取光了钱,释放锁,第二个取钱线程抢到锁后悔直接进行取钱,然而此时已经没有钱,出现了过早唤醒的问题。
因此放进while里,唤醒后会继续判断

为什么wait()必须在同步方法/代码块中调用?

因为wait()方法会要求当前线程释放锁,所以要求在在此之前获得锁

为什么wait(), notify() 和 notifyAll()是定义在Object里面而不是在Thread里面?

因为wait()是锁级别的操作,java提供的锁都是对象级别的而不是线程级别的,每个对象都有锁。如果定义在Thread中,线程等待的是哪个锁就不明显了。

signal()方法后不建议添加逻辑

lock.await()后,锁就释放了,但signal()后,锁不释放,一定要在unlock()之后,锁才释放,await()才会往下执行。

既然唤醒了其他线程,又不释放锁,可以调整唤醒的时机。一般在实际代码中,也是不建议signal()方法后添加逻辑,应该直接释放锁。

wait()与sleep()的区别

sleep是Thread类的静态方法,wait是object类的方法。sleep不涉及线程间同步的概念,仅仅是让线程沉睡,但是不释放线程持有的锁,只是让出CPU。wait是为了解决线程的同步,该过程包含同步锁的获取与释放,调用wait()会将调用者对象的线程挂起,并释放该对象的锁,知道其他线程调用同一个对象的notify方法才会重新激活调用者。

::线程调用notify方法之后,只有该线程从synchronized代码执行完后,monitor才会被释放,被唤醒线程才可以得到真正执行权

什么叫可重入锁?怎么实现的?

可重入锁就是同一线程可以对资源重复加锁
每个锁有个计数器和持有者线程,比如可重入锁ReentrantLock有个state,state=0时,代表这个锁未被占用,如果计数器不为0,且持有者线程就是当前线程,那么计数器+1,如果当前线程释放一次锁,state-1

讲讲公平锁和非公平锁

公平锁就是线程按申请锁的顺序去获取锁,而不按顺序就是非公平的。
比如说可重入锁ReentrantLock,分为公平锁和非公平锁,可重入锁的实现依赖于AQS的一个volatile变量state来维护同步状态,非公平锁会检查state字段,如果state=0,说明锁未被占用,就尝试去获取,若不为0,说明已被占用,就检查是不是自己占用,如果是则更新state字段,表示重入锁的次数。如果以上两点都没有成功,则获取锁失败,返回false。
在这里插入图片描述

lock和synchronized的区别

synchronized是关键字,而lock是接口,lock可以选择性获取锁,还可以如果一段时间内获取不到就放弃,但synchronized不行,lock必要要手动释放锁,而synchronized在同步块结束时自动释放锁
synchronized是非公平锁,而lock可以设置是不是非公平锁

什么是AQS

队列同步器,是一个用来构建锁和同步容器的框架, AQS使用一个FIFO的队列表示排队等待锁的线程和一个表示状态的字段state

CAS

CAS操作(CompareAndSwap)。CAS操作简单的说就是比较并交换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。

自旋+CAS组合来实现非阻塞的原子操作)

当队列头部的线程执行完了之后,这个线程会调用后面的队列的第一个线程

CAS的缺点,怎么解决?

如果CAS失败,会一直循环进行尝试,对CPU消耗大
有ABA问题,如果在获取初始预期值和当前内存值这段时间间隔内,变量值由 A 变为 B 再变为 A,那么对于 CAS 来说是不可感知的,但实际上变量已经发生了变化;

引入版本号,每次更新版本号+1

什么是自旋锁?

当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。
好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

什么是锁膨胀

为了优化synchronized引入锁升级的机制,锁的分为无锁,偏向锁,轻量级锁,重量级锁
锁只能升级不能降级

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。(为了方便同一线程反复获取锁的情况,当一个进程访问同步块并且获得锁的时候,会在对象头和栈帧的锁记录里面存储取得偏向锁的线程ID。
下一次有线程尝试获取锁的时候,首先检查这个对象头的MarkWord是不是储存着这个线程的ID。如果是,那么直接进去而不需要任何别的操作。如果不是,那么分为两种情况。1,对象的偏向锁标志位为0(当前不是偏向锁),说明发生了竞争,已经膨胀为轻量级锁,这时使用CAS操作尝试获得锁。2,偏向锁标志位为1,说明还是偏向锁,不过请求的线程不是原来那个了。这时只需要使用CAS尝试把对象头偏向锁从原来那个线程指向目前求锁的线程。)
如果这个CAS失败了呢?首先必须明确这个CAS为什么会失败,也就是说发生了竞争,有别的线程和它抢锁并且抢赢了,那么这个情况下,它就会要求撤销偏向锁(因为发生了竞争)。接着它首先暂停拥有偏向锁的线程,检查这个线程是否是个活动线程,如果不是,那么好,你拿了锁但是没在干事,锁还记录着你,那么直接把对象头设置为无锁状态重新来过。如果还是活动线程,先遍历栈帧里面的锁记录,让这个偏向锁变为无锁状态,然后恢复线程。

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
在这里插入图片描述


集合类型线程不安全问题

1.使用vector
2.利用collections.synchronizedList(new ArrayList<>())转换成线程安全的
3.在这里插入图片描述

线程创建几种方法

1.继承Thread
2.实现Runnable或者callable 接口(区别四callable接口可以有返回值,用FutureTask)

使用线程池的好处

使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。

java中的阻塞队列

CountDwonLatch和CyclicBarrier的区别

CountDwonLatch是让线程等待其他线程完成操作,不能重新初始化或者修改计数器的值
CyclicBarrier是同步屏障,线程要到达屏障时被阻塞,直到全部线程都到达屏障,线程们才会继续运行
CountDwonLatch的计数器只能使用一次,而同步屏障的计数器可以重置,

什么是Semaphore信号量

来控制同时访问特定资源的线程数量,用于流量控制
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值