目录
2、可打断锁(lock.lockInterruptibly();)
2、固定大小的线程池newfFixedThreadPool(n)
3、带缓冲功能的线程池newCachedThreadPool()
4、单线程线程池newSingThreadExecutor()
2、任务调度线程池newScheduledThreadPool(n)
一、创建线程
1、方法一:匿名内部类
public void test01(){
Thread t=new Thread(){
@Override
public void run() {
log.info("线程执行");
}
}; t.start();}
2、方法二:实现接口
public void test02() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
log.info("线程执行");
}
});
t.start();
}
用Runnable 容易与线程池等高级API配合
用Runnable 让任务脱离了Thread继承体系,更灵活
3、方法三:
static void RunnableTest2() throws ExecutionException, InterruptedException {
// 返回值类型
FutureTask<Integer> task=new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("running...");
Thread.sleep(4000);
return 100;
}
}); Thread t=new Thread(task,"t2");
t.start(); // 阻塞 直到获得task的返回值
System.out.println(task.get());
}
二、线程常见方法
start()
run()
join() //等待线程运行结束
join(long n) //等待线程运行结束 最多等待n毫秒
getId()
getName()
setName()
getPriority()
setPriority() //修改优先级
getState() //获取线程状态 NEW/RUNNABLE/TIMED_WAITING/TERMINATED等
isInterrupted() 判断是否被打段,不会清除打断标记
isAlive() 线程是否存活(是否允许结束)
interrupt() 打断线程 叫醒线程--抛出InterruptedException 异常
interrupted() --Thread判断当前线程是否被打断 会清除打断标记
currentThread()--Thread获取当前正在执行的线程 类似this
sleep(ms) --Thread 睡眠
yield() --Thread 把当前线程资源让给其他线程
1、sleep与yield
Thread.sleep
sleep
睡眠---调用sleep会让当前线程进入睡眠状态--TIMED_WAITING状态(阻塞)
Thread.sleep(2000);
也可以使用
TimeUnit.SECONDS.sleep(2); // 睡眠2秒
Thread.yield()
调用yield会让当前线程从Running进入Rnnable 就绪状态(停下来),然后调度执行其他线程
2、线程优先级
优先级是指有更多的机会,
Thread.yield(); // 当前线程资源让给其他线程
thread.setPriority(Thread.MAX_PRIORITY); // 设置线程的优先级 [1,10]
3、join 等待线程结束
t1.join();
为什么需要join
jion等待线程结束
t1.start(); // 启动t1
t1.join(); // 主线程等待t1结束
t1.join(2000); // 最多等待2秒
3、t1.interrupt()
打断sleep, wait,join
对于sleep, wait,join被打断后会清空打断标记thread.isInterrupted()为fales
打断后 抛出InterruptedException 异常
打断普通线程,时候没有效果,只是一个标记为true,需要自己结束
public static void test5() throws InterruptedException {
Thread thread = new Thread("t1") {
@Override
public void run() {
while (true){
System.out.println(1);
if (Thread.currentThread().isInterrupted()){
// 被打断
System.out.println("被打断");
break;
}
}
}
};
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 不能直接使线程停下,只是标记
System.out.println("打断结果状态:"+thread.isInterrupted());
}
打断park()线程
LockSupport.park(); //阻塞当前线程
public static void test6() throws InterruptedException {
Thread thread = new Thread("t1") {
@Override
public void run() {
while (true){
System.out.println("运行pack线程");
LockSupport.park(); //阻塞标记为false时候才会生效--阻塞当前线程
System.out.println("重新执行");
}
}
};
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 打断park
System.out.println("打断结果状态:"+thread.isInterrupted());
}
public static void test6() throws InterruptedException {
Thread thread = new Thread("t1") {
@Override
public void run() {
while (true){
System.out.println("运行pack线程");
LockSupport.park(); //阻塞当前线程
System.out.println("重新执行");
Thread.interrupted(); //重新把标记设置为 false ; LockSupport.park()再次阻塞 }
}
};
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 打断park
System.out.println("打断结果状态:");
}
不推荐使用的方法
这些方法已经过时,容易破坏同步代码块,造成线程死锁
stop() 停止线程运行
suspend() 挂起(暂停线程)
resume() 恢复线程运行
三、主线程和守护线程
默认情况下,java进程需要的等待所以线程都运行结束,才会结束
但是主线程结束后,如果有守护线程在运行,即使守护线程还在执行,那么程序也会结束
即主线程可以不用管守护线程死活
thread.setDaemon(true); // 设置成守护线程
thread.start();
垃圾回收器就是一种守护线程
Tomcat中Acceptor和Poller线程都是守护线程
四、线程的状态
初始状态、可运行状态(就绪状态)、运行状态、阻塞状态、终止状态
java层面NEW(创建)、RUNNABLE(运行,可运行,阻塞)、BLOCKED、WAITING、TIMED_WAITING(睡眠)、TERMINATED
五、线程安全
多个线程对共享资源的读写操作
一段代码块如果存在对共享资源的多线程读写操作,称为这段代码为临界区
原子类和synchronized
1、synchronized解决方案
对象锁
内部数据会上锁,相同synchronized(对象)片段只允许一个线程操作,其他的会被阻塞
static int counter=0;
static Object lock=new Object(); // 对象锁
public static void test9() throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
synchronized (lock) { // 内部数据会上锁,相同lock 只允许一个线程操作,其他的会被阻塞
counter++;
}
}
}
};
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock){
counter--;
}
}
}, "t2");
t1.start();
t2.start();
System.out.println("结果:"+counter);
}
synchronized实际是用对象锁保证了临界区内代码的原子性
原子性:不可分割,安全
class Room{
private int countt=0;
public void add(){ //安全加法
synchronized (this){
countt++;
}
}
public void sub(){ // 安全减法
synchronized (this){
countt--;
}
}
public int get(){ // 安全读取
synchronized (this){
return this.countt;
}
}
}
2、synchronized 方法
加在成员方法上相当于synchronized(this)
class Room{
private int countt=0;
public synchronized void add(){
countt++;
}
public synchronized void sub(){
countt--;
}
public synchronized int get(){
return this.countt;
}
}
如果加载static 方法上,那么想到他于 synchronized(Room.class)
对于单例类使用synchronized(this) 对于多例使用synchronized(xx.class)
3、线程安全分析
如果没有共享,则线程安全
如果被共享了
只有读操作,则线程安全,
如果有读写,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全
局部变量是线程安全的
但局部变量的引用的对象则未必线程安全
局部变量的线程安全
4、线程安全类
String
Integer 等包装类
StingBuffer
Random
Vector List<Integer> list=new Vector<>(); // 线程安全集合
Hashtable private Map<Integer,GuardeObject> boxes=new Hashtable<>();
java.util.concurrent包下的类(juc)
六、wait和notify
调用wait方法,即可进入WaitSet变为WAITING状态
BLOCKED(等待锁)和WAITING(放弃)的线程都处于阻塞状态,不占用CPU的时间片
WAITING线程在调用notify或者notifyAll时候唤醒,但唤醒后并不会立刻获得锁,任需要竞争
方法:
synchronized (obj){
obj.wait() 一直等待 --释放锁
obj.wait(2000) 等待两秒--释放锁 ,两秒后自动执行 obj.notify(),锁任需要竞争
obj.notify() 唤醒一个等待线程 ,锁任需要竞争
lock.notifyAll() 唤醒全部等待线程 ,锁任需要竞争
}
例子:
static final Object lock=new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("t1执行...");
try {
lock.wait(); // 释放lock锁 ,并进入等待队列A
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1其他代码...");
}
},"t1").start();
new Thread(()->{
synchronized (lock){
System.out.println("t2执行...");
try {
lock.wait(); // 释放lock锁 ,并进入等待队列A
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t2其他代码...");
}
},"t2").start();
TimeUnit.SECONDS.sleep(2);
synchronized (lock){
// lock.notify(); // 主线程获得锁后唤醒一个线程
lock.notifyAll(); // 唤醒全部lock等待 线程
}
}
1、sleep对比wait
sleep是Thread方法,wait是object方法
sleep可以直接使用,wait需要配合synchronized使用
sleep不会释放对象锁,wait会释放对象锁
七、Park & Unpark
基本使用:
它们是LockSupport类中的方法
暂停当前线程
LockSupport.park();
// 恢复某个线程的运行--可以在LockSupport.park(); 之前调用
LockSupport.unpark(t1)
与wait和notify相比
wait、notify和notifyAll需要配合 synchronized 使用
LockSupport.park() 不会释放锁
LockSupport.unpark(t1) 可以先调用
八、线程活跃性
死锁
t1线程获得A对象锁,接下来想获得B对象锁
t2线程获得B对象锁,接下来想获得A对象锁
命令查看锁
jps -- 查看运行的进程 id
jstack 进程id -- 查看线程信息 --可以查看死锁信息
活锁
线程互相改变自身的结束条件,两个线程都不能结束
饥饿
一个线程的优先级太低,始终得不到CPU调度执行,也不能够结束
九、ReentrantLock
对比synchronized
可中断
可以设置超时时间
可以设置为公平锁
支持多个条件变量
与synchronized一样,都可以支持可重入
1、可重入
是指一个线程如果首次获得了这把锁,那么再释放后有机会再次获得
同一个线程可以多次获取该锁,每次获取都需要对应的释放操作。
当一个线程已经持有锁时,再次获取锁时不会造成死锁,而是增加锁的计数器。
锁的计数器会记录锁被同一个线程持有的次数,只有计数器为0时,其他线程才能获取该锁。
可重入测试例子: 主线程在 lock.lock(); 下又掉用同一个需要锁的方法,么有发送阻塞
public class Test5 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
lock.lock(); // 锁
try{
System.out.println("主方法");
m1();
}finally {
lock.unlock(); // 释放锁
}
}
public static void m1(){
lock.lock();
try{
System.out.println("m1进入");
}finally {
lock.unlock();
}
}
public static void m2(){
lock.lock();
try{
System.out.println("m2进入");
}finally {
lock.unlock();
}
}
}
2、可打断锁(lock.lockInterruptibly();)
lock.lock(); // 不可打断的锁--阻塞
lock.lockInterruptibly(); // 可以被打断的锁-阻塞 可以被 t1.interrupt(); 打断锁阻塞状态
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread(()->{
try{
// 如果没有竞争此法就会获得锁
// 如果有竞争被阻塞,可以被interrupt() 方法打断阻塞状态--并且抛出异常
System.out.println("尝试获得锁...");
lock.lockInterruptibly(); // 可以被打断的锁-阻塞
System.out.println("获得锁");
} catch (InterruptedException e) {
System.out.println("被打断");
throw new RuntimeException("没有获得锁,被打断了");
}
System.out.println("抛出锁");
lock.unlock();
});
lock.lock();
t1.start();
Thread.sleep(1000);
System.out.println("打断t1");
t1.interrupt();
}
3、锁超时
lock.tryLock() 无参数 --不会阻塞---形式标识当前是否获得锁,获得锁放回true
lock.tryLock(1, TimeUnit.SECONDS) //阻塞等待1秒(时间,单位),--获得锁后为true
这个方法也是可以使用 t1.interrupt(); 打断的
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
System.out.println("尝试获得锁");
try {
if(!lock.tryLock(1, TimeUnit.SECONDS)){ // 等待1秒,获得锁后为true System.out.println("获得不到锁"); return; }
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
System.out.println("获得到锁");
}finally {
lock.unlock();
}
},"t1");
lock.lock();
System.out.println("主线程获得锁");
t1.start();
Thread.sleep(500);
lock.unlock();
}
例如 :哲学家吃饭避免死锁问题
public class Test8 {
public static void main(String[] args) {
ReentrantLock c1=new ReentrantLock();
ReentrantLock c2=new ReentrantLock();
ReentrantLock c3=new ReentrantLock();
ReentrantLock c4=new ReentrantLock();
ReentrantLock c5=new ReentrantLock();
new Re("苏格拉底",c1,c2).start();
new Re("柏拉图",c2,c3).start();
new Re("亚里士多德",c3,c4).start();
new Re("拉克",c4,c5).start();
new Re("阿吉姆",c5,c1).start();
}
}
class Re extends Thread{
ReentrantLock rt;
ReentrantLock lt;
public Re(String name,ReentrantLock rt,ReentrantLock lt){
super(name);
this.rt=rt;
this.lt=lt;
}
@Override public void run() {
while (true) {
// 获得左筷子
if(lt.tryLock()){
// 获得右手筷子
if(rt.tryLock()){
System.out.println(Thread.currentThread().getName()+"正在吃饭");
rt.unlock();
lt.unlock();
}else {
lt.unlock();
}
}
}
}
}
4、公平锁
ReentrantLock默认是不公平锁--争抢锁不是按阻塞队列执行,
ReentrantLock c1=new ReentrantLock(true); //设置公平锁,按阻塞队列顺序获得锁
公平锁一般没必要,会降低并发度
5、条件变量
支持多个条件变量
类似wait/notify
// 创建一个条件变量(线程休息室)
Condition condition=lock.newCondition();
Condition condition2=lock.newCondition();
lock.lock(); // 抢锁
condition.await(); // 线程等待 --释放锁--阻塞 和wait一样 可以被打断(被打断会抛出异常),可以设置超时时间
condition.signal(); // 唤醒一条condition 休息室的线程 ,唤醒后也需要从新竞争锁
condition.signalAll(); // 唤醒condition 里的所有等待线程
十、并发之共享模型
1、volatile关键字
可见性
用来修饰成员变量和静态成员变量,每次都获取最新值
但并没有解决指令交错的问题
volatile 可以避免指令的重排序
写屏障:对共享变量(包括之前的变量)的改变都会同步到主存中
读屏障:读取从主存中读取
但并没有解决指令交错的问题
对于在synchronized外的变量使用volatile关键字
2、单例
饿汉式:类加载就会导致该单例对象被创建
懒汉方:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
例子:饿汉式
public final class SingLeton implements Serializable {
private SingLeton(){
}
public SingLeton getSingLeton(){
return INSTANCE;
}
private static SingLeton INSTANCE=new SingLeton();
// 防止序列化反序列化创建对象
public Object readResolve(){
return INSTANCE;
}
}
例子:懒汉式
public class Sing2 {
private Sing2(){
}
private static class Test {
static final Sing2 InINSTANCE=new Sing2();
}
public Sing2 getINSTANCE(){
return Test.InINSTANCE;
}
}
七、乐观锁(无锁并发CAS)
public void withdraw(Integer amount){
while (true){ // 在最新的基础上修改
// 获取 最新值
int prev=balance.get(); // 修改越热
int next=prev-amount; // 真正的修改-- 比较并设置 CAS
if( balance.compareAndSet(prev,next)){
// 如果 成功set 就退出循环--否则继续
break;
}
}
}
CAS必须借助volatile才能读取到共享变量的最新值来实现比较并交换的效果
CAS特点
可以实现无锁并发,适用于线程少,多核CPU的场景
1、原子类(临界区数据)(重要)
数据类型
AtomicBoolean
AtomicInteger
AtomicLong
方法
i.compareAndSet(oldValue,newValue) 如果没有被修改过才执行赋值
i.incrementAndGet(); ++i
i.getAndIncrement() i++
i.decrementAndGet(); --i
i.getAndDecrement() +--
i.addAndGet(10) 先+10 再get
自定义运算
int i1 = i.updateAndGet(x -> 1); // x代表当前值, 先计算,再get
int i2=i.getAndUpdate(x2->7); // 先获取旧值 , 再计算
例如
public void withdraw(Integer amount){
while (true){ // 在最新的基础上修改
// 获取 最新值
int prev=balance.get(); // 修改越热
int next=prev-amount; // 真正的修改-- 比较并设置 CAS
if( balance.compareAndSet(prev,next)){
// 如果 成功set 就退出循环--否则继续
break;
}
}
}
可以改成:
public void withdraw(Integer amount){
this.balance.addAndGet(-1*amount); // 保证原子性
}
2、引用类型
AtomicReference 普通
AtomicMarkableReference 不关心引用被更改了几次,只关心
AtomicStampedReference 版本号
private AtomicReference<BigCount> reference=new AtomicReference<>(balance);
BigCount pre=this.reference.get();
BigCount next=new BigCount(pre.value-amount) ;
reference.compareAndSet(pre,next) // 主线程无法得知balance 是否从A-->B-->A
private AtomicStampedReference<BigCount> reference=new AtomicStampedReference<>(balance,0); // 包装balance ,以及设置版本号
BigCount pre=this.reference.getReference();
BigCount next=new BigCount(pre.value-amount) ;
int stamp = reference.getStamp(); // 获取版本号
this.reference.compareAndSet(pre,next,stamp,stamp+1) // 对比旧值和版本号 都一致才会执行Set
private AtomicStampedReference<BigCount> reference=new AtomicStampedReference<>(balance,true); // 包装balance ,以及设置版本号
this.reference.compareAndSet(pre,next,true,false) // 对比旧值和状态值,一致才会执行成功
3、原子数组
拉姆达表达式
Supplier ()->结果
function (参数)->结果 一个参数一个结果
BIFunction ( 参数1,参数2)-> 结果,两个参数一个结果
consumer 消费者 一个参数没结果 (参数)->void
BiConsumer (参数1,参数2)->void
AtomicIntegerArray 保护Integer数组
AtomicLognArray 保护long数组
AtomicReferenceArray 保护的引用数组
AtomicIntegerArray A=new AtomicIntegerArray(new int[]{1});
A.updateAndGet(0,(a1)->++a1); 请使用 updateAndGet 对数据进行修改,set方法不具备线程安全
4、字段更新器
AtomicReferenceFieIdUpdater 引用类型 字段
AtomicIntegerFieIdUpdater 保护 Integer 字段
AtomicLongFieIdUpdater 保护Long 字段
保护类的属性 ,属性需要设置成volatile 类型
Student student=new Student(); //
// 保护Student,String 的"name" 字段
AtomicReferenceFieldUpdater<Student,String> updater=AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
// 再null的基础上更改
updater.compareAndSet(student,null,"张三");
updater.updateAndGet(student,a->"王五"); // 原子更新字段
十一、线程池(重要)
状态
RUNNING
SHUTDWN 不会接接收新任务,但会处理阻塞队列剩余任务
STOP 会中断正在执行的任务,并且会抛弃阻塞队列任务
TIDYING 任务全执行完毕,祸端线程为0 即将进入终结
TERYING 终结状态
TERMINATED
构造方法
核心线程数(最多保留的线程数)
最大线程数目
生存时间--针对救急线程
时间单位--针对救急线程
阻塞队列
线程工厂
拒绝策略
当阻塞队列满了不能再放任务时候,会产生救急任务执行新任务
救急任务有生产时间
满负荷运行(核心线程满,救急线程满)才会执行拒绝策略
救急线程的前提是使用有界策略
1、拒绝策略
AbortPolicy 默认策略,抛出 异常
CallerRunsPolicy让调用者运行任务
DiscarPolicy放弃本次任务
DiscarOldestPolicy 放弃队列中最早的任务,本次任务取而代之
2、固定大小的线程池newfFixedThreadPool(n)
newfFixedThreadPool(n)
核心线程数=最大线程数(没有救急线程)
例如
ExecutorService pool = Executors.newFixedThreadPool(2); // 创建线程池
pool.execute(()->{ //执行任务
System.out.println(Thread.currentThread().getName()+" 1");
});
pool.execute(()->{//执行任务
System.out.println(Thread.currentThread().getName()+" 2");
});
pool.execute(()->{//执行任务
System.out.println(Thread.currentThread().getName()+" 3");
});
3、带缓冲功能的线程池newCachedThreadPool()
newCachedThreadPool()
全部都是救急线程(60s后可以回收)
救急线程可以无限创建
阻塞容器
SynchronusQueue<Integer> que=new SynchronusQueue<>();
que.put(1) // put后其他线程调用 que.take()后才会继续向下执行,不然会一直阻塞
4、单线程线程池newSingThreadExecutor()
newSingThreadExecutor()
使用场景
希望多个任务队列排队执行。线程数固定为1,任务数多于1时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放,
即使发生异常,下一次也会有一个可用的线程,即每次都保证有一个可用的线程
public static void test2(){
ExecutorService pool=Executors.newSingleThreadExecutor();
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 1");
});
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+(1/0));
});
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 3");
});
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 4");
});
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 5");
});}
5、方法(提交)
提交任务无返回值结果(execute)
只有它是无返回值的,其他都是有返回值的
public static void test2(){
ExecutorService pool=Executors.newSingleThreadExecutor();
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 1");
});
提交任务有返回结果(submit)
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2); // 运行有返回值的
Future<String> submit = pool.submit(() -> {
Thread.sleep(1);
System.out.println("run...");
return "ok";
});
String s = submit.get(); // 没有结果就会阻塞
System.out.println(s);
}
提交全部任务(invokeAll)
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2); // 执行多个任务
List<Future<Object>> futures = pool.invokeAll(Arrays.asList(
() -> {
System.out.println("任务1");
return "1";
},
() -> {
System.out.println("任务2");
return "2";
},
() -> {
System.out.println("任务3");
return "3";
}
));
futures.forEach(f->{
try {
System.out.println(f.get()); // 获得返回结果 --会阻塞
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
});
}
获取最先执行的任务返回值(invokeAny)
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
// 谁先运行结束,结果就是谁的返回值
Object futures = pool.invokeAny(Arrays.asList(
() -> {
System.out.println("任务1");
return "1";
},
() -> {
System.out.println("任务2");
return "2";
},
() -> {
System.out.println("任务3");
return "3";
}
));
System.out.println(futures);
}
方法(关闭)
关闭线程池
shutdown
线程次状态变成SHUTDOWN
不会接收新任务
但已提交任务会执行完
此方法不会阻塞线程的之执行
shutdownNow
线程池状态变为STOP
不会接收新任务
会将队列中的任务返回
并用 interrupt 的方式中断正在执行的所有线程
isShutdown()
不在RUNING 状态的线程池,此方法就会返回true方法
isTerminated()
判断线程池状态是否是TERMINATED终结状态
awaitTermination(long time,TimeUnit un)
线程池结束后需要做的事
例如:
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> submit = pool.submit(() -> {
System.out.println("run1...");
return 1;
});
Future<Integer> submit2 = pool.submit(() -> {
System.out.println("run2...");
return 2;
});
Future<Integer> submit3 = pool.submit(() -> {
System.out.println("run3...");
return 3;
});
System.out.println("shutdown");
pool.shutdown(); // 不会阻塞线主线程的执行,线程池任务会继续执行
pool.shutdown(); // 线程池任务会被打断}
十二、任务调度线程池
1、Timer实现
定时任务
public static void main(String[] args) {
Timer timer=new Timer();
TimerTask task=new TimerTask() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("run1");
}
};
TimerTask task2=new TimerTask() {
@Override
public void run() {
System.out.println("run2");
}
};
timer.schedule(task,1000); // 任务1的逻辑执行时长,会影响任务2 的执行
timer.schedule(task2,1000);
}
Timer的缺点:第一个任务抛出异常后,会使第二个任务无法执行,任务1的逻辑执行时长,会影响任务2 的执行
2、任务调度线程池newScheduledThreadPool(n)
延时执行 schedule(代码,时间,时间单位)
private static void Test(){
// 任务调度线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
// 1秒后执行 无返回值类型的提交
pool.schedule(()->{
System.out.println("task2");
},1, TimeUnit.SECONDS);
// 第一个任务不会影响第二个任务
pool.schedule(()->{
System.out.println("task2");
},1, TimeUnit.SECONDS);
}
重复执行任务
private static void Test(){
// 任务调度线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.scheduleAtFixedRate(()->{ // 时间间隔是 开始后执行
System.out.println("执行");
},1,1,TimeUnit.SECONDS); //代码逻辑 -- 初始延时-- 间隔时间--单位
}
pool.scheduleWithFixedDelay(()->{ // 时间间隔重复是 结束后再执行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行");},1,1,TimeUnit.SECONDS);
//代码逻辑 -- 初始延时-- 间隔时间--单位
// 间隔时间是指上次执行完毕后再过多久重复运行
异常处理
手动trt
使用get() 如果有异常会捕获
十三、CountdownLatch
用来进行线程同步协作,等待所有线程完成倒计时
await() 用来等待计数归零,countDown() 用来让计数减一
倒计时为0 时候等待线程就能继续运行
计数不能重新设置
例如
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch=new CountDownLatch(3);
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("1开始...");
latch.countDown();
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("2开始...");
latch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println("3开始...");
latch.countDown();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
latch.await(); // 阻塞 等于0 会运行下面
System.out.println("运行");
}
十四、CylicBarrier
每次调用.await() 计数就会减一 并且阻塞当前线程当计数为0 时候await() 就会停止阻塞,计数为0时候再次调用,那么计数会再次变成初始值
例如
static void test2(){
ExecutorService executorService = Executors.newFixedThreadPool(2);
CyclicBarrier barrier=new CyclicBarrier(2);
for (int i=0;i<1000;i++){
int j=i;
executorService.submit(()->{
System.out.println("运行"+j);
try {
TimeUnit.SECONDS.sleep(1);
barrier.await(); // 2-1
System.out.println(j+"结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
});
}
executorService.shutdown();
}
十五、线程安全集合
遗留的安全集合
Hashtable
Vector
修饰的安全集合(工具类调用,封装list或map)
SynchronizedMap
SychronizedList
JUC安全集合
Blocking 类型
大部分实现基于锁,并且供来使用来阻塞的方法
CopyOnWrite类型
修改开销相对较重
Concurrent类型
内部很多操作使用cas优化,一般可以提高吞吐量
弱一致性,容器被修改,遍历数据还是旧的
size未必准确
其他非线程安全的集合在遍历时候发生修改,会快速失败
ConcurrentHashMap
static void test4(){
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
map.put(1,"张三"); // 单个操作是原子的,但是一群操作,不一定安全
String s = map.get(1);
System.out.println(s); //
}
map.computeIfAbsent(1,key->"value"); // 当 key 不存在时候 才会 put key-value 原子的
static void test4(){
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
// 当 key 不存在时候 才会 put key-value原子的 返回get(key)
String s = map.computeIfAbsent(1, key -> "value");
System.out.println(s);}
队列
LinkedBlockingQueue
ArrayBlockingQueue
ConcurrentLinkedQueue
CopyOnWriteArraySet