目录
线程状态:
虚拟机中线程的六种状态:
- 新建状态(NEW) -------------> 创建线程对象
- 就绪状态(RUNNABLE) -------------> start方法
- 阻塞状态(BLOCKED) ------------->无法获得锁对象
- 等待状态(WAITING) -------------> wait方法
- 计时状态(TIMED_WAITING) -------------> sleep方法
- 结束状态(TERMINATED) -------------> 全部代码运行完毕
在Thread类内部State枚举内部类中可以看到6中状态
线程池
基本原理(更加的高效的使用多线程):
- 创建一个空的池子
- 有任务要执行时,才会创建线程对象,当任务执行完毕,线程对象归还给池子
- 所有的任务全部执行完毕,关闭连接池
创建一个池子用的是一个类Executors -----------------------》 创建Executors中的静态方法
创建线程、执行、归还 ------------------------》submit方法
所有的任务全部执行完毕,关闭连接池 ------------------------》shutdown方法
Executors创建线程池的方法:
- static ExecutorService newCachedThreadPool() 创建一个默认的线程池
public class MyTest {
public static void main(String[] args) {
//1、创建一个默认的线程池对象,池子中默认是空的,默认最多可以容纳int类型的最大值。
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors --可以帮助我哦们创建线程池对象
//ExecutorService ---可以帮助我们控制线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
try {
//Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"在执行了");
});
executorService.shutdown();
}
}
- static newFixedThreadPool (int nThreads) 创建一个指定最多线程数量的线程池
public class MyThreadPoolDemo {
public static void main(String[] args) {
//参数不是初始值 而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"开始执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"开始执行了");
});
executorService.shutdown();
}
}
上面两个类的底层都是ThreadPoolExecutor实现的
自定义线程池ThreadPoolExecutor
所以自定义线程池对象需要创建ThreadPoolExecutor对象
- 用员工,顾客 ---------类比---------->线程、任务
正式员工数量 ------->核心线程数
餐厅最大员工数 -------->线程池中最大线程的数量
临时员工空闲多长时间被辞退(值) -------->空闲时间(值)
临时员工空闲多长时间被辞退(单位) --------->空闲时间(单位)
排队的客户 ---------->阻塞队列
从哪里招人 ---------->创建线程的方式
当排队人数过多,超出顾客(拒绝服务) ----------->要执行的任务过多时的解决方案
- 创建线程池对象格式:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
参数一:核心线程数 不能小于0
参数二:最大线程数量 不能小于等于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 时间单位 TimeUtil类中的
参数五:任务队列 不能为null,任务在队列中等着,有空闲线程后获取并执行
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null:当提交的任务>池中最大线程数量+队列容量
public class MyThreadPoolDemo3 {
public static void main(String[] args) {
//参数一:核心线程数 不能小于0
//参数二:最大线程数量 不能小于等于0,最大数量>=核心线程数量
//参数三:空闲线程最大存活时间 不能小于0
//参数四:时间单位 时间单位 TimeUtil类中的
//参数五:任务队列 不能为null
//参数六:创建线程工厂 不能为null
//参数七:任务的拒绝策略 不能为null
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,10,5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
threadPoolExecutor.submit(()->{
System.out.println(Thread.currentThread().getName()+"执行了");
});
threadPoolExecutor.submit(()->{
System.out.println(Thread.currentThread().getName()+"执行了");
});
threadPoolExecutor.shutdown();
}
}
线程池的拒绝策略
- ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。时默认的策略。
- ThreadPoolExecutor.DiscardPolicy:丢弃任务,但并不抛出异常,这是不推荐的做法
- ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务 然后把当前任务加入队列中
- ThreadPoolExecutor.CallerRunsPolicy:调用run的方法,绕过线程池,直接执行
volatile关键字
作用:强制线程去看共享数据的最新值
public class Money {
public static volatile int money=100000;
}
public class Thread1 extends Thread{
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money=90000;
}
}
public class Thread2 extends Thread{
@Override
public void run() {
while (Money.money == 100000){
}
System.out.println("基金已经不是10万了");
}
}
public class MyTest {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
t2.start();
}
}
synchronized同步代码块:
作用:强制相处去看共享数据的最新值
- 线程获得锁
- 清空变量副本
- 拷贝共享变量最新的值到变量副本中
- 执行代码
- 将修改后变量副本中的值赋值给共享数据
- 释放锁
public class Money {
public static int money=100000;
public static final Object lock = new Object();
}
public class Thread1 extends Thread{
@Override
public void run() {
synchronized(Money.lock){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money=90000;
}
}
}
public class Thread2 extends Thread{
@Override
public void run() {
synchronized (Money.lock){
while (Money.money == 100000){
}
System.out.println("基金已经不是10万了");
}
}
}
public class MyTest {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
t2.start();
}
}
原子性及原子操作类
多个操作是一个不可分割的整体,要么同时成功,要么同时失败。
volatile:只能保证线程每次在使用共享数据的时候是最新值。但是不能保证原子性。
AutomicInteger原子操作类。
构造方法1:AutomicInteger(); 初始值为0
构造方法2:AtomicInteger(int value);用给定的初始值创建一个新的AtomicInteger
常用方法:
- int get(); 获取值
- int getAndIncrement();以原子方式将当前值加1,注意,这里返回的是自增前的值。
- int incrementAndGet();以原子方式将当前值加1,注意,这里返回的是自增后的值。
- int addAndGet(int data);以原子方式将参数与对象中的值相加,并返回结果。
- int getAndSet(int value);以原子方式设置为newValue的值,并返回旧值。
public class MyAtomThread implements Runnable {
private int count=0;
AtomicInteger ac = new AtomicInteger();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
count = ac.incrementAndGet();
System.out.println("已经第了"+count+"次表白了");
}
}
}
public class MyTest {
public static void main(String[] args) {
MyAtomThread myAtomThread = new MyAtomThread();
for (int i = 0; i < 1000; i++) {
new Thread(myAtomThread).start();
}
}
}
AutomicInteger原理:自旋锁+CAS算法
CAS算法:有3个操作数(内存值V,旧的预期值A,要修改的值B)
- 当旧的预期值A == 内存值 此时修改成功,将V改为B
- 当旧的预期值A != 内存值 此时修改失败,不做任何操作
- 并重新获取现在的最新值(这个重新获取的工作就是自旋)
incrementAndGet源码解析
//先自增,然后获取自增后的结果
public final int incrementAndGet() {
//this指当前的atomicInteger(值)
//
//1 自增1次
//+1 自增后的结果
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
//旧值
int var5;
//do-while 是自旋的过程
do {
//不断获取旧值
var5 = this.getIntVolatile(var1, var2);
//如果为true 结束自旋,如果为false继续自旋
//比较内存中的值、旧值是否相等,如果相等就把修改后的值写到内存中,返回true 表示修改成功
// 如果不相等,无法把修改后的值写到内存,返回false,表示修改失败
//如果修改失败,继续自旋
//var1 = 内存值 ; var5 = 旧值 ; var5 + var4 = 修改后的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
synchronized和CAS的区别
相同点:在多线程的情况下,都可以保证共享数据的安全性
不同点:
- synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每一次操作共享数据之前都会上锁。(悲观锁)
- cas是从乐观锁的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候会检查一下,别人有没有修改这个数据。如果别人修改过,那么我再次获取现在的最新值。如果别人没有修改过,那么我现在直接修改共享数据的值。(乐观锁)
HashMap是线程不安全的,多线程情况下会存在线程不安全情况。
默认长度为16,加载因子为0.75,当数组长度达到12时,数组扩容为原来的2倍
Hashtable
- 采用的是悲观锁synchronized的形式保证数据的安全性
- 只要有线程访问,会将整张表锁起来,所以Hashtable的效率底下
CurrentHashMap
线程安全的,效率高
- JDK1.7底层原理:
- 创建对象 :
- 默认创建一个长度为16,加载因子为0.75的大数组。这个大数组一旦创建无法扩容
- 还会创建一个长度为2的小数组,把地址值赋值给0索引处,其他索引位置的元素都是null
- 添加
- 如果为null,则按照模板创建小数组;创建完毕,会二次哈希,计算出在小数组中应存入的位置。直接存入
- 如果不为null,就会根据记录的地址值找到小数组。二次哈希,计算出在小数组中应存入的位置。
- 如果需要扩容,则将小数组扩容两倍
- 如果不需要扩容,就会判断小数组的这个位置有没有元素
- 如果没有元素,直接存入
- 如果有元素,会调用equals方法比较属性值
- 如果equals为true,则不存
- 如果为false,形成哈希桶结构
- JDK1.8底层原理
底层结构:哈希表。(数组、链表、红黑树的结合体)
结合CAS机制+synchronized同步代码块形式保证线程安全。
- 如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。在第一次添加元素的时候创建哈希表
- 根据键计算当前元素应存入的索引
- 如果该索引位置为null,则利用cas算法,将本结点添加到数组中
- 如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。
- 当链表的长度大于8时,自动转换成红黑树
- 以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。
CountDownLatch:
使用场景:让某一线程在等待其他线程执行完毕之后再执行
CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。
await():让线程等待,当计数器为0时,会唤醒等待的线程。
countDown():线程执行完毕时调用,会将计数器-1
Semaphore
Semaphore(int permits):构造方法 最多允许permits个线程执行任务
acquire() :获取线程执行的许可证
release():释放许可证
public class MyRunnable implements Runnable {
@Override
public void run() {
//创建一个信号管理员对象
Semaphore sm = new Semaphore(2);
try {
//获取执行许可证,获取到了执行,获取不到阻塞
sm.acquire();
//获取许可证之后的执行逻辑
System.out.println("获取到许可证,执行成功");
//释放许可证
sm.release();
System.out.println("执行已完成,归还许可证");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class SemaphorDemo {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
for (int i = 0; i < 100; i++) {
new Thread(my).start();
}
}
}