多线程:
线程的创建:
1.继承thred类,
2.实现runnable接口,
线程的启动:
使用start()方法,会产生两个线程
使用run()方法,产生一个线程
线程的5种状态(生命周期):
1.新建(创建了线程对象),
2.就绪(调用了start方法或从阻塞状态唤醒),
3.运行(分配了cpu时间片),
4.阻塞(做io操作或sleep),
5.死亡(线程执行结束后)
线程安全:
多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时进行保护,其他线程不能访问直到该线程读取完其他线程才可使用。
线程的锁机制:
锁大致可以分为互斥锁,共享锁和读写锁
互斥锁:只有一个线程可以访问被保护的资源,在访问共享对象之前,对其进行加锁操作,在访问完成后进行解锁操作。加锁后,其他试图加锁的线 程会被阻塞,直到当前线程解锁。解锁后,原本等待状态的线程变为就绪状态,重新竞争锁
1)synchronized和reentrantlock都是典型的互斥锁,synchronized是jvm关键字,不用处理异常状态下的锁释放,当资源使用完毕后,连接断开时 会自动释放;而reentrantlock是java类,需要显式调用释放锁
reentrantlock实现了lock接口,底层基于AQS(AbstractQueuedSynchronizer)实现,AQS为加锁和解锁过程提供了统一的模版函数。
在Reentrantlock中,它将AQS的state状态值定义为线程获取该锁的重入次数,state为0表示当前没有被任何线程持有,state为1表示被其他线程持 有,因为支持可重入,如果是持有锁的线程,再次获取同一把锁,直接成功,并且state的值+1,线程释放锁state的值-1,同理重入多次锁的线程需要释 放相应的次数。
可重入锁:一个线程获得一个锁,在重复的获取这个锁的时候直接成功,注意是重复获取相同的锁。reentrantlock和synchronized都是可重入锁。
共享锁:允许多个线程共同访问资源
读写锁:renntrantreadwritelock,既是互斥锁又是共享锁,在读模式下是共享锁,在写模式下是互斥锁
扩展:
公平锁:根据先进先出的规则,从等待队列中取出第一个等待线程获取锁
非公平锁:与公平锁相反,新来的线程在一上来就会尝试占有锁,如果这时候刚好在发出请求时锁变成可用状态,则这个线程会跳过队列中的等待线 程直接获取锁,否则,将自己加入到队列中
可中断锁:即等待锁的过程中时可以中断的。在互斥锁中,synchronized是不可中断的,而reentrantlock是可以中断的(提供了trylock和 lockinterruptibly两种方法来中断等待操作)
悲观锁:每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁。(数据库的行锁,表锁,读写锁)
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁。但在更新的时候会判断一下在此期间有没有人更新了这个数据(实现方式有版本 号等方式)
死锁:指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。当一个线程永久的持有一把锁后,其他线程将一直等待下 去
注意⚠️:
形成死锁的四个条件:
1)互斥性:即线程占用的锁是互斥锁,不能被其他未占有的线程访问
2)不可剥夺:即线程已经获得锁,在未主动放弃之前不会被其他线程剥夺
3)请求和保持:即有锁s1和s2,线程一持有s1,有发起了对s2的持有请求。而同时线程二持有了s2,又对s1发起了持有的请求。
4)环路等待:即死锁发生时必然有一个环形链,如(p1,p2,p3),p1等待p2释放资源,p2等待p3释放资源,p3等待p1释放资源。
锁实现:CAS算法
线程池的使用:(threadpoolexector)
参数说明:
public ThreadPoolExecutor(
int corePoolSize, 核心线程数
int maximumPoolSize, 最大线程数
long keepAliveTime, 非核心线程的存活时间
TimeUnit unit, 时间单元
BlockingQueue<Runnable> workQueue, 任务队列
ThreadFactory threadFactory, 线程工厂
RejectedExecutionHandler handler 拒绝策略(默认的是终止策略)
)
注意⚠️:提交优先级:首先提交到核心线程,其次是队列,再是非核心线程
执行优先级:首先执行核心线程中的任务,其次是非核心线程中的,再是队列中的
队列:
同步队列(SynchronourQueue):直接提交,不会保存任何任务
无界队列(LinkedBlockingQueue):对于新加入的任务全部存入队列当中,任务太多的话会导致内存溢出(OOM)
有界队列(ArrayBlockingQueue):队列有一个最大值,超过最大值的任务交给拒绝策略来
拒绝策略:
AbortPolicy(终止策略):如果线程池线程被用完了,队列也放不下了,直接抛出异常 rejectedExecution从而终止任务;
DiscardPolicy(丢弃策略):如果线程池的线程被用完了,队列也放不下了,不抛出异常,直接丢弃多余的任务;
DiscardOldestPolicy(丢弃最老策略):如果线程池的线程被用完了,队列放不下了,就把等待时间最久的任务丢弃
CallerRunsPolicy(呼叫者运行策略):如果线程池的线程全部被用完,队列放不下的时候,会把多余的任务返回给调用者(主线程)去执行
堵塞线程池和非堵塞线程池:(使用有界队列和呼叫者运行策略,)
堵塞线程池:在拒绝策略中将新进入的任务再次放入队列中,用put方法,如果满了就会一直等待直到队列中的其他任务被释放
优点:不会出现消息丢失和内存溢出的问题
缺点:把线程池的压力交给了主线程来处理,如果消息量过高,主线程得不到响应,会报出超时异常
非堵塞线程池:在拒绝策略中将新进入的任务进行存储,后续进行处理
优点:不会造成内存溢出,也不会堵塞
缺点:设计复杂
线程池的使用场景:
1.异步处理日志;
2.定时任务(定时发送邮件,定时对数据库备份)
3.数据迁移
线程池的优点:
1.减少频繁的创建和销毁线程
2.充分利用cpu,提高系统的运行效率
线程池线程数的设计:
线程数=处理器的核的数目*cpu利用率*(1+w/c)
w:等待时间
c:计算时间
executors下的五种创建线程池的方法:
1)、newCachedThreadPool:返回一个可以根据实际情况调整非核心线程数量的线程池,会出现cpu100%,使用的是同步队列;
2)、newFixedThreadPool:该方法返回一个固定核心线程数量的线程池,会出现内存溢出,使用的是一个基于链表的阻塞队列;
3)、newSingleThreadExecutor:该方法返回一个只有一个线程的现成的线程池,会出现内存溢出,使用的是一个基于链表的阻塞队列;
4)、newSingleThreadScheduledExecutor:该方法和newSingleThreadExecutor的区别是给定了时间执行某任务,可以进行定时执行等;
5)、newScheduledThreadPool:在4的基础上可以指定线程数量。