多线程
1. 多线程概述
1.1 多线程
cpu在处理数据时,同时处理多个任务,就是多线程
1.2 并发和并行
- 并发:同一时段内,交替执行多个任务
- 并行:同一时刻.同时执行多个任务
1.3 进程和线程
- 进程:正在运行的一个软件或程序
- 线程:正在运行的一个程序内的一个执行单元
- 单线程:一个进程中只有一个线程
- 多线程:一个进程内有多个线程
main方法执行也是需要线程的,由JVM虚拟机自动创建
2. 多线程的三种实现方式
2.1 继承Thread
- 创建一个Thread线程对象,并继承Thread
- 重写父类的run方法
- run方法内写线程运行的代码
- 创建Thread对象
- 调用start()方法,开启线程
Thread t = new Thread;
t.start();
t.run();
t.start()是开启一个新的线程,在新的线程中执行run方法
t.run()是在当前线程中执行run方法
2.2 实现Runnable接口
- 每一个Runnable接口实现类对象代表一个线程任务
new Thread(Runnable对象).start
- 用Thread的start()方法开启线程
2.3 实现callable接口
- 重写callable的call方法
FutureTask ft = new FutrueTask(Call对象);
Thread t = new Thread(ft);
t.start();
ft.get();//得到线程执行结果
创建FutureTask对象
创建线程对象
启动线程
get()方法可以获得线程的执行结果
3. Thread的常用方法
3.1 设置获取名字
Thread(String name) / void setName(String name)//设置名字
String getName()//获取名字
3.2 获取线程对象
currentThread()//获取当前线程对象
谁执行就获取谁
3.3 睡眠方法
sleep(毫秒值)//睡眠/休眠
谁执行,谁就休眠
3.4 线程的优先级
- 调配制度
- 分时调度:每个线程抢到的cpu执行的时间片段相同
- 抢占式调度:根据优先级抢占cpu的使用权
- 线程优先级的范围是1~10
- 默认优先级为5
3.5 守护线程
- 守护线程和普通线程的区别
- 普通线程:把线程执行完毕就退出
- 只要所有普通线程执行完毕,就退出
void setDaemon(boolean false)
4. 线程安全问题
4.1 出现的原因
- 多线程程序
- 多个线程必须要共享资源
- 多个线程对共享资源进行增删改操作
4.2 同步代码块
对代码进行加锁
synchronized(锁对象){
//代码
}
- 要求
- 锁对象可以使任意的java对象
- 多线程拿到的锁是同一把锁
- 在锁期间运行时,其他线程无法争抢执行权
4.3 锁对象唯一
4.4 同步方法
用synchronized对方法进行加锁
public [static] synchronized 返回值类型 方法名(){
}
- 非静态同步方法的锁是this
- 静态同步方法锁是类名.class某个类的字节码文件描述对象在一次程序的运行过程中,只会有一个
- 加锁会造成代码执行效率的下降:因为多个线程只能争抢执行相同的代码,并且会有频繁的加锁释放锁动作,都会造成效率下降
4.5 lock锁
- 实例化锁对象
Lock lock = new ReenTrantLock()
- 加锁方式
lock对象.lock()
- 解锁方式
lock对象.unlock()
- 优势:更灵活,程序员可以灵活的控制加锁和释放锁的动作
4.6 死锁
由于锁的嵌套,导致A线程等待B线程持有的锁,而B线程也在等待A线程持有的锁。
5. 等待唤醒机制
一个线程休眠,等待另一个线程唤醒后才会继续执行
void wait()//等待
void notify()//唤醒程序(随机唤醒一个)
oid notifyAll()//唤醒所有程序
调用者是锁对象
6. 阻塞队列
创建一个队列,线程只有满足条件才会执行
- 程序去完成一个功能时,由于某种原因,现在无法完成,程序会停住(后面的代码就不能再执行了),直到该功能完成为止!!!
6.1 创建方法
BlockingQueue:
ArrayBlockingQueue(int capacity)
LinkedBlockingQueue();
6.2 添加元素
put(元素)
6.3 获取元素
take()
6.4 阻塞队列实现等待唤醒机制
- 不需要加锁,阻塞队列底层就是用:锁+wait+notify
7. 线程状态
NEW:新建,Thread对象刚new,就处于这个状态
RUNNABLE:可执行状态,调用start方法开启线程,就会处于这个状态
BLOCKED:阻塞状态,抢锁没抢到
WAITING:等待状态,它不会抢锁!
TIMED-WAITING:限时等待
TERMANITED:终止或者死亡状态
一个线程从创建到销毁的过程
8. 线程池
8.1 基本原理
- 线程池:装线程(Thread)的池子(数组/集合)
- 解决的问题:
- 如果没有线程池:
每次都需要创建新的线程,线程执行完线程任务后,销毁线程。会带来资源和时间的浪费 - 如果有线程池:
一次性的创建多个线程放入到池子中,如果要使用线程,从池子中拿,使用完毕后,归还池子。从 而提高效率
- 如果没有线程池:
8.2 Executors默认线程池
Executors:
static ExecutorService newCachedThreadPool();
ExecutorService:
submit(Runable r);//添加线程(Runnable接口)
submit(Callable c);//添加线程(Callable接口)
shutdown();//销毁线程
没有空闲线程会创建新的线程
8.3 Executors创建指定上限的线程池
ExecutorService newFixedThreadPool(int nTheads)
8.4 ThreadPoolExecutor创建线程池(常用)
public ThreadPoolExecutor(
int corePoolSize, //核心线程数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //最大空闲时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列,存储任务
ThreadFactory threadFactory, //线程工厂,用来创建线程
RejectedExecutionHandler handler //拒绝策略
)
参数有:
核心线程数
最大线程数
最大空闲时间
时间单位
阻塞队列
线程工厂
拒绝策略
8.5 详细参数
- 向线程池提交任务,如果 最大线程+阻塞队列容量 < 短时间内提交的任务数量 则会被拒绝
- 默认的拒绝策略:丢弃任务,抛出异常
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,//核心线程数量
5,//最大线程数量
2,//最大空闲时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(10),//阻塞队列,存储任务
Executors.defaultThreadFactory(),//线程工厂,用来创建线程
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
while(true){
//任务被拒绝,则重复提交任务
try {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("Helo");
}
});
break;
} catch (Exception e) {
//任务被拒绝了
//重新提交任务
continue;
}
}
认为被拒绝会报错,重复提交任务,直到任务执行完毕
- 处理规则:
1. 先创建核心线程对象执行任务;
2. 如果不够用,则往阻塞队列中存放;
3. 如果阻塞队列存满了,再创建临时线程;
4. 如果临时线程也不够用了,则拒绝;
9. volatile
9.1 多线程存在的线程安全问题
- 高速缓存(cache)带来内存不可见问题,线程修改内存的动作没有被其他线程发现。
9.2 volatile解决方法
- 如果一个变量被volatile关键字修饰,jvm使用时发现这是一个值易发生变化的量,因此这样的变量,jvm每次都会去内存中读取.
9.3 synchronized解决
- synchronized会每次清空高速缓存中的内容,这样也可以达到每次都从内存中读取数据的效果
10. 原子性
10.1 概述
一个整体,不可分割!不可以被别的线程打断
10.2 count++是否具有原子性?
不具有;
一个count++,count++被编程成.class文件后,对应着有三个class指令,它代表了三个动作;其他线程就可以打断这三个操作;
10.3 count++的原子性保证
- volatile:不能保证
- synchronized:可以保证
10.4 AtomicInteger
AtomicInteger();
int get();//获取值
int getAndIncrement();//先获取再自增
int incrementAndGet();//先自增再获取
int addAndGet(int delta);//先添加一个值再获取
int getAndSet(int delta);//先获取再添加一个值
10.5 AtomicInteger-内存解析
- CAS算法的原理
- CAS: compare and swap----- 比较和交换
while(true){
1.读取内存的值Value,赋值给oValue: oValue = mValue;
2.对oValue加1,赋值给nValue: nValue = oValue+1;
3.比较 oValue和mValue是否相等
if(oValue == mValue){
把nValue赋值给mValue;
break;跳出循环
}else{
有其他线程把内存值mValue已经篡改了,继续重新循环再做一次
continue;
}
}
旧值-oValue 新值-nValue 内存值-mValue
10.6 悲观锁和乐观锁
- 悲观锁:synchronized
- 一上来认为别人就会修改,加锁
- 乐观锁: CAS
- 一上来认为别人不会改,万一别人修改,自旋
11. 并发工具类
11.1 HashTable
每个操作表的方法上都加了synchronized。
11.2 ConcurrentHashMap(jdk1.7)
ConcurrentHashMap值扩容小数组,并且每次只锁表的一小部分,哈希表永远只有16个位置
11.3 ConcurrentHashMap(jdk1.8)
ConcurrentHashMap存储数据时只锁住一部分;
如果要把一个元素存储到哈希表数组指定索引处:
- 该索引处是null:
cas算法往进存 - 该索引处不是null:
使用悲观锁,锁住整个链表
- 每次只锁一小部分,而且哈希表可以扩容,单个位置采用链表的形式,元素大于等于八个时,自动转成红黑树.
11.4 CountDownLatch
使用场景:
一个线程任务可能要依赖其他多个线程任务的结果才能继续时。
public CountDownLatch(int count):
public void await():
public void countDown():
11.5 Semaphore
使用场景:
在多线程争抢相同资源时进行限流
public Semaphore(int permits)
public void acquire()
public void release()