JUC编程
一、什么是JUC
什么是JUC:java.util.concurrent包名的简写,是关于并发编程的API。
与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。
也查看了一些前辈的博客,这里使用了一下别人画的知识体系图,后续根据以下方面进行记录总结。
二、线程基础知识
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
线程六种状态:新建(new) ,运行(RUNNABLE),阻塞(BLOCKED),等待(WAITING),延迟等待(TIMED_WAITING),终止(TERMINATED)
wait/Sleep 区别
1.类不同
s.wait(1000);//object 类 TimeUnit.SECONDS.sleep(1);//Thread 类
2.释放资源不同
sleep:抱着锁睡得,不会释放锁,会自己醒!wait 会释放锁,不会自己醒,需要唤醒!
3.使用范围不同
wait 和 notify 是一组,一般在线程通信的时候使用!notify是为了唤醒wait资源的
sleep 就是一个单独的方法,在那里都可以用
4.关于异常
sleep 需要捕获异常!
两者都可以暂停线程的执行。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。线程不会自动苏醒
三、线程锁
1、synchronized 传统方式
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的(后面会在8锁现象里具体说明)
先看传统的synchronized写法:
/* *线程操作资源类,资源类是单独的 */ public class Demo1 { public static void main(String[] args) { Ticket ticket = new Ticket(); //线程操控资源类 new Thread(new Runnable() { public void run() { for (int i = 0; i <30 ; i++) { ticket.saleTicket(); } } },"A").start(); new Thread(new Runnable() { public void run() { for (int i = 0; i <30 ; i++) { ticket.saleTicket(); } } },"B").start(); new Thread(new Runnable() { public void run() { for (int i = 0; i <30 ; i++) { ticket.saleTicket(); } } },"C").start(); } } //资源类 假设我们在卖票 class Ticket{ private int num = 100; public synchronized void saleTicket(){ if (num>0){ System.out.println(Thread.currentThread().getName() +"卖出第"+(num--)+"票,还剩"+num); } } }
2、Lock锁
锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有 2.线程执行发生异常,此时JVM会让线程自动释放锁
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能地等待,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到
public class Demo2 { public static void main(String[] args) { Ticket2 ticket = new Ticket2(); new Thread(()->{ for (int i = 0; i < 30; i++)ticket.saleTicket(); },"A").start(); new Thread(()-> { for (int i = 0; i <30 ; i++) ticket.saleTicket(); },"B").start(); new Thread(()->{ for (int i = 0; i < 30; i++) ticket.saleTicket(); },"C").start(); } } //资源类 假设我们在卖票 class Ticket2{ // 使用Lock,它是一个对象 // ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...) // ReentrantLock 默认是非公平锁! // 非公平锁: 不公平 (插队,后面的线程可以插队) // 公平锁: 公平(只能排队,后面的线程无法插队) // ReentrantLock(轻量级锁)也可以叫对象锁,可重入锁,互斥锁 private Lock lock = new ReentrantLock(); private int num = 100; public void saleTicket(){ lock.lock(); try { if (num>0){ System.out.println(Thread.currentThread().getName() +"卖出第"+(num--)+"票,还剩"+num); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
注意lock.lock()不能放在try里面
说明:因为在 try-finally 外加锁的话,如果因为发生异常导致加锁失败,try-finally 块中的代码不会执行。相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行,但是由于当前线程加锁失败并没有持有 lock 对象锁,所以程序会抛出异常。
3、Synchronized 和 Lock 区别
1、Synchronized 是一个关键字、Lock 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;
4、关于锁的问题,彻底理解锁
考虑问题:有几把锁,锁的是谁?
1、锁的是具体的对象 new
2、锁的是唯一的模板 static
synchronized锁的对象是方法的调用者!
staitc synchronized锁的对象是唯一的class模板
1、第一种问题
/* * 1、两个同步方法情况下,两个线程先打印 发短信?打电话? * 2、线程A睡眠2s,两个线程先打印 发短信?打电话? * */ public class Test1 { public static void main(String[] args) { //一个对象 phone1 phone1 = new phone1(); //A线程 new Thread(() -> { phone1.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone1.call(); }, "B").start(); } } class phone1 { public synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } public synchronized void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远都是先执行发短信,再执行打电话!
该问题中,有一把锁,锁的是phone1具体的对象
A线程先拿到锁(操作phone1对象),即便睡眠了2s,在没有释放锁之前,B线程无法操作phone1对象(在同步的情况下)
2、第二种问题
/* * 一个同步,一个普通方法情况下,先执行 发短信?打电话? * */ public class Test2 { public static void main(String[] args) { //一个对象 phone2 phone2 = new phone2(); //A线程 new Thread(() -> { phone2.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone2.call(); }, "B").start(); } } class phone2 { //同步的 public synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } //非同步的 public void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远都是先打电话,再发短信!
该问题中,有一把锁,锁的是phone2对象
虽然A线程先拿到锁,但是B线程要执行的方法是非同步的,不用判断锁的存在,可直接执行!
3、第三种问题
/* * 两个对象,两个同步方法;先执行发短信?打电话? * */ public class Test3 { public static void main(String[] args) { //两个对象 phone3 phone31 = new phone3(); phone3 phone32 = new phone3(); //A线程 new Thread(() -> { phone31.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone32.call(); }, "B").start(); } } class phone3 { public synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } public synchronized void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远先执行打电话,再执行发短信!
该问题中,有两个锁,分别锁的是phone31,phone32对象
A线程拿到锁(phone31的)后睡眠,但是B线程拿的是(phone32的锁),操作的不是phone31的对象;所以B线程在A线程睡眠时,先执行!
4、第四种问题
/* * 两个静态同步方法,只有一个对象;先执行 发短信?打电话? * */ public class Test4 { public static void main(String[] args) { //一个对象 phone4 phone4 = new phone4(); //A线程 new Thread(() -> { phone4.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone4.call(); }, "B").start(); } } class phone4 { //静态同步方法 public static synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } //静态同步方法 public static synchronized void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远先执行发短信,再执行打电话!
该问题中,有一把锁,锁的是class模板
A线程先拿到模板的锁,即便睡眠,在不释放锁之前,B线程无法操作这个class模板(同步的前提下)
5、第五种问题
/* * 两个对象,两个静态同步方法;先执行发短信?打电话? * */ public class Test5 { public static void main(String[] args) { //两个对象 phone5 phone51 = new phone5(); phone5 phone52 = new phone5(); //A线程 new Thread(() -> { phone51.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone52.call(); }, "B").start(); } } class phone5 { //静态同步方法 public static synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } //静态同步方法 public static synchronized void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远先执行发短信,再执行打电话!
该问题中,还是一把锁,锁的是唯一的class模板
sendSms和call方法,他们都是静态的,存在于class模板中;而不是具体的对象中
即便A线程使用了phone51对象,但是操作的方法在class模板中,是class锁,在不释放锁的前提下,B线程无法操作class模板(同步的前提下)
7、第七种问题
/* * 1个静态的同步方法,一个普通的同步方法,两个对象;先打印 发短信?打电话? * */ public class Test7 { public static void main(String[] args) { //两个对象 phone7 phone71 = new phone7(); phone7 phone72 = new phone7(); //A线程 new Thread(() -> { phone71.sendSms(); }, "A").start(); //线程睡眠1s try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } //B线程 new Thread(() -> { phone72.call(); }, "B").start(); } } class phone7 { //静态同步方法 public static synchronized void sendSms() { //调用该方法的线程先睡眠2s try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->发短信!"); } //普通同步方法 public synchronized void call() { System.out.println(Thread.currentThread().getName() + "--->打电话!"); } }
永远先执行打电话,再执行发短信!
该问题中,有两把锁;锁的是唯一的class模板、phone72对象
A线程拿到class模板的锁,睡眠后;B线程拿到phone72对象的锁,执行call方法,它们是两把锁
四、集合类不安全
1、List 不安全
我们通常使用的集合类都是非同步的,除了vector
当多个线程,向同一个集合写入数据时
ConcurrentModificationException 并发修改异常!
public static void main(String[] args) { //我们通常使用的List集合都是非同步的 除了vector List<String> list=new ArrayList<>(); /* * 解决方案 * 1、vector集合(效率低) * 2、Collections.synchronized(list) (使用集合工具类,将非同步的集合变为同步的) * 3、CopyOnWrite 写入时复制 * 当多个线程同时向list中写入数据时,存在写入覆盖问题 * 在写入的时候复制,避免另一个线程将某一线程未写完的数据覆盖 * */ for (int i = 1; i < 10; i++) { new Thread(()->{ //多个线程向同一个集合中写入数据 list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } }
2、Set 不安全
与上同理,CopyOnWriteArraySet
HashSet底层是什么?
public HashSet() { map = new HashMap<>(); } private static final Object PRESENT = new Object(); // 不变值! // add set 本质就是 map key是无法重复的! public boolean add(E e) { return map.put(e, PRESENT)==null; }
3、Map 不安全
回顾Map基本操作
// ConcurrentModificationException public static void main(String[] args) { // map 是这样用的吗? 不是 // 默认等价于什么? new HashMap<>(16,0.75); //并发下的map Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 1; i <=30; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring( 0,5)); System.out.println(map); },String.valueOf(i)).start(); }
7、Callable接口
实际上,和Runnable的功能一样,区别
run()–>call()
无返回值–>有返回值(与泛型中定义的一致)
不抛出异常–>可以抛出异常
但是,开启线程的方式,只有new Thread(Runnable接口).start !
所以,我们需要找到一个类,该类与Runnable、Callable都有关系!
FutureTask类:该类是Runnable接口的实现类,可以接收Callable对象
public static void main(String[] args) throws ExecutionException, InterruptedException { /* * 开启线程,只能用Runnable接口才行 * FutureTask是Runnable的实现类,并且可以接收一个Callable对象 * */ MyThread myThread = new MyThread(); FutureTask futureTask = new FutureTask(myThread); new Thread(futureTask).start(); String str = (String) futureTask.get(); System.out.println(str); } } class MyThread implements Callable<String> { @Override public String call() throws Exception { System.out.println("call方法执行了!"); return "call()"; }
8、常用的辅助类
1、CountDownLatch
减法计数器,当线程数量不减为0时,不往后执行!
/* * 线程的计数器! * 当线程数量不减为0时,不向下继续执行! * * 注意:匿名内部类要使用的变量,作用域为final * */ public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { //总数是7,必须在线程执行后,让该数-1 CountDownLatch countDownLatch = new CountDownLatch(7); for (int i = 1; i <= 7; i++) { //lambada表达式,实际上就是一个匿名内部类,无法直接使用i变量 //需要final域的变量 final int temp=i; new Thread(()->{ System.out.println(Thread.currentThread().getName()+"--->Go out"); //告诉CountDownLatch,线程数量-1 countDownLatch.countDown(); },String.valueOf(i)).start(); } //进行等待操作,当线程数量不减为0,永久等待,之后的代码不执行! countDownLatch.await(); System.out.println("线程数量为0了!!!"); } }
2、CyclicBarrier
加法计数器,当线程数量达到某个值后,开启一个新线程,执行一段代码
/* * 加法计数器 * 当线程的数量达到某个值时,开启一个新线程,执行一段代码 * */ public class CyclicBarrierTest { public static void main(String[] args) { //加法计数器,线程数量达到7时,执行一段新的线程 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { System.out.println("召唤神龙成功!"); }); for (int i = 1; i <= 7; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "--->个龙珠"); // 每开启一个线程,cyclicBarrier对象中的值就会自动-1 // 减为0时,表示开启了这么多个线程,并去执行一段新的线程! try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }, String.valueOf(i)).start(); } } }
3、Semaphore
信号量机制,为了让多个共享资源,进行互斥的使用!
可以做限流功能,控制最大的线程数!
/* * 信号量,让多个共享资源,进行互斥的使用 * */ public class SemaphoreTest { public static void main(String[] args) { //表示有3个信号量 //每个线程开启时,拿走一个信号量 //每个线程关闭后,释放一个信号量 Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(()->{ try { //开启一个线程后,拿走一个信号量 -1 semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"--->线程开启了!"); TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }finally { //线程运行结束后,释放一个信号量 +1 semaphore.release(); System.out.println("--------------------------------------"); System.out.println(Thread.currentThread().getName()+"--->线程结束了!"); } },String.valueOf(i)).start(); } } }
结果:在同一时刻,只有三个线程能进行操作!
9、读写锁
也叫独占锁,共享锁
即,可以有多个线程读,但是只允许一个线程写!
public static void main(String[] args) { MyThread myThread = new MyThread(); //开启写线程 for (int i = 1; i <= 5; i++) { final String temp = String.valueOf(i); //注意:匿名内部类,只能使用final作用域的变量 new Thread(() -> { myThread.put(temp, temp); }, String.valueOf(i)).start(); } //开启读线程 for (int i = 1; i <= 5; i++) { final String temp=i+""; new Thread(()->{ myThread.get(temp); },String.valueOf(i)).start(); } } } class MyThread { private volatile Map<String, Object> map = new HashMap<>(); //获得一把读写锁 private ReadWriteLock readWriteLock=new ReentrantReadWriteLock(); //写操作,只允许一个线程写 public void put(String key, Object value) { //写操作,使用独占锁 readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "开始写入..."); map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入完成!"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } //读操作,可以有多个线程读 public void get(String key) { //读操作,使用共享锁 readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "开始读取..."); Object value = map.get(key); System.out.println(Thread.currentThread().getName() + "读取完成!值为:" + value); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } }
10、阻塞队列
BlockingQueue阻塞队列
Queue队列
它并不是什么新东西
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
1、BlockingQueue的四组API
add remove 抛出异常
offer poll 有返回值,无异常
put take 无返回值,永久等待
offer poll 超时等待
11、线程池
Executors工具类,类似于Collections,Arrays;用来创建线程池的!
1、创建单个线程的线程池
public static void main(String[] args) { //1、创建线程池 //线程池中只有一个线程! ExecutorService pool1 = Executors.newSingleThreadExecutor(); //2、使用线程池来开启线程 for (int i = 1; i < 5; i++) { //executor开启线程池中的一个线程,并执行runnable中的run方法 pool1.execute(()->{ System.out.println(Thread.currentThread().getName()+" ok "); }); } //3、程序运行结束,关闭线程池 pool1.shutdown(); }
2、创建固定大小的线程池
public static void main(String[] args) { //1、创建线程池 //创建一个固定的线程池大小 ExecutorService pool2 = Executors.newFixedThreadPool(5); //2、使用线程池来开启线程 for (int i = 1; i < 5; i++) { //executor开启线程池中的一个线程,并执行runnable中的run方法 pool2.execute(()->{ System.out.println(Thread.currentThread().getName()+" ok "); }); } //3、程序运行结束,关闭线程池 pool2.shutdown(); }
3、创建可伸缩大小的线程池
public static void main(String[] args) { //1、创建线程池 //创建一个可伸缩大小的线程池 ExecutorService pool3 = Executors.newCachedThreadPool(); //2、使用线程池来开启线程 for (int i = 1; i < 5; i++) { //executor开启线程池中的一个线程,并执行runnable中的run方法 pool3.execute(()->{ System.out.println(Thread.currentThread().getName()+" ok "); }); } //3、程序运行结束,关闭线程池 pool3.shutdown(); }
注意:线程池用完,程序结束,记得关闭线程池!shutdown();
4、七大参数
我们使用Executors工具类创建线程池时发现
不论创建哪一种线程池,都使用了ThreadPoolExecutor对象来创建线程!
//Executors.newSingleThreadExecutor public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor (1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
//Executors.newFixedThreadPool public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor (nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor (0, Integer.MAX_VALUE, //该值为21亿! 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
ThreadPoolExecutor对象有7大参数!
1、corePoolSize
核心线程池大小;线程池一被创建,池中就有X个核心线程可以直接运行,不会被释放!
2、maximumPoolSize
最大线程池大小;线程池中可以并发执行的全部线程数量;
当要开启一个新线程时,会先进入阻塞队列中;先由核心线程帮你完成功能
当阻塞队列满了,才会再开启一个线程!
3、keepAliveTime
超时了没有人调用,就会释放,只保留核心线程!
4 TimeUnit
超时单位
5、BlockingQueue
阻塞队列(阻塞队列也有四组API)!
除了核心线程外,当你想要再开启一个新的线程时,会先进入阻塞队列!
6、ThreadFactory
线程工厂:创建线程的,默认的即可!
Executors.defaultThreadFactory();
7、RejectedExecutionHandler
拒绝策略(4种拒绝策略)!
当达到了最大线程池数,阻塞队列也满了之后
再要开启线程时,使用拒绝策略!
8、ThreadPoolExecutor手动创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 2, //核心线程池大小 5, //最大线程池大小 2, //超时了没有人调用,就会释放 TimeUnit.SECONDS, //超时单位 new LinkedBlockingDeque<>(3), //阻塞队列 Executors.defaultThreadFactory(), //线程工厂:用来创建线程,默认即可 new ThreadPoolExecutor.AbortPolicy() //拒绝策略 );
9 简单理解
12、四种拒绝策略
1 AbortPolicy
抛出异常!
2 CallerRunsPolicy
谁开启的这个线程,谁来执行!(例如:main线程)
3 DiscardPolicy
丢掉任务,不会抛出异常!
4 DiscardOldestPolicy
尝试去和最早的竞争,也不会抛出异常!
5 自定义拒绝策略
private class CustomRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { // 记录异常 // 报警处理等 System.out.println("error............."); try { executor.getQueue().put(r); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
13、池的最大的大小如何去设置
1 CPU密集型
CPU为几核,就将最大线程数设置为几,这样这些线程之间可以并行执行!保持CPU的效率最高!
//获取当前的CPU核数 System.out.println(Runtime.getRuntime().availableProcessors());
2 IO密集型
将最大线程数设置为:大于程序中十分耗IO的线程数
因为IO操作的效率很低,使用多线程来操作可以提高效率!将这些线程都存放在线程池中,使用线程池来开启多线程,执行IO操作!