JUC学习总结

目录

一、创建线程

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

十五、线程安全集合


一、创建线程

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 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  把当前线程资源让给其他线程

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 list=new Vector<>(); // 线程安全集合
Hashtable  private Map 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 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)   // 对比旧值和状态值,一致才会执行成功

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 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 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 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> 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 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(); // 线程池任务会被打断}

十二、任务调度线程池

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 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

你可能感兴趣的:(java,web后端的学习,学习,java,开发语言)