自己有了域名 怎么做网站,怎么创建教育网站,深圳建设 骏域网站建设专家,苏州网站设计多少钱1、Java 线程实现/创建方式
#xff08;1#xff09;继承Thread类
Thread类本质上是实现了Runnable接口的实例#xff0c;代表一个线程的实例#xff0c;通过start()启动#xff0c;自动执行run()方法。
#xff08;2#xff09;实现Runnable接口
Runnable是一个没有…1、Java 线程实现/创建方式
1继承Thread类
Thread类本质上是实现了Runnable接口的实例代表一个线程的实例通过start()启动自动执行run()方法。
2实现Runnable接口
Runnable是一个没有返回值的线程任务类Java有两种方式进行实现
1、自定义线程类实现Runnable接口覆写run方法在主程序中利用Thread类构造器传入自定义线程覆盖默认Thread实例2、利用匿名类在Thread构造器中传入匿名类覆盖默认Thread实例
3ExcutorService、Callable、Future 含返回值的线程
若需要执行带有返回值的任务必须实现Callable接口执行Callable任务后可以获取一个Future对象等待结果返回利用get方法获取返回值利用ExcutorService实现线程池执行任务实现带有返回值的多线程
4线程池
线程池是为了解决线程的创建与销毁带来的系统资源消耗问题而实现的缓存策略
Java提供了四种线程池
利用 Excutors 的静态方法进行常见线程池其实际上的顶级父类是ExcutorService接口
newCachedThreadPool 根据需要任务数量创建线程并设立一个缓存时间 每调用一次execute都会遍历一次线程池是否有可用线程若没有可用线程则创建一个新的线程执行任务若一个线程超过60秒未使用该线程将会被回收。 newFixedThreadPool 创建一个固定线程数的线程池以共享的无界队列方式来运行这些线程 若全部线程都被任务占用则新任务会保持在任务队列中等待调度执行。若一个线程因为任务异常导致线程结束则会重新创建一个线程调度任务队列的任务进行执行。 newScheduledThreadPool 创建一个具有定时功能的线程池利用schedule()添加任务并设置执行周期newSingleThreadExcutor 创建一个只有一个线程的线程池当一个线程因为任务异常而结束内部重新创建一个新线程替代旧线程执行新任务。 2、线程的生命周期
线程的生命周期一共有五种新建、就绪、运行、阻塞、死亡
新建状态New 当程序使用 new 关键字创建一个线程后线程处于新建状态此时JVM为其分配内存并初始化成员变量的值。就绪状态Runnable 当线程对象调用 start() 之后线程处于就绪状态JVM为其创建方法调用栈和程序计数器等待调度执行。运行状态Running 处于就绪状态的线程获得了CPU开始执行 run() 的线程方法体。阻塞状态Blocked 线程因为某些原因放弃了CPU使用权暂停运行直到线程进入可运行状态。有三种阻塞情况 1、等待阻塞 线程对象执行wait()JVM 会把线程放入等待队列waiting queue中。2、同步阻塞 运行状态的线程在获取同步锁时若该同步锁被其它线程占用则JVM 会把线程放入锁池中。3、其它阻塞 运行状态的线程执行sleep()或join()当发起IO请求时JVM会把该线程置为阻塞状态当sleep状态超时或join()等待线程终止或者超时或IO处理完毕时线程重新转入可运行状态。 线程死亡 线程死亡有三种方式 1、run 或 call。方法执行完成线程正常结束2、线程抛出一个未捕获的 Exception 或 Error3、调用线程对象的stop()可能会导致死锁的发生 3、终止线程
终止线程的四种方式
1、正常结束 程序运行结束线程自动结束。2、使用退出标志退出线程 当一个run()执行完成线程就自动结束。当线程需要一个条件进行退出则可以使用一个boolean类型的标志进行退出。可以使用valatile同步退出标志3、调用Interrupt() 1当线程处于阻塞状态时列入使用了sleep同步锁的wait()socket的receiver.accept()使得线程处于阻塞状态当方法体调用了interrupt()方法时会抛出InterruptException异常只有线程捕获到了该异常才能让该线程正常结束。2当线程处于非阻塞状态时使用 isInterrupt() 判断线程的中断标志来退出循环当使用interrupt()时会将中断标志设为true。 4、调用stop() 调用thread.stop()后创建子线程的线程会抛出ThreadDeathError的错误并且释放子线程持有的所有锁这种操作可能会产生数据不一致所以通常不会使用这种方式结束线程。 4、sleep 与 wait
1sleep 属于Thread类方法线程对象调用该方法导致程序暂停执行指定的时间并且让出CPU但依旧持有对象锁并且保持监控状态当时间过期后自动恢复运行状态。2wait 属于Object类方法线程对象调用该方法导致线程对象进入等待锁定池中并且释放对象锁只有针对该对象调用 notify() 时本线程才会进入对象锁定池准备获取对象锁进入运行状态。 5、start 与 run
1start 线程调用该方法真正实现多线程执行无需等待run方法执行完毕继续执行代码此时的线程处于就绪状态并不运- 行。2run 线程调用该方法使得就绪状态的线程开始运行run函数中的代码run方法结束即线程结束。 6、Java后台线程
1概念 后台线程也叫**“守护线程”**它是为用户线程提供公共服务的线程在没有用户线程时自动离开。2优先级 守护线程是一种公共线程所以其优先级较其它线程低。3设置 在用户线程对象创建之前用线程对象的setDaemon(true)来设置线程为守护线程。4子线程创建 Daemon线程创建的子线程也是Daemon线程。5并发性 Daemon线程只存在于JVM只有停止JVM的运行才能销毁Daemon线程。6案例 GC线程是一个经典的守护线程只有在有可回收对象时才会执行服务方法始终以低级别的状态运行。7生命周期 与JVM生命周期挂钩当所有的JVM线程都为守护线程时JVM就可以退出了。 7、Java锁
1乐观锁 获取数据不需要锁而更新时先获取版本号若数据版本与存储的版本相同则获取锁进行更新若数据版本与存储的版本不同则重复 读 - 比较 - 写 的操作。Java中的乐观锁是通过CAS操作实现。
2悲观锁 读写数据都需要获取锁才能进行。Java中的Synchroized 就是一种悲观锁。而AQS框架下的锁是先尝试CAS乐观锁获取锁若获取不到则转为悲观锁获取例如ReetrantLock。
3自旋锁 是一种获取锁的机制若持有锁的线程能够在短时间内释放锁资源则等待竞争的线程不需要做内核态和用户态的切换进入阻塞挂起状态只需要等待一点时间自旋等待持有锁的线程释放锁后立即可以获取锁。因为自旋是一种持续在CPU中不断尝试获取锁的方式所以会持续占用CPU导致CPU做无用功所以通常需要设置一个自旋等待的最大时间时间到达还未获取锁资源则停止自旋并进入阻塞状态。
自旋锁的优缺点
自旋锁不同场景的性能提升情况锁竞争不激烈的场景 大幅度提升程序性能因为自旋的损耗小于阻塞再唤醒的损耗锁竞争激烈的场景或是持有锁的线程长期占用锁执行同步块 不适用自旋锁因为自旋随时间的拉长而损耗变大所以需要关闭自旋锁。
自旋锁如何选择自旋执行时间 在JDK 1.5 时自旋时间是固定的JDK 1.6 引入了适应性自旋锁通过前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定一个上下文切换的时间是一个最佳自旋时间。
自旋锁的开启
JDK 1.6 使用 -XX:UseSpinning 开启-XX:PreBlockSpin10 设置自旋次数JDK1.7后由JVM控制开启自旋
4Sychronized 同步锁
sychronized 是一种可以将任意一个非NULL对象作为锁的实现方式属于独占式的悲观锁同时属于可重入锁。
Sychronized 作用范围 1、作用于方法时锁住的是对象的实例this2、作用于静态方法时锁住的是Class实例由于Class的相关数据存储在永久代元数据区所以锁住静态方法相当于锁住了所有调用该方法的线程3、作用于一个对象实例时锁住的是所有以该对象为锁的代码块它有多个队列当多个线程一起访问某个对象监视器时对象监视器会将这些线程存储在不同的容器中。 Sychronized 核心组件 1、Wait Set 调用wait()的线程放置在这里。2、Contention List竞争队列所有请求锁的线程首先放置在该队列。3、Entry List竞争队列中可竞争的线程移动到该队列。4、OnDesk 任意时刻最多只有一个线程竞争锁资源该线程被称为OnDesk5、Owner 当前获取锁的线程对象6、!Owner 当前释放锁的线程对象 Sychronized 实现 1、JVM从Contention List的队尾取出一个对象作为OnDesk在并发情况下会从Contention List取出一部分移动如Entry List作为OnDesk候选线程队列2、Owner线程在unlock时会从Contention List 迁移出部分线程到Entry List并指定一个线程为OnDesk一般是第一个进入的线程3、Owner线程释放锁时成为OnDesk的线程竞争锁的优势最大牺牲一定的公平性换取提高系统吞吐量这种行为被称为竞争切换4、OnDesk获取锁后成为Owner而未获取锁的线程会留在Entry List 中当Owner线程被wait阻塞则自动释放锁并且转入Wait Set直到notify或notifyAll转入Entry List5、处于Wait Set、Contention List、Entry List的线程都处于阻塞状态这种状态是由OS来完成的6、Sychronized 是非公平锁在线程进入Contention List 之前会通过自旋来抢占竞争锁资源列表中线程对象的锁资源包括OnDesk7、对象加锁是通过monitor对象进行的monitorenter和monitorexit指令组成了加锁的范围通过标记位来判断是否方法是否加锁8、Sychronized 是重量级锁需要调用OS相关接口性能较低9、Java 1.6 对sychronized 进行了优化适应性自旋、锁清除、锁粗化、轻量级锁及偏向锁等Java 1.7 和 1.8 对关键字实现机制进行优化10、锁膨胀锁可以从偏向锁升级到轻量级锁再升级到重量级锁11、JDK 1.6中默认开启偏向锁和轻量级锁通过-XX:-UseBiasedLocking来禁用偏向锁
5ReetrantLock
ReentantLock 是一种可重入锁继承并实现接口 Lock除了能完成synchronized所有工作外还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
Lock接口的主要方法
1、void Lock() 调用该方法时若锁空闲则直接获取锁若被占用则阻塞当前线程直到获取锁2、boolean tryLock()调用该方法尝试获取锁若锁空闲则获取锁并返回true否则返回false3、void unlock()调用该方法使当前线程释放锁若未持有锁则可能会发生异常。4、Condition newCondition() 条件对象获取等待通知组件。该组件只有在获取锁之后才能使用当前线程调用await()当前线程释放锁。5、getHoldCount() 查询当前线程执行lock方法的次数6、getQueueLength() 返回正在等待锁释放的线程数7、getWaitQueueLength(Condition condition) 返回使用同一个条件对象并且执行了await()的线程数8、boolean hasWaiters(Condition condition) 查询是否存在使用指定条件对象并执行await()的线程9、boolean hasQueuedThread(Thread thread) 查询指定线程是否在等待锁10、boolean hasQueuedThreads 是否有等待当前锁11、boolean isFairO 查询该锁是否为公平锁12、boolean isHeldByCurrentThread当前线程是否被锁定主要是当前线程是否执行了lock()13、boolean isLock()此锁是否有线程使用14、lockInterruptibly() 如果当前线程未被中断获取锁15、boolean tryLock() 尝试获取锁若能获取锁则true若未能获取则false16、boolean tryLock(long timeout, TimeUnit unit) 若锁在规定时间内未被一个线程获取则获取锁
ReentrantLock 与 Synchronized的优势
ReentrantLock 通过lock()与unlock()进行加解锁并且不交由JVM进行控制需要手动解锁具有可中断、公平锁、可多个锁的特性。
ReetrantLock 实现
public class MyService {//1、获取ReetrantLock通过布尔值来控制锁是否为公平锁private Lock lock new ReetrantLock(true);//2、创建条件等待对象private Condition condition lock.newCondition();//3、创建服务方法public void testMethod(){try{//4、加锁lock.lock();//5、执行await();condition.await();//6、唤醒线程condition.signal();//处理数据...}catch(InterruptedException e){e.printStackTrace;}finally{//释放锁lock.unlock();}}}Condition 与 Object 锁方法的区别
1、await 与 wait 等效2、signal 与 notify 等效3、signalAll 与 notifyAll 等效4、ReetrantLock 可指定符合条件的线程唤醒而Object唤醒是随机的
tryLock、lock、lockInterruptibly的区别
1、tryLock 在可获取锁时直接获取锁并返回true若未获取锁则返回false可以通过增加等待时间进行尝试获取锁若超时则直接返回false2、lock 能获取锁就返回true不能就一直等待获取锁3、lock 和 lockInterceptibly两者都能尝试获取锁都可以因为中断而停止获取锁但是lock不会抛出异常而lockInterruptibly会抛出异常。
6Semaphore 信号量
Semaphore 是一种基于计数的信号量可以设定一个阈值用来控制申请资源的线程数当多线程进行竞争资源时就会申请信号量若申请的信号量超过阈值则申请的线程将被阻塞直到有线程归还信号量。 通常信号量被用来限制共享资源池。
通过Semaphore 实现互斥锁 互斥锁就是只有一个线程获取锁即Semaphore信号量为1.
Semaphore 与 ReentrantLock Semaphore 基本能完成ReentrantLock的所有工作适用方法也相似。
获取锁acquire() 默认使用可响应中断锁—— lockInterruptibly()释放锁release() —— unlock()尝试获取锁 tryAcquire() —— tryLock()同样提供可轮询的锁请求与定时锁的功能同样提供公平锁与非公平锁的机制并也可以在构造函数中设定锁释放都手动释放所以都需要在finnaly代码块中完成
7AtomicInteger
AtomicInteger是一种提供原子操作的Integer类相同的还有 AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等可以通过 AtomicReferenceV 将一个对象的所有操作都转为原子操作。
8可重入锁
可重入锁也叫递归锁指的是当外层函数获取锁时内层递归函数仍可以执行递归操作不受锁的影响。
9公平锁与非公平锁
公平锁Fair 加锁前检查是否有排队等待的线程优先排队等待的线程按照顺序进行加锁。
非公平锁Nonfair 加锁前不需要考虑是否有排队等待的线程若无法直接获得锁则自动在队尾等待。性能优于公平锁
10读锁与写锁
为了提高性能Java提供读写锁在读的方法上加读锁在写的方法上加写锁读写锁是互斥锁读锁之间并不互斥。
读锁 可供多线程进行读数据但不能与写操作进行并发。 写锁 只能单线程进行写数据且互斥读操作
Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock也有更加具体的实现ReentrantReadWriteLock
11共享锁与独占锁
Java提供了两种加锁模式共享锁和独占锁
独占锁模式 每次只有一个线程能获取锁属于悲观锁加锁策略避免了读/读冲突。
共享锁模式 共享锁模式允许多个线程同时获取锁并发访问共享资源属于乐观锁加锁策略。
1、AQS内部的Node定义了两个常量 SHARED 和 EXCLUSIVE标识等待线程的锁获取方式2、Java并发包的读写锁允许一个资源被多个读操作访问或被一个写操作访问但两者不能同时进行
12重量级锁与轻量级锁
重量级锁
一种依赖于操作系统的Mutex Lock来实现的一种锁所以重量级锁需要频繁切换用户态和内核态而状态的转换需要很长的时间所以消耗的资源也较多在开发过程中应尽量减少对重量级锁的使用。
轻量级锁
锁的状态有四种按锁升级过程进行排序无锁、偏向锁、轻量级锁、重量级锁
轻量级锁是相对于利用操作系统的互斥量来实现的传统锁而言的轻量级锁使用的场景是在线程交替访问同步代码块时同一时间访问同一把锁时就会触发锁升级过程。
13偏向锁
偏向锁的作用是在某个线程在获得锁之后消除这个锁重入的开销偏向锁只需要置换ThreadID时依赖一次CAS原子指令所以在只有一个线程执行同步代码块时进一步提高线程。
14分段锁
分段锁不是实际意义上的锁而是将数据区域分割成一段一段的每一次线程访问都只访问一段数据从而形成多线程访问同一容器内的数据。
15锁优化
减少锁持有时间 只在需要线程安全的线程上加锁减小锁粒度 在大对象的某些需要线程安全的代码块上加锁锁分离 典型的有 读写锁根据功能进行分离读锁和写锁。锁粗化 为保证多线程之间有效并发要求线程持有锁的时间尽量缩短即在使用完资源后立即释放锁但频繁的获取/释放锁产生的资源浪费会很高。锁消除 锁消除是编译器级别的策略若在即时编译器时若发现不可能共享的对象则可以消除这些对象的所操作大多数情况都是因为编码不规范引起的。
8、线程基本方法
线程的基本方法有 wait、notify、notifyAll、sleep、join、yield等
1线程等待 wait 调用该方法使得当前线程进入WATTING状态直到被唤醒但需要注意的是调用wait方法会释放锁因此wait一般用于同步方法或同步代码块中。
2线程睡眠 sleep 调用该方法使得当前线程进入WATTING状态被notify或notifyAll或过期则唤醒但需要注意的是调用sleep方法不会释放锁。
3线程让步 yield 调用该方法使得当前线程让出CPU进入竞争队列中。
4线程中断 interrupt 中断一个线程给这个线程一个通知信号会影响这个线程内部的一个中断标识位这个线程不会改变自身状态。
interrupt() 无法中断处在RUNNING状态的线程interrupt() 可以中断处于TIME_WATTING状态的线程在声明抛出InterruptedException异常之前会清除标记中断状态可以安全的终止一个线程
5等待线程终止 join 当前线程调用join()则当前线程会转入BLOCKING状态直到另一个线程结束当前线程由BLOCKING转为RUNNABLE状态等待CPU调度。
join使用场景 当主线程需要等待子程序返回结果时主线程需要调用子线程的join()方法当主线程再次被CPU调度就可以获取子线程返回的结果。
6线程唤醒 notify 随机唤醒指定对象监视器中的单个线程。
7其它方法
1、isAlive()判断一个线程是否存活。2、activeCount()程序中活跃的线程数。3、enumerate(): 枚举程序中的线程。4、currentThread()得到当前线程。5、isDaemon() 一个线程是否为守护线程。6、setDaemon()设置一个线程为守护线程。(用户线程和守护线程的区别在于是否等待主线 程依赖于主线程结束而结束)7、setName()为线程设置一个名称。8、setPriority(): 设置一个线程的优先级。9、getPriority(): 获得一个线程的优先级。 9、线程上下文切换
CPU利用时间片轮转的方式为每个任务都服务一定时间然后将当前任务状态保存下来再加载下一个任务的状态后继续服务下一任务。 “任务的状态保存及再加载的过程叫做上下文切换” 10、同步锁与死锁
1同步锁 由于多线程同时访问一个数据时可能会出现数据不一致的问题所以需要保证线程同步互斥即并发执行的多个线程在同一时间内只允许一个程序访问共享资源Java中 使用了 Synchronized 关键字来取得一个对象的同步锁。
2死锁 多个线程同时被阻塞它们中的一个或多个都在等待某个资源被释放。 11、线程池原理
线程池的作用是线程可复用、控制最大并发数、管理线程。
1线程复用 通过继承重写Thread类在其 start 方法中添加不断循环调用Queue传递过来的Runnable对象调用其run()方法即可实现线程复用机制。
2线程池的组成
线程池管理器 用于创建并管理线程池工作线程池 线程池中的线程任务接口 每个任务必须实现的接口用于工作线程调度其运行任务队列 用于存放待处理的任务提供一种缓冲机制
3线程池参数
corePoolSize 指定线程池的核心线程数量maximumPoolSize 指定线程池最大线程数量keepAliveTime 临时线程空闲时间unit 时间单位workQueue 任务队列存放等待执行的任务threadFactory 线程工厂用于创建线程一般用于默认即可handler 拒绝策略当线程池线程全员忙碌时采用的拒绝任务策略
4线程池有哪些拒绝策略
AbortPolicy 直接抛异常结束程序执行CallerRunsPolicy 交付给调用者线程进行执行即串行化执行该任务DiscardOldestPolicy 丢弃等待时间最久的任务并尝试提交当前任务DiscardPolicy 直接丢弃任务且不抛出异常 12、线程池工作过程
1创建新的线程池池中没有线程任务队列作为参数传入 2调用execute()方法时线程池做判断
如果正在使用的线程数小于核心线程数则马上创建线程运行该任务如果正在使用的线程数大于等于核心线程数则将这个任务放入任务队列等待执行如果任务队列达到满阈值且正在运行的线程数小于线程池最大线程数则创建临时线程调度执行任务如果任务队列达到满阈值且正在运行的线程数大于等于线程池最大线程数则按照拒绝策略执行
3当一个线程完成任务后调度任务队列中的任务执行 4当一个线程空闲时间达到线程池最大空闲时间线程池中运行线程数大于核心线程数则停止该线程直至等于核心线程数 13、阻塞队列原理BlockQueue
1线程阻塞的两种情况
当队列中没有数据时消费者端的所有线程将被阻塞挂起直到有数据放入队列当队列中充满数据时生产者段的所有线程将被阻塞挂起直到队列中有空位置
2阻塞队列的主要方法
方法类型抛出异常特殊值阻塞超时插入方法add(e)offer(e)put(e)offer(e, time, unit)删除方法remove()poll()take()poll(time, unit)检查方法element()peek()不可用不可用
抛出异常 抛出一个异常特殊值 返回一个特殊值null 或 false阻塞 在成功操作之前一直阻塞线程超时 放弃前在最大时间内阻塞
3Java中的阻塞队列
SynchronousQueue不存储元素的阻塞队列每一个put操作必须等待take操作否则无法添加元素到队列所以SynchronousQueue一般用来做传递队列可以高效解决队列与队列之间数据流通问题。PriorityBlockingQueue支持优先级排序的无界阻塞队列默认情况下采用自然顺序的升序排列通过自定义实现compareTo()指定排序规则或初始化PriorityBlockingQueue时指定构造器中的Comparator来对元素进行排序。DelayQueue使用优先级队列实现的延时无界阻塞队列队列中的元素必须实现Delayed接口在创建元素时可以指定可访问新元素的时间间隔。应用场景有缓存系统设计、定时任务调度。ArrayBlockingQueue由数组实现的有界阻塞队列按照FIFO的原则对元素进行排序默认情况下采用非公平策略进行访问队列但ArrayBlockingQueue构造器提供开启公平策略的参数。LinkedBlockingQueue由链表实现的有界阻塞队列按照FIFO的原则对元素进行排序针对生产者端和消费者端分别采用独立的锁控制数据同步保证生产者与消费者并行操作队列中的数据提高了整个队列的并发性能LinkedBlockingQueue默认拥有Integer.MAX_VALUE大小的容量。LinkedTransferQueue由链表组成的无界阻塞队列相较于别的阻塞队列多了两个核心功能点 transfer() 当消费者正在等待接收元素transfer方法可以马上将生产者传入队列的值传递给消费者而当没有消费者等待时队列将传入的值存储到tail节点中等待消费者请求接收。tryTransfer() 用来尝试询问消费者是否接收队列中的值若没有消费者等待接收元素则返回false。tryTransfer(long time, TimeUnit unit) 用来做一定时间内等待消费者接收元素若这段时间内未被消费则返回false。 LinkedBlockingDeque由链表结构组成的双向阻塞队列对比LinkedBlockingQueue多了双向操作方法能够有效减少线程的IO竞争提高了线程的读写效率 14、CyclicBarrier、CountDownLatch、Semaphore
1CountDownLatch (线程计数器)
CountDownLatch类位于java.util.concurrent包下利用它可以实现类似计数器的功能。
例如有 一个任务A,它要等待其他4个任务执行完毕之后才能执行。
final CountDownLatch latch new CountDownLatch(2);new Thread(){()-{System.out.println(子线程Thread.currentThread().getName()正在执行);Thread.sleep(3000);System.out.println(子线程Thread.currentThread().getName()执行完毕);latch.countDown();};
}.start();new Thread(){()-{System.out.println(子线程Thread.currentThread().getNameO正在执行);Thread.sleep(3000);System.out.println(子线程Thread.currentThread().getNameO执行完毕);latch.countDown();};
}.start();System.out.println(等待2个子线程执行完毕...);
latch.await();
System.out.println(2个子线程已经执行完毕);
System.out.println(继续执行主线程);2CyclicBarrier回环栅栏-等待至barrier状态再全部同时执行
它可以实现让一组线程等待至某个状态之后再全部同时执行当所有等待线程都被释放以后CyclicBarrier可以被重用暂且把这个状态就叫做 barrier当调用await()方法之后线程就处于barrier 了。
CyclicBarrier中最重要的方法就是await方法
public int await() 用来挂起当前线程直至所有线程都到达barrier状态再同时执行后续任务public int await(long timeout, TimeUnit unit) 让这些线程等待至一定的时间如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。
3Semaphore 信号量-控制同时访问的线程个数
Semaphore可以控制同时访问的线程个数通过 acquire。获取一个许可如果没有就等待而release()释放一个许可。 Semaphore类中阻塞型方法 public void acquire() 用来获取一个许可若未获得许可则会一直等待直到获得许可。public void acquire(int permits) 获取 permits 个许可public void release() 释放已获得的许可public void release(int permits) 释放 permits 个许可 Semaphore类中非阻塞型方法 public boolean tryAcquire() 尝试获取一个许可若获取成功则立即返回true若获取失败则立即返回falsepublic boolean tryAcquire(long timeout, TimeUnit unit) 尝试获取一个许可若在指定的时间内获取成功则返回true,否则返回falsepublic boolean tryAcquire(int permits) 尝试获取permits个许可若获取成功则返回true若获取失败则返回falsepublic boolean tryAcquire(int permits, long timeout, TimeUnit unit) 尝试获取 permits 个许可若在指定的时间内获取成功则返回true,否则返回falseavailablePermits() 得到可用的许可数目 15、volatile关键字的作用变量可见性、禁止重排序
Java语言提供了一种稍弱的同步机制即volatile变量用来确保将变量的更新操作通知到其他线程。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方因此在读取volatile类型的变量时总会返回最新写入的值所以 volatile 变量适合作为一个共享变量多个线程可以直接给这个变量赋值。
1volatile变量具备两种特性变量可见性、禁止重排序
变量可见性 保证该变量对所有线程可见当一个线程修改了变量的值其他线程是可以立即获取新的值。禁止重排序 volatile禁止了指令重排
2volatile变量实现共享的原理 对非volatile变量进行赋值每个线程必须先从内存拷贝变量到CPU Cache如果计算机有多个CPU那么变量可能会被拷贝到不同的CPU Cache所以JVM保证每次读变量都从主内存中读跳过CPU cache。
3volatile变量适用场景
对变量的写操作不依赖于当前值(i),或者是单纯的变量赋值(boolean flag true)不同的volatile变量之间不能互相依赖只有在状态真正独立于程序内其他内容时才能使用volatile 16、如何实现线程之间共享数据
Java里面进行多线程通信的主要方式是共享内存共享内存主要的关注点有三个可见性、有序性、原子性。
JMM恰好解决了可见性和有序性用锁解决了原子性。
常见实现方法
1数据操作代码抽象到一个方法中并加上“synchronized”获取同步锁保证其同步性2将Runnable对象作为一个类的内部类将共享数据作为这个类的成员变量每个线程对共享数据的操作也封装在外部类中以便实现对数据的操作同步性和互斥性作为内部类的Runnable对象调用操作类。 17、ThreadLocal的作用
ThreadLocal的作用是提供线程内的局部变量这种变量在线程的生命周期内起作用减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
1ThreadLocalMap 线程的一个属性
每个线程中都维护了一个ThreadLocalMap对象可以将本线程的对象存储在其中各线程可以对应访问到自己的对象。将一个共用的ThreadLocal静态实例作为key将不同对象的引用保存到不同线程的ThreadLocalMap中然后在线程执行的各方法中通过这个静态ThreadLocal实例的 get() 方法获取自己线程保存的那个对象避免了将这个对象作为参数传递的麻烦。 2使用场景
最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。 18、sychronized 和 ReentrantLock 的区别
1共同点
都是用来协调多线程对共享对象、变量的访问都是可重入锁同一线程可以多次获得同一个锁都保证了可见性和互斥性
2不同点
ReentrantLock显示的获得、释放锁而synchronized隐式获得释放锁ReentrantLock可响应中断、可轮回而synchronized是不可以响应中断的为处理锁的不可用性提供了更高的灵活性ReentrantLock是API级别的而 synchronized 是 JVM 级别的ReentrantLock可以实现公平锁ReentrantLock可以通过Condition可以绑定多个条件底层实现不同synchronized是同步阻塞使用的是悲观并发策略lock是同步非阻塞采用的是乐观并发策略Lock是一个接口而synchronized是Java中的关键字synchronized是内置的语言实现。synchronized在发生异常时会自动释放线程占有的锁因此不会导致死锁现象发生而Lock在发生异常时如果没有主动通过unlock()释放锁则很可能造成死锁现象 因此使用Lock时需要在finally块中释放锁。Lock可以让等待锁的线程响应中断而synchronized却不行使用synchronized时 等待的线程会一直等待下去不能够响应中断通过Lock可以知道有没有成功获取锁而synchronized却无法办到Lock可以提高多个线程进行读操作的效率即实现读写锁等 19、ConcurrentHashMap 并发
1减小锁粒度
减小锁粒度是指缩小锁定对象的范围从而减小锁冲突的可能性从而提高系统的并发能力ConcurrentHashMap的锁粒度恰好因为分段锁而缩小使得达到线程安全的成本减少。
2ConcurrentHashMap 分段锁
ConcurrentHashMap它内部细分了若干个小的HashMap称之为段(Segment)。
默认情况下ConcurrentHashMap被进一步细分为16个段即可以被16个线程并行访问16个不同的段。
若需要向ConcurrentHashMap添加一个新的表项首先根据hashCode判断表项应存储的段然后向对应段加锁并完成put操作只要多线程不在同一个段内进行put操作那么线程间就可以做到真正的并行。
3ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成
Segment的数据结构与HashMap类似其是一种可重入锁ReentrantLock在ConcurrentHashMap扮演锁的角色。 HashEntry一种链表结构的元素用于存储键值对数据。
每个Segment守护一个HashEntry数组里的元素当HashEntry数组的数据进行修改时必须先获得它对应的 Segment 锁 20、Java中的线程调度
1抢占式调度
抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制。
系统控制指的是在系统某种运行机制下可能每条线程都分同样的执行时间片也可能是某些线程执行的时间片较长甚至某些线程得不到执行的时间片。在这种机制下一个线程的堵塞不会导致整个进程堵塞。
2协同式调度
协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行。
线程的执行时间由线程本身控制线程切换可以预知不存在多线程同步问题。如果一个线程编写有问题运行到一半就一直堵塞那么可能导致整个系统崩溃。
3JVM的线程调度实现抢占式调度
Java中线程按照优先级分配CPU时间片运行且优先级越高越优先执行但优先级高并不代表能独自占用执行时间片优先级低也不代表不会占用执行时间片。
4线程让出cpu的情况
当前运行线程主动放弃CPUJVM暂时放弃CPU操作例如调用 yield() 方法。当前运行线程因为某些原因进入阻塞状态例如阻塞在I/O上当前运行线程结束 21、进程调度算法
在操作系统中有三种进程调度算法优先调度算法、高优先权优先调度算法、基于时间片的轮转调度算法
1优先调度算法
先来先服务调度算法FCFS 当作业调度中采用该算法时每次调度都是从后备作业队列中选择一个或多个最先进入队列的作业将他们调入内存为它们分配资源、创建进程然后放入就绪队列在进度调度中采用FCFS算法每次调度都是从就绪队列中选择一个最先进入队列的进程为其分配处理机该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。短作业(进程)优先调度算法 短作业优先算法SJF是从后备队列中调入一个预计运行时间最短的作业将它们调入内存运行。 短进程优先算法SPF是从就绪队列中调入一个预计运行时间最短的进程将处理机分配给它使它立即执行直到完成或发生某事件而阻塞放弃处理机时再调度。
2高优先权优先调度算法 为解决紧迫型作业使之进入系统后便获得优先处理引入最高优先权优先调度算法FPF。
当用于作业调度时系统从后备队列中选择若干个优先权最高的作业装入内存。 当用于进程调度时系统将处理机分配给就绪队列中优先权最高的进程。
非抢占式优先权调度算法 由于线程无法抢占CPU如果CPU分配给优先级最高的进程那么其它进程必须等待该进程结束才有机会获得CPU执行程序。主要用于批处理系统中也可以用于某些对实时性不高的实时系统。抢占式优先权调度算法 由于线程可抢占CPU如果CPU在执行某个优先级最高的进程当有一个优先级更高的进程请求CPU则CPU放弃当前进程保存当前进程的状态后调度优先级更高的进程执行程序。主要用于实时要求高的实时系统以及对性能要求较高的批处理和分时系统中。高响应比优先调度算法极为优秀的调度算法 由于线程可能会长时间等待CPU调度而无法得到执行所以引入了动态优先权的概念动态优先权指的是作业优先权随着等待时间的增长以一定的增长速率进行提高长作业在等待一定时间后必然能分配到CPU进行处理。 当作业等待时间相同时则按照服务时间越短优先级越高适用于短作业当作业服务时间相同时则按照作业等待时间越长优先级越高实现了FCFS针对长作业该算法也会因为响应比计算让它得到CPU的优先执行避免了长时间的等待导致服务崩溃。
3基于时间片的轮转调度算法
时间片轮转算法 早起的时间片轮转算法是系统将就绪进程按照FCFS进行排列每一次调度时都会将CPU分配给队首进程并令其执行一个时间片一个时间片的时间从几ms到几百ms当执行时间用完则由一个计时器发出时钟中断请求调度程序停止当前进程执行并将它送往就绪队列的队尾再将CPU分配给队首进程循环往复直至完成所有进程任务。多级反馈队列调度算法 通过多级就绪队列每个队列的优先级不同根据每个队列的优先级分配不同的时间片优先级高的分配的时间片长。当一个进程进入就绪队列首先会分配到第一就绪队列根据FCFS原则等待时间片执行若时间片结束但未完成进程任务则将该进程分配到第二就绪队列根据FCFS原则等待时间片执行若仍然未完成则继续向后排列直至完成。若优先级高的队列空置时CPU才会去执行优先级低的队列当有新进程进入高优先级队列则CPU将当前执行的线程放入当前队列的队尾优先执行优先级高的进程。 22、什么是CASCompare And Swap
1概念 CAS意思是比较并交换通过对比存储的值与旧值是否相同来确定是否数据被修改。 2三个参数 VEN
V 更新的变量内存值E 期待值旧值N 新值 3更新机制 当且仅当 V E才能将 V 设置为 N若 V ! E则不进行操作最终 CAS 返回 V 的值。 4CAS的线程安全原理 CAS采用乐观锁原则当多个线程同时使用CAS操作一个变量时总是只有一个线程能够完成更新其它线程全部失败基于这样的原理CAS也能够实现变量更新的同步性。 5原子包 java.util.concurrent.atomic 这个包提供了一组原子类这样的原子操作能在多线程环境下具有排他性CAS是基于CPU指令集的操作对比需要依赖CPU切换的算法性能更好。 6ABA问题 ABA问题是CAS算法因时间差而产生的数据不一致问题但因为最终结果依旧为原结果所以这个过程依旧能让CAS操作成功有一部分的乐观锁通过版本号对比的方法来解决ABA问题。 23、什么是AQS
AQSAbostractQueueSynchronizer 抽象队列同步器定义了一套多线程访问共享资源的同步器框架许多同步类实现都依赖于AQS。例如常用的 ReentrantLock、Semaphore、CountDownLatch
AQS维护了一个 volatile int state代表共享资源和一个FIFO线程等待队列多进程竞争资源被阻塞会进入等待队列。
1共享资源 state 的访问方式有三种
getState()setState()compareAndSetState()
2AQS的两种资源共享模式
Exclusive独占模式—— ReentrantLockShare共享模式—— Semaphore/CountDownLatch
AQS只是一个框架具体资源的获取/释放需要交给自定义同步器去实现AQS定义了一个接口
自定义同步器实现获取具体资源通过state的get/set/CAS方法进行获取之所以state没有被定义为 abstract是因为独占模式下只用实现tryAcquire-tryRelease而在共享模式下只用实现tryAcquiredShared-tryReleaseShared如果都定义成 abstract那么每个模式也要去实现另一个模式下的接口不同的自定义同步器在实现时只需要实现共享资源state的获取与释放的方式即可至于具体线程等待队列的维护AQS已经在顶层实现好了。
自定义同步器实现时主要实现方法
isHeldExclusively() 该线程是否正在独占资源只有用到 condition 才需要去实现它。tryAquire(int) 独占方式尝试获取资源成功则true失败则falsetryRelease(int) 独占方式尝试释放资源成功则true失败则falsetryAquireShared(int) 共享方式尝试获取资源负数表示失败0表示成功但资源已经耗尽整数表示成功但仍有可用资源。tryReleaseShared(int) 共享方式尝试释放资源若释放后允许唤醒后等待结点返回 true否则返回 false
同步器的实现是ABS核心state 资源状态计数
同步器的实现是 ABS核心以ReentrantLock为例初始化state0当线程A lock() 执行时state 1当其它线程到尝试获取锁时判断state是否为0就可以知道是否可以获取锁当线程A unlock() 时state - 1回归零态所以每一次获取与释放都是对等的彻底释放锁也就是state回归零态若线程A 持续获取锁则state不断 1 这就是可重入锁的实现。