概念:
什么是线程池:
线程池是用来存储多线程的容器,是一种处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
线程池使用和不使用的区别:
- 因为系统创建线程池的成本很高,会涉及到和操作系统交互,频繁的创建线程和销毁会对消耗系统资源
- 线程池会启动一个线程执行这个任务,如果不够可以自动加,执行完线程会进入空闲状态,不会销毁,等待下一次执行(很乖的)
不使用线程池会造成资源浪费
我们创建一个线程执行任务后线程死亡.如果线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
比如:中午吃完饭将碗洗干净,晚上接着使用 重复利用,可以节省资源
线程状态:
线程状态 | 具体含义 |
---|---|
NEW | 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。 |
RUNNABLE | 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的调度。 |
BLOCKED | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
四种线程池:
方法名 | 说明 |
---|---|
newCachedThreadPool | 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程 |
newFixedThreadPool | 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待 |
newScheduledThreadPool | 创建一个定长线程池,支持定时及周期性任务执行。 |
newSingleThreadExecutor | 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 |
线程池常用方法:
方法 | 说明 |
---|---|
submit() | 使用完毕归还线程 |
shutdown() | 关闭线程池 |
newCachedThreadPool:
构造方法
方法名 | 说明 |
---|---|
newCachedThreadPool() | 创建一个线程池,默认为空,最大容量是int最大值 |
newCachedThreadPool(ThreadFactory threadFactory) | 创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。 |
代码演示: |
public static void main(String[] args) throws InterruptedException {/**创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值idea会提醒你,手动创建会更好,这里的初始值是最大值,最大多少个线程Executors:创建线程池对象ExecutorService:控制线程池*/ExecutorService es = Executors.newCachedThreadPool();// submit:使用完毕归还线程es.submit(() -> System.out.println(Thread.currentThread().getName() + ": 执行"));/**这里让线程睡眠会导致只有一条线程运行原因:创建的时候没有指定线程,所以会自动创建一条线程,执行完放回线程池,然后睡眠完了发现线程池有刚才闲置的线程就直接用了不睡眠为什么会创建两个:先自动创建,使用完还没有来得及归还,代码就创建下一个线程了但是睡眠时间比较少的话,比如sleep(1),那么还是会创建两个线程*/Thread.sleep(1000);es.submit(() -> System.out.println(Thread.currentThread().getName() + ": 执行"));// 关闭线程池es.shutdown();}
newFixedThreadPool:
代码演示:
public static void main(String[] args) {// 参数不是初始值而是最大值ExecutorService es = Executors.newFixedThreadPool(10);System.out.println(((ThreadPoolExecutor) es).getPoolSize());// 0es.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));es.submit(() -> System.out.println(Thread.currentThread().getName() + "在执行了"));System.out.println(((ThreadPoolExecutor) es).getPoolSize());// 2es.shutdown();}
ThreadPoolExecutor自定义线程池:
参数详解:
参数一:核心线程数量
参数二:最大线程数量
参数三:空闲线程最大存活时间
参数四:时间单位,对应参数三的
参数五:任务列表(阻塞队列):让任务在队列中等,等到线程有空,再从队列中获取任务执行
参数六:创建线程工厂:按照默认的方式创建线程对象
参数七:任务的拒绝策略:
- 什么时候拒绝:
提交的任务 > (池子中最大线程数 + 队列容量)
- 如何拒绝:看下面的四种拒绝策略
线程类:
public class Runn implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "执行了");}
}
测试类:
public class Demo05 {public static void main(String[] args) {ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());tpe.submit(new Runn());tpe.submit(new Runn());tpe.shutdown();}
}
任务拒绝策略:
方法名 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常。是默认的拒绝策略,可灵活回收空闲线程,若无可回收,则新建线程 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常 这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
public class Demo {public static void main(String[] args) {/*ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());*//* ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());*/// 如果是最大线程数是3,队列是1,一共是4,创建10个线程,那么使用此拒绝策略,会打印,4个,但是打印的是线程1、2、3、10,10前面的就不要了/* ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());*/// 任务过多的时候直接缇跳过线程池让main方法执行多出的线程ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 5, 2,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 0; i < 16; i++) {tpe.submit(new Runn());}tpe.shutdown();}
}
volatile:
Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的,但是不能不能保证原子性
使用场景:加在共享数据上
当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题
- 堆内存是唯一的,每一个线程都有自己的线程栈。
- 每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
- 在线程中,每一次使用是从变量的副本中获取的。
定义循环测试,如果没有获取最新数据就会一直循环
共享数据类:
public class Share {public static int MONEY =10;
}
定义两个线程类:
public class libai extends Thread {@Overridepublic void run() {while (Share.MONEY = 1000) {}System.out.println("钱发生了变化");}
}
public class hanxin extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Share.MONEY = 900;}
}
测试类:
public class Demo04 {public static void main(String[] args) {hanxin t1 = new hanxin();t1.setName("韩信");t1.start();libai t2 = new libai();t2.setName("李白");t2.start();}
}
解决方案:
在共享数据上加上volatile就可以了
public class Share {public static volatile int MONEY =10;
}
synchronized:
上面的情况用synchronized也是可以解决的
原因是:
- synchronized会清空变量副本
- 拷贝共享变量最新的值到变量副本中
- 如果修改了共享数据,会把修改后的变量副本中的值赋值给共享数据
共享数据类:
public class Share {public static Object lock = new Object();public static volatile int MONEY = 10;
}
两个线程类:
public class hanxin extends Thread {@Overridepublic void run() {while (true) {synchronized (Share.lock) {if (Share.MONEY != 10) {System.out.println("数值发生变化");break;}}}}
}
public class libai extends Thread {@Overridepublic void run() {synchronized (Share.lock) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Share.MONEY = 1000;}}
}
测试类:
public class Demo04 {public static void main(String[] args) {hanxin t1 = new hanxin();t1.setName("韩信");t1.start();libai t2 = new libai();t2.setName("李白");t2.start();}
}
原子性AtomicInteger:
- 原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
- volatile只能保证线程每次在使用共享数据的值是最新的,但是不能保证原子性
- AtomicInteger原理 : 自旋锁 + CAS 算法
- CAS算法:
- 有3个操作数(内存值V, 旧的预期值A,要修改的值B)
- 当旧的预期值A == 内存值 此时修改成功,将V改为B
- 当旧的预期值A!=内存值 此时修改失败,不做任何操作
- 并重新获取现在的最新值(这个重新获取的动作就是自旋)
- 自旋:
- 在修改共享数据的时候,先把原来的旧值记录下来,然后要修改时候如果内存中和旧值的一样证明其他线程没操作内存值,那就修改成功,如果不一样说明其他线程操作了,就失败,会把最新的值从内存获取到旧值,然后再修改,重新获取就是自旋
例:
public class Demo06 implements Runnable {private int count = 0;@Overridepublic void run() {for (int i = 0; i < 100; i++) {/* 1、从共享数据中读取数据到本地线程栈中2、修改本线程栈中的变量副本值3、会把本线程栈中变量副本值赋值给共享数据异常:1、线程A刚修改了本线程栈中的变量副本值为101,还没同步给共享数据2、这时候线程B抢走了CPU的执行权,也修改了本线程栈中的变量副本值为101,然后同步给共享数据3、线程B同步完后,线程A也同步,但是两个线程提交的是同一个值。*/count++;System.out.println(count);}}
}
测试类:
public class Demo07 {public static void main(String[] args) {Demo06 d = new Demo06();for (int i = 0; i < 100; i++) {new Thread(d).start(); // 9999}}
}
lock锁可以解决,但是效率比较低,因为每次要去判断锁、获得锁、释放锁
public class Demo01 {public static void main(String[] args) {Run runn = new Run();for (int i = 0; i < 100; i++) {new Thread(runn).start();}}
}
class Run implements Runnable {private volatile int count = 0;private Object lock = new Object();@Overridepublic void run() {for (int i = 0; i < 100; i++) {synchronized (lock) {count++;System.out.println("已经打印了 " + count + " 次");}}}
}
AtomicInteger方法:
构造方法:
方法名 | 说明 |
---|---|
public AtomicInteger() | 初始化一个默认值为0的原子型Integer |
public AtomicInteger(int initialValue) | 初始化一个指定值的原子型Integer |
成员方法:
方法名 | 说明 |
---|---|
int get() | 获取值 |
int getAndIncrement() | 以原子方式将当前值加1,注意,这里返回的是自增前的值 |
int incrementAndGet() | 以原子方式将当前值加1,注意,这里返回的是自增后的值 |
int addAndGet(int data) | 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果 |
int getAndSet(int value) | 以原子方式设置为newValue的值,并返回旧值 |
: 。
public static void main(String[] args) {// int get(): 获取值AtomicInteger ai1 = new AtomicInteger(10);System.out.println(ai1.get()); //10 // int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。AtomicInteger ai2 = new AtomicInteger(10);System.out.println(ai2.getAndIncrement());//10System.out.println(ai2.get());//11// int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。AtomicInteger ai3 = new AtomicInteger(10);System.out.println(ai3.incrementAndGet());//11// int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。AtomicInteger ai4 = new AtomicInteger(10);System.out.println(ai4.addAndGet(1)); //11System.out.println(ai4.get()); //11// int getAndSet(int value): 先获得再设置新值。AtomicInteger ai5 = new AtomicInteger(10);System.out.println(ai5.getAndSet(1)); //10System.out.println(ai5);//1}
AtomicInteger解决共享数据问题:
public class Demo09 implements Runnable {AtomicInteger ac = new AtomicInteger(0);@Overridepublic void run() {for (int i = 0; i < 100; i++) {int count = ac.incrementAndGet();System.out.println(count);}}
}
incrementAndGet源码解析:
//先自增,然后获取自增后的结果
public final int incrementAndGet() {//+ 1 自增后的结果//this 就表示当前的atomicInteger(值)//1 自增一次return U.getAndAddInt(this, VALUE, 1) + 1;
}public final int getAndAddInt(Object o, long offset, int delta) {//v 旧值int v;//自旋的过程do {//不断的获取旧值v = getIntVolatile(o, offset);//如果这个方法的返回值为false,那么继续自旋//如果这个方法的返回值为true,那么自旋结束//o 表示的就是内存值//v 旧值//v + delta 修改后的值} while (!weakCompareAndSetInt(o, offset, v, v + delta));//作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。// 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。//如果修改失败,那么继续自旋。return v;
}
synchronized和CAS的区别 :
相同点:
在多线程情况下,都可以保证共享数据的安全性。
不同点:
synchronized(悲观锁
):总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。
cas(乐观锁)
:是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值。如果别人没有修改过,那么我现在直接修改共享数据的值
Hashtable
Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。
效率低的原因:因为底层是悲观锁,整个表都锁起来,那其他线程在外面等就会变的效率很低
public static void main(String[] args) throws InterruptedException {Hashtable<String, String> hm = new Hashtable<>(100);Thread t1 = new Thread(() -> {for (int i = 0; i < 25; i++) {hm.put(i + "", i + "");}});Thread t2 = new Thread(() -> {for (int i = 25; i < 51; i++) {hm.put(i + "", i + "");}});t1.start();t2.start();System.out.println("----------------------------");//为了t1和t2能把数据全部添加完毕Thread.sleep(1000);//0-0 1-1 ..... 50- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}
ConcurrentHashMap:
ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。
ConcurrentHashMap1.7和1.8区别:
JDK1.7
JDK1.8
- 如果使用空参构造ConcurrentHashMap对象,则什么事情都不做,在第一次添加元素的时候创建哈希表
- 计算当前元素应存入的索引
- 如果该索引位置为null,则利用cas算法,将本节点添加到数组中
- 如果该索引位置不为null,则利用volatile关键字获得当前位置最新结点地址,挂载它下面,变成链表
- 当链表的长度大于等于8时,自动转成红黑树
- 以链表或者红黑树头结点为锁对象,配合synchronized保证多线程操作集合时数据的安全性
public static void main(String[] args) throws InterruptedException {ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);Thread t1 = new Thread(() -> {for (int i = 0; i < 25; i++) {hm.put(i + "", i + "");}});Thread t2 = new Thread(() -> {for (int i = 25; i < 51; i++) {hm.put(i + "", i + "");}});t1.start();t2.start();System.out.println("----------------------------");//为了t1和t2能把数据全部添加完毕Thread.sleep(1000);//0-0 1-1 ..... 50- 50for (int i = 0; i < 51; i++) {System.out.println(hm.get(i + ""));}//0 1 2 3 .... 50}
CountDownLatch:
使用场景: 让某一条线程等待其他线程执行完毕之后再执行
方法 | 解释 |
---|---|
public CountDownLatch(int count) | 参数传递线程数,表示等待线程数量 |
public void await() | 让线程等待 |
public void countDown() | 当前线程执行完毕 |
线程一:
public class Thread1 extends Thread {private CountDownLatch countDownLatch;public Thread1(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {// 让线程等待for (int i = 0; i < 10; i++) {System.out.println("线程一在执行第 " + i + " 次");}// 当前线程执行完毕,计数器-1,这时候线程还剩1countDownLatch.countDown();}
}
线程二:
public class Thread2 extends Thread {private CountDownLatch countDownLatch;public Thread2(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {// 让线程等待for (int i = 0; i < 10; i++) {System.out.println("线程二在执行第 " + i + " 次");}// 当前线程执行完毕,计数器-1,这个时候计数器为0countDownLatch.countDown();}
}
父类线程:
public class FuThread extends Thread {private CountDownLatch countDownLatch;public FuThread(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}@Overridepublic void run() {//1.等待try {//当计数器变成0的时候,会自动唤醒这里等待的线程。countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("父类线程执行完毕");}
}
测试类:
public class Test {public static void main(String[] args) {//1.创建CountDownLatch的对象,需要传递给四个线程。//在底层就定义了一个计数器,此时计数器的值就是2CountDownLatch countDownLatch = new CountDownLatch(2);//2.创建三个线程对象并开启他们。FuThread motherThread = new FuThread(countDownLatch);motherThread.start();Thread1 t1 = new Thread1(countDownLatch);t1.setName("线程一");Thread2 t2 = new Thread2(countDownLatch);t2.setName("线程二");t1.start();t2.start();}
}
Semaphore:
使用场景 :
可以控制访问特定资源的线程数量。
比如有一个路口没有交警、没有红绿灯、会引发交通事故,可以做的和收费站一样发通行证、一次只能进来一辆车、或者两辆车、其他的等车想进来必须等这两个出去一个或者都出去、semaphore就是做这个,就是管理员的身份,也可以理解是有两个锁吧,执行完就释放锁,在执行就等待
实现步骤 :
- 需要有人管理这个通道
- 当有车进来了,发通行许可证
- 当车出去了,收回通行许可证
- 如果通行许可证发完了,那么其他车辆只能等着
线程类:
public class Thread1 implements Runnable{//1.获得管理员对象,private Semaphore semaphore = new Semaphore(2);@Overridepublic void run() {//2.获得通行证try {semaphore.acquire();//3.开始行驶System.out.println("获得了通行证开始行驶");Thread.sleep(2000);System.out.println("归还通行证");//4.归还通行证semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}}
}
测试类:
public class Test {public static void main(String[] args) {Thread1 t= new Thread1();for (int i = 0; i < 5; i++) {new Thread(t).start();}}
}