【线程-J.U.C】

news2024/12/25 9:14:57

Lock

J.U.C最核心组件,Lock接口出现之前,多线程的并发安全只能由synchronized处理,但java5之后,Lock的出现可以解决synchronized的短板,更加灵活。
Lock本质上是一个接口,定义了释放锁(unlock)、获得锁(lock)的抽象方法。

ReentrantLock(重入锁)

线程获得锁之后,再次获得该锁不需要阻塞,而是直接关联一次计数器,增加重入次数。它是唯一一个实现了Lock接口的类,也是互斥锁。
重入锁可以防止死锁,就单个线程而言,锁未释放时又要重新获取同一把锁。

reentrantLock.lock() --获得锁
reentrantLock.unlock() --释放锁

public class AtomicDemo { 
	private static int count=0; 
	static Lock lock = new ReentrantLock(); 
	public static void inc(){ 
		lock.lock(); 	//获得锁
		try { 
			Thread.sleep(1); 
		} catch (InterruptedException e) { 
			e.printStackTrace(); 
		} 
		count++; 		//原子递增
		lock.unlock(); 	//释放锁
	} 
	
	public static void main(String[] args) throws InterruptedException { 
		for(int i=0;i<1000;i++){ 
			new Thread(()->{AtomicDemo.inc();}).start();; 
		} 
		Thread.sleep(3000);
		System.out.println("result:"+count); 
	} 											
}

ReentrantReadWriteLock(重入读写锁)

ReentrantLock接口的实现类。这个类维护了两个锁:ReadLock和WriteLock,这两个锁分别实现了Lock接口。

ReadLock、WriteLock之间的基本原则:
读读不互斥、读写互斥、写写互斥。

ReentrantReadWriteLock是一个非排他锁,允许多个线程同时访问,但在写线程访问时,会阻塞其他线程。所以比较适用于读多余写的场景。

public class RWLock {
    static ReentrantReadWriteLock wrl=new ReentrantReadWriteLock();
    static Lock read = wrl.readLock();	// 读锁
    static Lock write = wrl.writeLock();	// 写锁
    static Map<String,Object> cacheMap=new HashMap<>();
    
   public static final Object get(String key){
        System.out.println("begin read data:"+key);
        read.lock(); 
        try {
            return cacheMap.get(key);
        }finally {
            read.unlock();
        }
    }
    public static final Object put(String key,Object val){
        write.lock();
        try{
            return cacheMap.put(key,val);
        }finally {
            write.unlock();
        }
    }
}

StampedLock

JDK8引入的新的锁机制,是读写锁的改进版本。StampedLock是一种乐观的读策略,不阻塞写线程,解决了读写互斥。
通过偏移量stateOffset比较内存地址,以乐观锁的方式保证排他性(类似数据库乐观锁)。

AQS(abstractQueuedSynchronizer)

它是一个同步队列,也是Lock的核心组件。
AQS功能分两种:独占、共享
独占(互斥):每次只有一个线程持有锁,类似ReentrantLock。
共享:允许多个线程同时持有锁,类似ReentrantReadWriteLock。

AQS内部实现

AQS内部维护的是一个FIFO双向链表,线程争抢锁失败后,会封装成Node加入到AQS。当获取锁的线程释放锁之后,会从队列中唤醒一个阻塞的Node。
head节点表示获取锁成功的节点,prev前置节点,next后继节点。
在这里插入图片描述
添加新节点
1.新节点的prev指向前置节点,并将前置节点的next指向自己。
2.通过CAS将tail指向新节点自己

在这里插入图片描述
抢锁时节点变化
在这里插入图片描述
1.修改head节点,指向下一个获得锁的节点。
2.获得锁的节点,prev指针指向null。

公平锁与非公平锁

锁的公平性是相对于获得锁的顺序而言的。
公平锁中,如果锁被占用,则直接入等待队列,并按照队列顺序获得锁。
非公平锁中,线程先去检查并设置锁状态,会直接与队列中的头节点抢锁,抢锁失败才会进入等待队列。

设置公平锁和非公平锁
ReentrantLock lock = new ReentrantLock(true);
// true -- 公平锁, false -- 非公平锁
公平锁

公平锁时,获得锁的顺序与线程启动顺序一致。非公平锁则不然。

public class MyFairLock {
   private static ReentrantLock lock = new ReentrantLock(true);	//公平锁
   public  static void testFail(){
      try {
         lock.lock();
         System.out.println(Thread.currentThread().getName() +"获得了锁");
      } finally {
         lock.unlock();
      }
   }
   public static void main(String[] args) throws InterruptedException {
      Runnable runnable = () -> {
         System.out.println(Thread.currentThread().getName()+"启动");
         testFail();
      };
      Thread[] threadArray = new Thread[10];
      for (int i=0; i<10; i++) {
         new Thread(runnable).start();
      }
   }
 }

如何设计锁

锁的互斥性
没有抢占到锁的线程如何处理?
公平和非公平
重入锁

Condition

多线程协调通信的工具类,让线程一起等待某个条件(condition),条件满足时,唤醒线程。
调用condition之前,需要先获得Lock锁。

condition.await()

使当前线程进入等待队列并释放锁,释放锁之后,如果节点不在AQS队列,则阻塞当前线程,如果在,则CAS自旋等待尝试获取锁。

condition.signal()

唤醒阻塞线程,唤醒等待队列中等待时间最长的节点(首节点),唤醒之前先将节点移到同步队列。

demo

public class ConditionWait implements Runnable{
    private Lock lock;
    private Condition condition;

    public ConditionWait(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }
    @Override
    public void run() {
        try {
            lock.lock(); 	//获得锁
            try {
                System.out.println("begin - ConditionWait");
                condition.await();//阻塞(1.释放锁, 2.阻塞当前线程, FIFO(单向、双向))
                System.out.println("end - ConditionWait");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            lock.unlock();//释放锁
        }
    }
}
public class ConditionNotify implements Runnable {
   private Lock lock;
   private Condition condition;

   public ConditionNotify(Lock lock, Condition condition) {
      this.lock = lock;
      this.condition = condition;
   }
   @Override
   public void run() {
      try {
         lock.lock();	//获得了锁
         System.out.println("begin - conditionNotify");
         condition.signal();	//唤醒阻塞状态的线程
         System.out.println("end - conditionNotify");
      } finally {
         lock.unlock(); 	//释放锁
      }
   }
}
public class demo{
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public static void main(String[] args){
		 new Thread(new ConditionWait (lock , condition )).start;
		 new Thread(new ConditionNotify (lock , condition )).start;
	}
}

CountDownLatch

CountDownLatch countDownLatch =new CountDownLatch(3); //入参int类型,表示计数器的初始值。

countDownLatch.countDown() // 计数器减一

countDownLatch.await() // 阻塞主流程,计数器为0时,继续执行。

public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch =new CountDownLatch(3);
         for (int i = 0; i < 2; i++) {
             new Thread(()->{
                 countDownLatch.countDown();	//减1
             }).start();
         }
        countDownLatch.await();//阻塞,等到countDownLatch计数为0时,继续执行		
        System.out.println("continue");
    }
}

利用CountDownLatch实现高并发场景

public class CountDownLatchDemo extends Thread{

    static CountDownLatch countDownLatch=new CountDownLatch(1);//模拟一个开关

    public static void main(String[] args) {
        for(int i=0;i<1000;i++){		//启动1000个线程
            new CountDownLatchDemo().start();
        }
        countDownLatch.countDown();		//所有阻塞线程同时跑run(),模拟并发场景
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();	//阻塞,countDown()之后,所有线程同时继续执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //TODO
        System.out.println("ThreadName:"+Thread.currentThread().getName());
    }
}

Semaphore

控制访问线程个数,常用于限流,底层实现基于AQS共享锁。

Semaphore 分公平策略和非公平策略,类似公平锁和非公平锁
初始化

Semaphore semaphore=new Semaphore(3);

入参int类型,表示同时访问的个数限制,只有3个令牌。

设置给AQS的state。

semaphore.acquire()

如果没有达到上限,则获得一个令牌,否则阻塞当前线程,直到有令牌释放出来并抢到令牌后,继续执行。
state = state - 1;

semaphore.release();

释放一个令牌。
state = state + 1;

demo

public class SemaphoreDemo {

    static class Car extends  Thread{
        private int num;
        private Semaphore semaphore;

        public Car(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }
        public void run(){
            try {
                semaphore.acquire();	 	//获得一个令牌, 如果拿不到令牌,则阻塞
                System.out.println("第"+num+" 抢占一个车位");
                Thread.sleep(2000);
                System.out.println("第"+num+" 开走喽");
                semaphore.release();		//释放一个令牌,其他线程可以开始抢令牌
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(3);	//同时访问线程数最多3个
        for(int i=0;i<10;i++){
            new Car(i,semaphore).start();
        }
    }
}

LockSupport

LockSupport.park()
LockSupport.unpark(Thread) // 唤醒某个线程
Waite/notify 无法唤醒某个线程。

Cyclicbarrier

设置一个屏障,当所有线程都达到这个屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。

Cyclicbarrier 初始化

CyclicBarrier cyclicBarrier=new CyclicBarrier(3);
CyclicBarrier cyclicBarrier=new CyclicBarrier(3,new CycliBarrierDemo());
入参int类型,表示计数器。
入参Runnable的实现类,表示所有线程都到达屏障后,主线程唤醒阻塞方法之前,先开始跑CycliBarrierDemo的run方法,然后所有阻塞的线程继续工作。

cyclicBarrier.await()

设置一个屏障,线程在此阻塞
可设置入参:超时时间。在限定时间内,如果没有足够线程到达,也解除阻塞,继续工作。

demo

public class DataImportThread extends Thread{
    private CyclicBarrier cyclicBarrier;
    private String path;
    public DataImportThread(CyclicBarrier cyclicBarrier, String path) {
        this.cyclicBarrier = cyclicBarrier;
        this.path = path;
    }

    @Override
    public void run() {
        System.out.println("开始导入:"+path+" 数据");
        //TODO
        try {
            cyclicBarrier.await(); 	//设置一个屏障,线程在此阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println(path+" 数据继续开始后续处理");
    }
}
public class CycliBarrierDemo extends Thread{
    @Override
    public void run() {	//主线程唤醒阻塞方法之前,先跑run()
        System.out.println("所有文件都已导入,解除屏障,开始后续处理");
    }

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(3,new CycliBarrierDemo());
        new Thread(new DataImportThread(cyclicBarrier,"file1")).start();
        new Thread(new DataImportThread(cyclicBarrier,"file2")).start();
        new Thread(new DataImportThread(cyclicBarrier,"file3")).start();
    }
}

ConcurrentHashMap

J.U.C里提供的线程安全且高效的hashmap,主要为了解决HashMap线程不安全和HashTable效率不高的问题。
HashTable之所以效率不高,主要是因为使用了synchronized关键字对put等操作加锁,synchronized对整个对象加锁,也就是说put等操作修改Hash表时,锁住了整个Hash表

HashMap线程不安全

会出现值覆盖的情况

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else 
	……
}

Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
    return new Node<>(hash, key, value, next);
}

当多个线程同时进行if ((p = tab[i = (n - 1) & hash]) == null)的判断,并且都进入的if方法,newNode方法当中直接返回了对象,进行赋值,那么就会出现值覆盖的情况。

ConcurrentHashMap线程安全

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        } else {

	……
	   synchronized (f) {
 	}
}
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

1.for自旋,并且table被volatile修饰,保证每次自旋的时候可以拿到最新的table。
2.tabAt(tab, i = (n - 1) & hash) 表示的意思就是tab[i],但需要注意的是,table被volatile修饰,但并不能保证table内的各个元素是最新的。所以通过tabAt方法基于native方法直接取内存中第i个元素,基于底层的总线锁、缓存锁,保证table内元素的可见性。
3.casTabAt取通过cas操作,实现赋值,保证只有一个线程能修改成功,其他的修改失败,保证了原子性,避免线程安全问题。
4.synchronized给node加锁。

阻塞队列(BlockingQueue)

ArrayBlockingQueue
数组实现的有界阻塞队列,按照FIFO原则对元素排序。

LinkedBlockingQueue
链表实现的有界阻塞队列,次列队的默认和最大长度为Integer.MAX_VALUE,按照FIFO原则对元素排序。

PriorityBlockingQueue
支持优先级排序的无界阻塞队列, 默认情况下元素采取自然顺序升序排列。也可自定义类实现compareTo()方法来指定元素排序规则,或者初始化 PriorityBlockingQueue 时,指定构造参数 Comparator 来对元素进行排序。

DelayQueue
优先级队列实现的无界阻塞队列,有延时功能

SynchronousQueue
没有容量,不存储元素的阻塞队列, 每一个 put 操作必须等待一个 take 操作,元素被消费了才能再添加。

LinkedTransferQueue
链表实现的无界阻塞队列

LinkedBlockingDeque
链表实现的双向阻塞队列

插入操作

add(e)
添加元素,如果队列满了,则报错IllegalStateException。
offer(e)
添加元素,同时返回一个状态,如果成功则返回true。
put(e)
添加元素,队列满了之后,会阻塞生产者线程,直到队列可用。
offer(e, time, unit)
添加元素,队列满了之后,生产者线程被阻塞指定时间,如果超时了,则该生产者线程直接退出。

移除操作

Remove()
移除元素,移除成功则返回true,如果队列为空,则返回false。
poll():
当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回 null
take():
基于阻塞的方式获取队列中的元素,如果队列为空,则 take 方法会一直阻塞,直到队列中有新的数据可以消费
poll(time,unit):
带超时机制的获取数据,如果队列为空,则会等待指定的时间再去获取元素返回

ArrayBlockingQueue

ArrayBlockingQueue(int capacity)
ArrayBlockingQueue(int capacity, boolean fair)
ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c)

capacity --列队长度
fair-- 是否为公平阻塞队列,默认情况下是非公平的

public ArrayBlockingQueue(int capacity) {	
    this(capacity, false);	//默认非公平锁
}

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);	//重入锁
    notEmpty = lock.newCondition();	//初始化非空等待队列
    notFull =  lock.newCondition();	//初始化非满等待队列
}

Atomic

J.U.C提供了12个原子操作类
原子更新基本类型
AtomicBoolean、AtomicInteger、AtomicLong
原子更新数组
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
原子更新引用
AtomicReference 、AtomicReferenceFieldUpdater、AtomicMarkableReference(更新带有标记位的引用类 型)
原子更新字段
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedReference

AtomicInteger

内部实现是调用unsafe类

AtomicInteger atomicInteger=new AtomicInteger(0); //初始化atomicInteger=0
atomicInteger.get() //取值
atomicInteger.incrementAndGet(); //递增+1

AtomicLong

AtomicLong atomicLong = new AtomicLong(0L); //初始化atomicLong=0L
long vaule = atomicLong.longValue() //取值,调用get()
atomicLong .addAndGet(1L) //atomicLong+1L
注:1.8之后建议使用LongAdder,减少乐观锁的重试次数,性能更好。

线程池

线程的创建、销毁所花费的时间和系统资源相当大,所以就有了线程池的概念。
任务处理完之后,线程不会被销毁,而是以挂起的状态返回到线程池,等待后续任务的分配。

优点:
线程复用,避免平凡创建、销毁线程带来的性能开销。
控制资源数量,避免出现资源瓶颈。

ThreadpoolExecutor

public ThreadPoolExecutor(
	int corePoolSize,		// 核心线程数
          int maximumPoolSize,	// 最大线程数
          long keepAliveTime,	// 核心线程以外的线程最大存活时间
          TimeUnit unit,		// 存活时间单位
          BlockingQueue<Runnable> workQueue,	//保存执行任务的阻塞队列
          ThreadFactory threadFactory,		// 创建新线程使用的工厂
          RejectedExecutionHandler handler)	// 当任务无法执行时的处理方式

corePoolSize
线程数达到corePoolSize后,后续到达的任务会被放到阻塞队列中

maximumPoolSize
队列满了,则创建新线程处理后续任务,直到线程数量达到maximumPoolSize。

keepAliveTime
线程空闲时间达到keepAliveTime,则终止该线程,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout(boolean)设置为true,核心线程也会退出,直到线程数量为0。

Unit
时间单位,TimeUnit.DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS……

BlockingQueue workQueue
阻塞队列,用于存储等待执行的任务。

threadFactory
创建线程池的工厂

RejectedExecutionHandler handler
线程丢弃策略
在这里插入图片描述

newFixedThreadPool

适用于 负载比较大,且为了资源的合理利用,需要限制线程数量的场景

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

指定corePoolSize和maximumPoolSize,且相同。
阻塞队列用的是LinkedBlockingQueue,默认容量是Inter.MAX_VALUE,可以一直添加任务,自然也就不用创建核心线程以外的线程。

newCachedThreadPool

没有核心线程,直接向SynchronousQueue中提交任务(添加一个、消费一个交替完成),由空闲线程执行任务,没有空闲线程则新建一个。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

空闲线程的生存时间60S。

newSingleThreadExecutor

只创建一个线程,保证任务按照顺序执行。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool

延期执行

线程池原理分析

在这里插入图片描述

源码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
      int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {	// 池中线程数小于corePooolSize,新建
        if (addWorker(command, true))	// 创建核心线程,接收任务
            return;
        c = ctl.get();	
    }
    if (isRunning(c) && workQueue.offer(command)) { //核心池满、队列未满,添加到队列
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))	// 核心池满、队列满、创建新线程
        reject(command);	// 如果新线程创建失败,拒绝任务
}
常见问题

使用newfixedThreadPool或者singleThreadPool,允许的队列长度为Inter.MAX_VALUE,如果使用不当,会导致队列堆积了大量请求,而导致OOM风险。
使用newCachedThreadPool,允许的线程数量为Inter.MAX_VALUE,可能导致大量线程创建,导致CPU使用过高,或者OOM。

如何合理配置线程池大小

CPU密集型
主要执行计算任务,响应时间快,这种任务CPU利用率很高。那么线程数的配置应该根据CPU的核数来决定。如果CPU4核,那么最多同时执行4个线程。否则过多的线程会导致上下文切换,返回效率降低。
最大线程数可设置为CPU核数+1。

IO密集
主要执行IO操作,时间较长,CPU利用率不高。这种情况下可以结合线程等待时长来判断,等待时间越长,线程数可设置的越多。
一般最大线程数设置为CPU核数2倍。

公式:
线程池设定最佳线程数目 =
((线程池设定的线程等待时间+线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目

线程池的初始化

默认情况下,创建了线程池后,线程池中是没有线程的,有任务来才会创建线程去执行任务。

prestartCoreThread() //初始化一个核心线程
prestartAllCoreThreads() //初始化全部的核心线程

线程池的关闭

ThreadPoolExecutor提供了两个方法shutdown() 和 shutdownNow()。

shutdown()
不会立即终止线程池,等队列中所有任务执行完毕后才终止,并不再接收新的任务。

shutdownNow()
立即终止线程池,并尝试打断正在执行的任务,并清空队列,返回尚未执行的任务。

线程池容量的动态调整

ThreadPoolExecutor提供了两个方法setCorePoolSize() 和 setMaximumPoolSize()。

setCorePoolSize() 设置核心池大小
setMaximumPoolSize() 设置最大线程数

线程池任务缓存队列 – workQueue

workQueue的类型为BlockingQueue,通常取ArrayBlockingQueue、
LinkedBlockingQueue、SynchronousQueue这三种类型

线程池的监控 ThreadPoolExecutor

项目中大规模的使用线程池,那必须有一套监控体系。线程池提供了响应的扩展方法,通过重写线程池的beforeExecute、afterExecute、shutdown等方法可以实现对线程的监控。

public class Demo1 extends ThreadPoolExecutor {

  private ConcurrentHashMap<String, Date> startTimes;	//存储任务开始的时间

  public Demo1(	
      int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue) {
    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    this.startTimes = new ConcurrentHashMap<>();
  }

  @Override
  public void shutdown() {
    System.out.println(
        "已经执行的任务数:" + this.getCompletedTaskCount()
            + ",当前活动线程数:" + this.getActiveCount()
            + ",当前排队线程数:" + this.getQueue().size());
    super.shutdown();
  }

  @Override
  protected void beforeExecute(Thread t, Runnable r) {	//任务开始之前执行
    startTimes.put(String.valueOf(r.hashCode()), new Date());	//记录开始时间
    super.beforeExecute(t, r);
  }

  @Override
  protected void afterExecute(Runnable r, Throwable t) {
    Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
    Date finishDate = new Date();
    long diff = finishDate.getTime() - startDate.getTime();		//任务运行用时
    System.out.print("任务耗时:" + diff + "\n");
    System.out.print("初始线程数:" + this.getPoolSize() + "\n");
    System.out.print("核心线程数:" + this.getCorePoolSize() + "\n");
    System.out.print("正在执行的任务数量:" + this.getActiveCount() + "\n");
    System.out.print("已经执行的任务数:" + this.getCompletedTaskCount() + "\n");
    System.out.print("任务总数:" + this.getTaskCount() + "\n");
    System.out.print("最大允许的线程数:" + this.getMaximumPoolSize() + "\n");
    System.out.print("线程空闲时间:" + this.getKeepAliveTime(TimeUnit.MILLISECONDS) + "\n");
    super.afterExecute(r, t);
  }
  public static ExecutorService newCachedThreadPool() {
    return new Demo1(0, Integer.MAX_VALUE, 60L, 
		TimeUnit.SECONDS, new SynchronousQueue());
  }
}
public class Test implements Runnable{
  private static ExecutorService es = Demo1.newCachedThreadPool();
  
  @Override
  public void run() {
     try {
        Thread.sleep(1000);	
     } catch (InterruptedException e) {
        e.printStackTrace();
     } 
}
  public static void main(String[] args) {
     for (int i = 0; i < 100; i++) {	
        es.execute(new Test());
     }
     es.shutdown();
  } 
}

Callable/Future

线程池执行任务有两种方法:execute 和 submit。

execute 和 submit区别

execute 只能接受Runnable的参数,没有返回值,有异常则抛出。
submit可以接受Runnable和Callable两种类型的参数,如果传入Callable类型的参数,可以得到一个Future返回值。Submit方法不会抛异常,Future.get()才会抛异常。

Submit

public class FutureDemo implements Callable<String> {

   @Override
   public String call() throws Exception {
       System.out.println("execute:call");
       Thread.sleep(5000);
       return "Hello Call";
   }

   public static void main(String[] args) throws ExecutionException, InterruptedException {
       FutureDemo futureDemo=new FutureDemo();
//        FutureTask future=new FutureTask(futureDemo);
//        new Thread(future).start();
       ExecutorService executorService= Executors.newFixedThreadPool(3);
       Future future =executorService.submit(futureDemo);
       System.out.println(future.get());	//阻塞获取结果
   }
}

Submit相对于execute而言,多做了一步,封装了一个RunnableFuture

public <T> Future<T> submit(Callable<T> task) {
   if (task == null)  throw new NullPointerException();
   RunnableFuture<T> ftask = newTaskFor(task);
   execute(ftask);
   return ftask;
}

FutureTask

FutureTack是Runnable和Future的结合,FutureTack的run方法计算结果,FutureTack的get方法获取结果。

public class FutureDemo implements Callable<String> {

   @Override
   public String call() throws Exception {
       System.out.println("execute:call");
       Thread.sleep(5000);
       return "Hello Call";
   }

   public static void main(String[] args) throws ExecutionException, InterruptedException {
       FutureDemo futureDemo=new FutureDemo();
       FutureTask future=new FutureTask(futureDemo);
       new Thread(future).start();
//        ExecutorService executorService= Executors.newFixedThreadPool(3);
//        Future future =executorService.submit(futureDemo);
       System.out.println(future.get()); 	//阻塞获取结果
   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/385761.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决:centos7如何解决网络不可达和wget: 无法解析主机地址 “downloads.mysql.com”

遇到此类问题可能会有多重解决方法&#xff0c;需要一个一个的去排除。 1、查看自己的网络设置是不是设置的NAT模式&#xff0c;设置完成后再去ping一下网络地址。 2、ping一下百度看能不能拼成功&#xff0c;下图是ping成功的样式。&#xff08;如果不行继续往下走&#xff0…

牛客sql题目练习

Sql3描述 题目&#xff1a;现在运营需要查看用户来自于哪些学校&#xff0c;请从用户信息表中取出学校的去重数据。 示例:user_profile iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20北京大学Beijing42315femal…

PowerShell攻击指南

文章目录一&#xff1a;PowerShell简介1.1&#xff1a;基本概念1.2&#xff1a;执行策略与绕过1.3&#xff1a;常用命令二&#xff1a;PowerSploit2.1&#xff1a;PowerSploit安装2.2&#xff1a;PowerSploit攻击实战2.2.1&#xff1a;直接shellcode反弹meterpreter shell2.2.2…

【C/C++每日一练】总目录(不断更新中...)

C/C 2023.03 20230303 1. 字符串相乘 ★★ 2. 单词拆分 II ★★★ 3. 串联所有单词的子串 ★★★ 20230302 1. 个位数是6&#xff0c;且能被3整除的五位数共有多少个&#xff1f; ☆ 2. 不同方式求n的阶乘 ★ 3. 报数游戏 ★☆ 20230301 1. 冒泡排序法排序 ★ …

视频号频出10w+,近期爆红的账号有哪些?

回顾2月&#xff0c;视频号持续放出大动作&#xff0c;不仅进行了16小时不间断的NBA全明星直播&#xff0c;还邀请国际奥委会入驻&#xff0c;分享奥运的最新资讯。视频号成为越来越多官方机构宣传推广的有效渠道。官方积极入驻&#xff0c;内容创作生态也在同步繁荣发展&#…

中村成洋《垃圾回收的算法与实现》PDF 读书笔记

观前提醒 为了能够锻炼自己&#xff0c;我会查阅大量外文不停的修改内容&#xff0c;少部分会提示成中文。 可能有误&#xff0c;请见谅 提示&#xff1a;若是觉得阅读困难&#xff0c;可以看如下内容 脚本之家可获取&#xff0c;若失效可私信浏览器的沙拉查词扩展&#xf…

Shell脚本学习指南 - 第二章入门篇

shell脚本的第一行#! #! /bin/awk -f 内核会扫描文件开头的#!后面内容&#xff0c;跳过所有空白符号&#xff0c;寻求可以用来执行程序的解释器的full path和option&#xff08;option后面的空格会识别&#xff09; ; shell用分号隔开多条语句 & 后台执行该命令&#xff…

ChatGPT解答:JavaScript保存当前网页页面图片为pdf文件或者word文件,前端用vue2,给出详细的方案和代码

ChatGPT解答&#xff1a;JavaScript保存当前网页页面图片为pdf文件或者word文件&#xff0c;前端用vue2&#xff0c;给出详细的方案和代码 ChatGPTDemo Based on OpenAI API (gpt-3.5-turbo). JavaScript保存当前网页页面图片为pdf文件或者word文件&#xff0c;前端用vue2&am…

Python 操作Redis

在 Python中我们使用 redis库来操作 Redis数据库。Redis数据库的使用命令这里就不介绍了。 需要安装 redis库。检查是否安装redis&#xff1a; pip redis 如果未安装&#xff0c;使用 pip命令安装 redis。 pip install redis #安装最新版本 一、Redis连接 Redis提供两个类 Re…

CEC2021:鱼鹰优化算法(Osprey optimization algorithm,OOA)求解CEC2021(提供MATLAB代码

一、鱼鹰优化算法简介 鱼鹰优化算法&#xff08;Osprey optimization algorithm&#xff0c;OOA&#xff09;由Mohammad Dehghani 和 Pavel Trojovsk于2023年提出&#xff0c;其模拟鱼鹰的捕食行为。 鱼鹰是鹰形目、鹗科、鹗属的仅有的一种中型猛禽。雌雄相似。体长51-64厘米…

2023年“楚怡杯“湖南省职业院校技能竞赛“网络安全”竞赛任务书

2023年“楚怡杯“湖南省职业院校技能竞赛“网络安全”竞赛任务书 一、竞赛时间 总计&#xff1a;360分钟 竞赛阶段竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 A模块 A-1 登录安全加固 180分钟 200分 A-2 本地安全策略配置 A-3 流量完整性保护 A-4 事件监控 …

技术官方文档中的代码是用什么展示的?代码高亮插件总结

****内容预警****菜鸟教程***大佬绕道我们经常看到各种技术官方文档&#xff0c;有很多代码展示的区域&#xff0c;用于我们复制粘贴代码&#xff0c;比如vue 的官网当我们需要自己实现这么一个网站的时候&#xff0c;我就开始手忙脚乱&#xff0c;这到底是咋实现的&#xff1f…

如何使用ChatGPT快速构建一个网站模板?

欢迎来到令人兴奋的自然语言处理和机器学习世界&#xff01;今天&#xff0c;我们将探索 ChatGPT 的功能&#xff0c;它是由 OpenAI 公司开发的目前最先进的人工智能工具。当然&#xff0c;你也可以将其看作是一个智能机器人。ChatGPT 最令人印象深刻的功能之一是它能够根据简单…

如何获取或设置CANoe以太网网卡信息(SET篇)

CAPL提供了一系列函数用来操作CANoe网卡。但是,但是,首先需要明确一点,不管是获取网卡信息,还是设置网卡信息,只能访问CAPL程序所在的节点下的网卡,而不是节点所在的以太网通道下的所有网卡 关于第一张图中,Class节点下,有三个网卡:Ethernet1、VLAN 1.100、VLAN 1.200…

我的 System Verilog 学习记录(8)

引言 本文简单介绍 SystemVerilog 的接口。 前文链接&#xff1a; 我的 System Verilog 学习记录&#xff08;1&#xff09; 我的 System Verilog 学习记录&#xff08;2&#xff09; 我的 System Verilog 学习记录&#xff08;3&#xff09; 我的 System Verilog 学习记…

哪个牌子的蓝牙耳机音质好?公认音质最好的真无线耳机推荐

现如今&#xff0c;使用蓝牙耳机的人越来越多&#xff0c;更多的蓝牙耳机品牌出现在大众视野。哪个牌子的蓝牙耳机音质好&#xff1f;最近看到很多人问音质。都说蓝牙耳机的音质比不上有线耳机的音质&#xff0c;但经过那么多年的技术进步&#xff0c;蓝牙耳机在音质上也有着不…

CSS - 扫盲

文章目录1. 前言2. CSS2.1 css 的引入方式2.2 选择器2.3 CSS 常用属性2.3.1 字体属性2.3.2 文本属性2.3.3 背景属性2.4 圆角矩形2.5 元素的显示模式2.6 盒子模型2.7 弹性布局1. 前言 上文我们简单 将 HTML 过了一遍 &#xff0c; 知道了 HTML 知识表示页面的结构和内容 &#x…

【NLP相关】Transformer模型:从Seq2Seq到自注意力机制(Transformer原理、公式推导和案例)

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

逆向练习之 mingyue.exe wp

目录 一.查壳 二.主函数 三.operate函数 四.storage函数及4618和4620指针功能的解释 五.judge函数 六.求解flag 七.其他--ida字符识别问题 一.查壳 64位无壳 二.主函数 1.这里的pointer_4618和4620是两个相邻的八字节内存单元,其中4620是字符串链表表头head 2.puts和s…

自动驾驶规划 - 5次多项式拟合

简介 自动驾驶运动规划中会用到各种曲线&#xff0c;主要用于生成车辆的轨迹&#xff0c;常见的轨迹生成算法&#xff0c;如贝塞尔曲线&#xff0c;样条曲线&#xff0c;以及apollo EM Planner的五次多项式曲线&#xff0c;城市场景中使用的是分段多项式曲线&#xff0c;在EM …