目录
一、创建线程
1、方法一:匿名内部类
2、方法二:实现接口
3、方法三:
二、线程常见方法
1、sleep与yield
2、线程优先级
3、join 等待线程结束
3、t1.interrupt()
三、主线程和守护线程
四、线程的状态
五、线程安全
1、synchronized解决方案
2、synchronized 方法
3、线程安全分析
4、线程安全类
六、wait和notify
1、sleep对比wait
七、Park & Unpark
八、线程活跃性
九、ReentrantLock
1、可重入
2、可打断锁(lock.lockInterruptibly();)
3、锁超时
4、公平锁
5、条件变量
十、并发之共享模型
1、volatile关键字
2、单例
七、乐观锁(无锁并发CAS)
1、原子类(临界区数据)(重要)
2、引用类型
3、原子数组
4、字段更新器
十一、线程池(重要)
1、拒绝策略
2、固定大小的线程池newfFixedThreadPool(n)
3、带缓冲功能的线程池newCachedThreadPool()
4、单线程线程池newSingThreadExecutor()
5、方法(提交)
提交任务无返回值结果(execute)
提交任务有返回结果(submit)
提交全部任务(invokeAll)
获取最先执行的任务返回值(invokeAny)
方法(关闭)
十二、任务调度线程池
1、Timer实现
2、任务调度线程池newScheduledThreadPool(n)
十三、CountdownLatch
十四、CylicBarrier
十五、线程安全集合
public void test01(){
Thread t=new Thread(){
@Override
public void run() {
log.info("线程执行");
}
}; t.start();}
public void test02() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
log.info("线程执行");
}
});
t.start();
}
用Runnable 容易与线程池等高级API配合
用Runnable 让任务脱离了Thread继承体系,更灵活
static void RunnableTest2() throws ExecutionException, InterruptedException {
// 返回值类型
FutureTask task=new FutureTask<>(new Callable() {
@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 把当前线程资源让给其他线程
Thread.sleep
sleep
睡眠---调用sleep会让当前线程进入睡眠状态--TIMED_WAITING状态(阻塞)
Thread.sleep(2000);
也可以使用
TimeUnit.SECONDS.sleep(2); // 睡眠2秒
Thread.yield()
调用yield会让当前线程从Running进入Rnnable 就绪状态(停下来),然后调度执行其他线程
优先级是指有更多的机会,
Thread.yield(); // 当前线程资源让给其他线程
thread.setPriority(Thread.MAX_PRIORITY); // 设置线程的优先级 [1,10]
t1.join();
为什么需要join
jion等待线程结束
t1.start(); // 启动t1
t1.join(); // 主线程等待t1结束
t1.join(2000); // 最多等待2秒
打断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
对象锁
内部数据会上锁,相同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;
}
}
}
加在成员方法上相当于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)
如果没有共享,则线程安全
如果被共享了
只有读操作,则线程安全,
如果有读写,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全
局部变量是线程安全的
但局部变量的引用的对象则未必线程安全
局部变量的线程安全
String
Integer 等包装类
StingBuffer
Random
Vector List list=new Vector<>(); // 线程安全集合
Hashtable private Map boxes=new Hashtable<>();
java.util.concurrent包下的类(juc)
调用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等待 线程
}
}
sleep是Thread方法,wait是object方法
sleep可以直接使用,wait需要配合synchronized使用
sleep不会释放对象锁,wait会释放对象锁
基本使用:
它们是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调度执行,也不能够结束
对比synchronized
可中断
可以设置超时时间
可以设置为公平锁
支持多个条件变量
与synchronized一样,都可以支持可重入
是指一个线程如果首次获得了这把锁,那么再释放后有机会再次获得
同一个线程可以多次获取该锁,每次获取都需要对应的释放操作。
当一个线程已经持有锁时,再次获取锁时不会造成死锁,而是增加锁的计数器。
锁的计数器会记录锁被同一个线程持有的次数,只有计数器为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();
}
}
}
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();
}
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();
}
}
}
}
}
ReentrantLock默认是不公平锁--争抢锁不是按阻塞队列执行,
ReentrantLock c1=new ReentrantLock(true); //设置公平锁,按阻塞队列顺序获得锁
公平锁一般没必要,会降低并发度
支持多个条件变量
类似wait/notify
// 创建一个条件变量(线程休息室)
Condition condition=lock.newCondition();
Condition condition2=lock.newCondition();
lock.lock(); // 抢锁
condition.await(); // 线程等待 --释放锁--阻塞 和wait一样 可以被打断(被打断会抛出异常),可以设置超时时间
condition.signal(); // 唤醒一条condition 休息室的线程 ,唤醒后也需要从新竞争锁
condition.signalAll(); // 唤醒condition 里的所有等待线程
可见性
用来修饰成员变量和静态成员变量,每次都获取最新值
但并没有解决指令交错的问题
volatile 可以避免指令的重排序
写屏障:对共享变量(包括之前的变量)的改变都会同步到主存中
读屏障:读取从主存中读取
但并没有解决指令交错的问题
对于在synchronized外的变量使用volatile关键字
饿汉式:类加载就会导致该单例对象被创建
懒汉方:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
例子:饿汉式
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;
}
}
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的场景
数据类型
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); // 保证原子性
}
AtomicReference 普通
AtomicMarkableReference 不关心引用被更改了几次,只关心
AtomicStampedReference 版本号
private AtomicReference 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 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 reference=new AtomicStampedReference<>(balance,true); // 包装balance ,以及设置版本号
this.reference.compareAndSet(pre,next,true,false) // 对比旧值和状态值,一致才会执行成功
拉姆达表达式
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方法不具备线程安全
AtomicReferenceFieIdUpdater 引用类型 字段
AtomicIntegerFieIdUpdater 保护 Integer 字段
AtomicLongFieIdUpdater 保护Long 字段
保护类的属性 ,属性需要设置成volatile 类型
Student student=new Student(); //
// 保护Student,String 的"name" 字段
AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
// 再null的基础上更改
updater.compareAndSet(student,null,"张三");
updater.updateAndGet(student,a->"王五"); // 原子更新字段
状态
RUNNING
SHUTDWN 不会接接收新任务,但会处理阻塞队列剩余任务
STOP 会中断正在执行的任务,并且会抛弃阻塞队列任务
TIDYING 任务全执行完毕,祸端线程为0 即将进入终结
TERYING 终结状态
TERMINATED
构造方法
核心线程数(最多保留的线程数)
最大线程数目
生存时间--针对救急线程
时间单位--针对救急线程
阻塞队列
线程工厂
拒绝策略
当阻塞队列满了不能再放任务时候,会产生救急任务执行新任务
救急任务有生产时间
满负荷运行(核心线程满,救急线程满)才会执行拒绝策略
救急线程的前提是使用有界策略
AbortPolicy 默认策略,抛出 异常
CallerRunsPolicy让调用者运行任务
DiscarPolicy放弃本次任务
DiscarOldestPolicy 放弃队列中最早的任务,本次任务取而代之
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");
});
newCachedThreadPool()
全部都是救急线程(60s后可以回收)
救急线程可以无限创建
阻塞容器
SynchronusQueue que=new SynchronusQueue<>();
que.put(1) // put后其他线程调用 que.take()后才会继续向下执行,不然会一直阻塞
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");
});}
只有它是无返回值的,其他都是有返回值的
public static void test2(){
ExecutorService pool=Executors.newSingleThreadExecutor();
pool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 1");
});
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2); // 运行有返回值的
Future submit = pool.submit(() -> {
Thread.sleep(1);
System.out.println("run...");
return "ok";
});
String s = submit.get(); // 没有结果就会阻塞
System.out.println(s);
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2); // 执行多个任务
List> 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);
}
});
}
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 submit = pool.submit(() -> {
System.out.println("run1...");
return 1;
});
Future submit2 = pool.submit(() -> {
System.out.println("run2...");
return 2;
});
Future submit3 = pool.submit(() -> {
System.out.println("run3...");
return 3;
});
System.out.println("shutdown");
pool.shutdown(); // 不会阻塞线主线程的执行,线程池任务会继续执行
pool.shutdown(); // 线程池任务会被打断}
定时任务
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 的执行
延时执行 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() 如果有异常会捕获
用来进行线程同步协作,等待所有线程完成倒计时
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("运行");
}
每次调用.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 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 map = new ConcurrentHashMap<>();
// 当 key 不存在时候 才会 put key-value原子的 返回get(key)
String s = map.computeIfAbsent(1, key -> "value");
System.out.println(s);}
队列
LinkedBlockingQueue
ArrayBlockingQueue
ConcurrentLinkedQueue
CopyOnWriteArraySet