一、线程和进程的概念
1.进程:正在运行的一个应用程序:多个进程共享cpu
2.线程:组成进程的最小的单元,单一运行的顺序流:多个线程共享进程分配的cpu
二、创建的线程的两种
1.extends Thread类: 当前就是线程类
2.implements Runnable接口:当前类是线程类的target
注意:两种方式的区别
三、线程的生命周期:五个阶段:
新建阶段、就绪阶段、运行阶段、阻塞阶段、死亡阶段
每个阶段的特点:互相转换的条件
四、线程中常见的方法:
1.start():启动线程的方法
2.stop():摒弃的方法,可能导致数据不安全
3.suspend():摒弃的方法,可能导致死锁
4.resume():摒弃的方法.
5.join():阻塞主线程,插队的子线程执行完毕后,主线程才能执行
6.yield():让位:只给优先级高的线程让位,将运行的线程进入就绪状态
7.sleep(millsecond):可以用在任何地方
8.wait():等待
sleep()和wait()区别:
- 拥有对象不同:wait是任何对象的方法,sleep是Thread的方法
- wait可以释放对象锁,sleep保留对象锁
- wait可以是任意对象来调用,sleep只能线程调用
- wait可以通过notify随时唤醒,sleep只能等待设定时间结束后自然唤醒,否则将引发异常
- wait必须在同步方法或同步块中进行调用,sleep可以在任意位置调用
五、线程同步问题:
1.什么是线程安全问题?多条线程同时访问一个资源,所产生的数据不一致的问题
2.如何保证线程安全?
第一种方式:
使用同步锁:synchronized:
synchronized :修饰代码段 :
语法:
synchronized(对象-同步监视器){
被锁的代码段
}
同步监视器只能是引用类型的。多条线程锁的是同一个对象
修饰方法:语法 public synchronized void method(){}
同步的方法,对该方法进行锁定,并且调用当前方法的对象
第二种方式:
使用Lock锁:ReentrantLock类
区别:
1.synchronized 关键字,jdk提供的。ReentrantLock类
2.synchronized 可以修饰代码段,也可以修饰方法。但是ReentrantLock只能用于代码段
3.如果资源竞争不太激烈时,效率没太大差别,如果资源竞争非常激烈时,ReentrantLock比synchronized 高很多。
4.synchronized 运行完代码段自动解锁,但是lock锁必须手动调用unlock()方法,有可能造成死锁:才要把unlock()写在finally中
5.synchronized阻塞其他线程的,但是lock可以使用tryLock()尝试获取锁,不一定非要阻塞。
第三种方式:ThreadLocal:以空间换取时间
六、wait和notify进行协作或者通信
1.wait只能有notify唤醒,并且调用wait和notify的对象必须是同一个。
七、线程池
线程的使用:手动创建线程 JVM销毁线程
以前方式缺点:1.不能实现线程的复用,造成资源的浪费
2.创建,销毁线程都是需要耗费时间的,大量创建线程,会浪费很多的时间
3.线程是一种对象,存在于堆中的,占内存的。有可能耗尽内存
使用线程池的优点:
1.可以复用线程对象,节约资源
2.不需要大量手动创建和销毁线程对象,提高效率
3.节约内存的使用
java.util.concurrent
接口 Executor接口
ExecutorService接口 submit(),shutdown()
AbstractExecutorService抽象类
ThreadPoolExecutor类
corePoolSize :核心线程数量
maximunPoolSize:最大线程数量
aliveTime :空闲线程存活时间
unit :时间单位
workQueue :任务队列
rejust :拒绝策略
factory :线程池工厂
辅助的类:Executors 创建出来各种线程池,隐藏了细节
静态的方法:Executors.newFixedThreadPool(3);创建固定池大小的线程池
Executors.newCachedThreadPool();根据实际业务创建线程,最小0,最大Integer.MAX_VALUE
Executors.newSingleThreadExecutor();创建单一线程的线程池
Executors.newScheduledThreadPool(corePoolSize);
使用线程池的流程
1.创建线程池对象
2.向线程池中提交任务:Runnable/Callable
3.编写任务过程
4.在最后合适的时候关闭线程池
Callable接口,任务类实现的接口,重写call方法。
class SumTask implements Callable<V>{
@Override
public V call() throws Exception {
//具体任务执行过程,如果多条线程都操作同一个数据,也需要加锁
}
向线程池中提交的任务类型可以是Runnable,也可以是Callable接口类型的。
两者的区别:
1.Runnable没有返回值;Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
2.Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。