一、什么是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模板(同步的前提下)
6、第六种问题
/*
* 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队列
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、创建可伸缩大小的线程池
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
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 简单理解
四种拒绝策略
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操作!