第一章 线程处理 v1.0
第一部分 线程的基础概念
一、线程状态
1.并发与并行
正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,Java中多个线程其实是并发与并行同时进行的
并发:CPU分时轮询的执行线程(CPU线程有限,由于CPU切换的速度很快,感觉上这些线程在同时执行)
并行:同一个时刻多个线程同时在执行
2.线程的执行状态
- New:线程刚被创建,但并未启动
- Runnable:线程对象已经调用了start()方法等待CPU调度
- Blocked:线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态
- Waiting:一个线程进入无限等待状态,另一个线程调用notify或者notifyAll方法才能够唤醒
- Timed Waiting:计时等待状态,另一个线程调用notify或者notifyAll方法才能够唤醒,超时则自动退出
- Teminated:因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
二、线程的创建
1.继承Thread类创建线程
继承类必须重写run()方法,该方法是新线程的入口点。它也必须调用start()方法才能执行。
class 线程类名 extends Thread {
@Override
public void run() { //直接调用run还是相当于单线程执行run方法
//程序主体
}
} //重写run()方法
class MyJavaClass {
public static void main(String[] args) {
线程类名 obj = new 线程类名();
obj.start(); //启动线程
}
}优缺点:
优点:简单,容易实现
缺点:直接继承Thread类,导致无法继承其他类,且线程若有执行结果是不能直接返回
2.实现Runnable接口创建线程
class 类名 implements Runnable {
@Override
public void run() {
//程序主体
}
}
class MyJavaClass {
public static void main(String[] args) {
类名 t = new Thread(new 类名());
t.start();
}
}new Thread(() -> {
//方法体
}).start(); //创建线程并启动线程优缺点:
优点:线程任务类只是实现了Runnale接口,可以继续继承和实现。
缺点:如果线程有执行结果是不能直接返回的。
3.实现Callable接口并结合FutureTask创建线程(JDK5引入)
a.实现步骤
- 定义任务对象:定义类实现Callable接口,重写call方法,封装要做的事情
- 封装任务对象:使用FutureTask(Future的一个实现类)把实现了Callable接口的对象封装为任务对象
- 创建线程对象:把任务对象交给Thread创建线程对象
- 启动线程对象:调用线程对象的start方法启动线程,执行任务
- 获取任务结果:调用任务对象get方法得到call方法返回结果
b.Callable接口实现
class 类名 implements Callable<泛型> {
@Override
public 返回值 call() {
//代码块
return 返回值; //在使用FutureTask对象的get()方法时,若没有得到返回值,程序会进入等待
}
}class ThreadTest implements Callable<String> { //1.定义任务对象
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "返回值";
}
}
class Test{
public static void main(String[] args) throws Exception{
FutureTask<String> future=new FutureTask<>(new ThreadTest()); //2.封装任务对象
Thread thread=new Thread(f); //3.创建线程对象
thread.start(); //4.启动线程对象
System.out.println(future.get()); //5.获取任务结果
}
}优缺点:
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强,并且可以在线程执行完毕之后获取结果
缺点:编码较为复杂
三、常用方法
1.构造方法
Thread(String name):创建一个线程对象并指定名称Thread(Runnable target):封装Runnable对象成为线程对象Thread(Runnable target, String name):封装Runnable对象成为线程对象并指定名称
2.线程对象的常用方法
*每个线程有优先级以确定线程调度的顺序,范围从1到10(默认为5)
第二部分 线程操作
一、线程同步
1.线程安全问题
多个线程同时访问并修改同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题
原因:多个线程同时操作同一个共享资源
解决方案:通过使多个线程实现先后依次访问共享资源,以此解决线程安全问题
2.线程同步的核心思想
加锁, 把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
3.同步代码块
作用与原理:把出现线程安全问题的核心代码加锁,每次只能一个线程进入,执行完毕后自动解锁
synchronized (同步锁对象) { //同步锁对象唯一时进行加锁,一般使用共享资源作为锁对象
//核心代码
}锁对象要求:
理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
实际开发中:实例对象一般用this作为锁对象,静态方法一般使用字节码(类名.class)作为锁对象
4.同步方法
作用与原理:把出现线程安全问题的核心方法加锁,每次只能一个线程进入,执行完毕后自动解锁
同步方法中实例对象默认用this作为锁对象,静态方法默认使用字节码(类名.class)作为锁对象
修饰符 synchronized 返回类型 方法名称(形参列表) {
//核心代码
}5.Lock锁(JDK5引入)
JDK5后提供锁接口Lock,为了更清晰的表达如何加锁和释放锁,可以使用其实现类ReentrantLock构建锁对象
class X {
private final ReentrantLock lock = new Reentrantlock();
public void m() {
lock.lock();
try{
//核心代码
} finally {
lock.unlock();
}
}
}二、线程通信
线程间相互发送数据,通常通过共享一个数据的方式实现,线程间根据共享数据的情况决定自己的行为状态
1.生产者与消费者模型
生产者线程负责生产数据,消费者线程负责消费数据。
要求:生产者线程生产数据后唤醒消费者,然后等待自己;消费者消费该数据后唤醒生产者,然后等待自己。
2.线程通信的前提
线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。
3.Object类的等待和唤醒方法
注:上述方法必须使用当前同步锁对象调用;使用中应当先唤醒(其他线程)再等待(当前线程)
三、线程池
由于创建新线程的开销较大,会影响系统的性能,通过线程池(ExecutorService接口)来作为复用线程的技术
1.线程池相关概念
- 临时线程创建条件
新任务提交时核心线程忙碌,任务队列已满,且还可以创建临时线程,此时才会创建临时线程。
- 拒绝任务条件
核心线程和临时线程都处于忙碌状态,任务队列已满,新任务提交时会开始拒绝任务。
2.创建线程池对象
a.使用ExecutorService接口的实现类ThreadPoolExecutor创建一个线程池对象
public ThreadPoolExecutor(int corePoolSize, //1.指定线程池的线程数量(核心线程)
int maximumPoolSize, //2.指定线程池可支持的最大线程数
long keepAliveTime, //3.指定临时线程的最大存活时间
TimeUnit unit, //4.执行存活时间单位
BlockingQueue<Runnable> workQueue, //5.指定任务队列
ThreadFactory threadFactory, //6.指定用哪个线程工厂创建线程
RejectedExecutionHandler handler) //7.新任务拒绝策略ExecutorService pools = new ThreadPoolExecutor(3 ,5 ,8 ,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());b.使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象(ExecutorService)
c.相关参数对象详解
[7.新任务拒绝策略对象(RejectedExecutionHandler handler)]
ThreadPoolExecutor.AbortPolicy():(默认)丢弃任务并抛出异常ThreadPoolExecutor.DiscardPolicy():丢弃任务但不抛出异常ThreadPoolExecutor.DiscardOldestPolicy():丢弃等待最久的任务并将当前任务加入队列ThreadPoolExecutor.CallerRunsRolicy():绕过线程池,由主线程调用run()方法执行
3.常用方法
四、定时器
一种控制任务延时调用,或者周期调用的技术,常见应用场景:闹钟、定时邮件发送。
1.Timer
构造方法:Timer():创建定时器对象
常用方法:schedule(TimerTask task, long delay, long period):延时后按周期执行TimerTask任务
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("执行操作");
}
}, 1000, 1000); //以毫秒为单位,延时1秒后,每隔1秒执行一次存在问题:
- Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
- 使用Timer定时器可能因为其中的某个任务发生异常,使Timer线程终止,从而影响后续任务执行。
2.ScheduledExcutorService
通过Executors(线程池的工具类)得到线程池对象:newScheduledThreadPool(int corePoolsize)
执行周期调度:
scheduleAtFixedRate(Runnable command, long initialDelay, 1ong period, TimeUnit unit)
优点:基于线程池,某个任务的执行并不会影响其他定时任务的执行结果