一、线程理论
1、线程和进程
(1)进程----一个应用程序(软件--QQ)----进程间内存不共享
线程----进程中的一个执行场景(和不同人聊天)----线程间栈内存和程序计数器不共享,堆内存和方法区共享
(2) JVM是一个进程
方法区和堆内存共享-----一个方法区,一个堆
栈内存不共享,一个线程一个栈------多个栈
2、单核多核cpu与线程并发之间关系
单核cpu--在一个时间点上实际上只能处理一件事
如线程a。b并发执行,实际上是两个线程循环切换执行,速度很快,让我们产生同时进行的错觉
多核cpu---在一个时间点上可以处理多件事,真正的并发执行
如4核,可以4个进程并发执行
二、创建线程的三种方式
(一)无返回值---Thred类和Runnable接口
1、继承Thread类--单继承
class MyThread extends Thread{
public void run(){
//该线程要完成的功能
}
}
2、实现Runnable接口
class MyThread implement Runnable{
public void run(){
//该线程要完成的功能
}
}
推荐使用: 避免单继承局限性,方便同一个对象被多个线程使用
3、线程的启动---start()
start()启动线程,开辟新的栈空间,瞬间结束,线程run方法开始执行。
启动的线程与目前主线程后续代码并发执行
4、run()与start()区别
- 直接线程对象调用run方法,不会启动新线程,不会新分配线程栈空间,仍然是单线程自上而下执行,run方法执行结束后,才继续执行主线程剩下代码
- 线程对象先start线程,会启动一个新线程,开辟新的栈空间,然后系统自动调用run方法,run方法和主线程剩下代码并发执行
(二)有返回值---实现callable接口
1、作用
可以拿到线程的返回值,即线程的执行结果
2、实现callable接口中没有run方法,其中call()方法相当于run方法,
call方法有返回值,而run方法没有返回值
3、优缺点:
优点:可以获取到线程的执行结果
缺点:效率较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
4、具体实现过程
方式一
//线程的定义
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception{
Thread.sleep(2000);
return " world!";
}
}
//线程的调用
public static void main(String[] args) throws Exception{
ExecutorService executorService=Executors.newSingleThreadExecutor();
MyCallable myCallable=new MyCallable(); //new线程对象
Future<String> result =executorService.submit(myCallable);//启动线程,返回值result接收
System.out.println("hello "+result.get());//通过get方法获取返回结果
}
方式二
public static void main(String[] args){
//1、创建一个“未来任务类”的对象 FutureTask
// 给 “未来任务类”的对象 传递callable接口实现类的对象【传参】
// JUC包下的,属于java的并发包(JDK8的新特性)
FutureTask task=new FutureTask(new Callable()){
public Object call() throws Exception{
//功能代码
}
});
//2、创建线程并【传入task对象】
Thread t=new Thread (task);
t.start();//启动t线程
//3、这里是main方法,通过FutureTask对象获取t线程的执行结果
Object obj=task.get();
System.out.println("线程执行结果"+obj);
//4、这里程序的执行必须等待t线程执行结束返回结果给get()方法,此处主线程和t线程非并发执行
System.out.println("线程执行结果"+obj);
}
三、线程的生命周期
1、新建状态:new线程对象
2、 就绪状态:start调用之后(可运行状态),表示当前线程具有抢夺cpu时间片的权利
3、 运行状态:run方法执行,当占有的cpu时间片用完之后,会重新回到就绪状态继续抢夺
4、 阻塞状态:遇到阻塞事件后,如sleep中止正在执行的线程,线程会放弃占有的cpu时间片
5、 死亡状态:程序结束
【注】
- 同线程在就绪状态和运行状态频繁切换-----实现线程并发
- 单核线程并发实质还是单线程,只是在其他线程还没有运行完之前允许其他进程进行时间片的抢夺
- 若没有多线程并发,则另一个线程只能等当前线程执行完,然后再开始执行
四、线程常用方法
1、获取当前线程对象----Thread.currentThread()
main方法中---当前线程就是main方法
线程类中---谁在执行run方法,当前对象就是谁
2、获取和设置线程名称---线程对象.setName()
3、线程休眠----Thread.sleep(毫秒)
作用:让当前线程进入阻塞状态,放弃占有的cpu时间片
(1)线程的调用:类名.sleep()
(2)静态方法与对象无关,只能用类名进行调用,且在哪调用,哪个线程休眠
如,在main方法中调用终止主线程,在自定义线程中调用终止自定义线程
4、唤醒睡眠---线程对象.interrupt()
(1) interrupt()中断方法
- interrupt()不能中断在运行中的线程,它只能改变中断状态,线程仍然继续运行
- interrupt()的作用主要用来唤醒阻塞中的线程
- 中断线程主要用 标记+return方法
(2)相关方法
- interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。
- isInterrupted(),用来判断当前线程的中断状态(true or false)。
- interrupted()是个Thread的static方法,用来恢复中断状态
参考:Java中interrupt的使用 - 无名码者 - 博客园
5、终止线程---stop()
一个线程本来运行结束需要10s,需要在5秒后终止该线程,即只让线程执行5秒
(1)强行终止线程的执行---stop()
缺点:容易丢失数据,
这种方式是直接杀死线程,线程没有保存的数据容易丢失
(2)合理终止线程的执行---打标记 flag=true,当flag=flase--->return;
在return前有想要保存的数据可以加save语句进行保存。
6、守护线程----线程对象.setDaemon(ture)
作用:调用守护线程的用户线程结束,守护线程随之结束,即使守护线程是死循环需一直运行
即,守护线程的生死不由其本身的运行状态决定,由其用户线程决定
如,t是守护线程,main方法中调用并启动该线程,此时main方法是其用户线程
当main方法执行结束后,t线程也随之结束,不管run方法是否执行完毕
7、设置线程调度优先级---Thread.currentThread().setPriiority(1)
优先级设为1
8、线程让位----Thread.yied()
在哪个线程中调用该函数,哪个线程等待,不执行(静态类,类名.调用)
9、线程合并----t.jion()
谁调用jion函数,谁先执行,其他线程等待(线程对象.调用)
10、线程等待----对象.wait()
作用: 让当前线程等待并释放占有的共享对象锁
11、唤醒线程-----对象.notify()
作用:-唤醒线程,但不释放共享对象之前占有的锁
notifyAll()---唤醒该对象上等待的所有线程
【注】sleep()和wait()区别
1、sleep来自Thread类----线程对象的方法【Thread.sleep()】----让线程休眠进入阻塞状态,
放弃占有的cpu时间片,但不释放锁
wait来自object类----Java对象的方法【对象.wait()】-----让本线程等待唤醒再执行,
释放占有的锁
2、sleep任何地方都可以,在哪个地方调用,对应哪个线程类休眠
wait只能在同步代码块中使用,共享变量调用并等待
3、sleep需要捕获
wait不需要
12、计时器 ---Timer
(1)创建定时器对象
Timer timer =new Timer();
(2)指定定时任务
timer.schedule(要执行的任务类对象,开始执行任务时间,间隔多久执行一次)
(3)编写需要执行的任务类---extend TimerTask
//main方法
//1、创建定时器对象
Timer timer =new Timer();
//2、指定定时任务
SimpleDateFormat sdf=new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
Date firstTime=sdf.parse("2021-09-19 19:40:00");
timer.schedule(new LogTimerTask(),firstTime,1000*60*60*24*365);
//编写定时任务类---编写具体功能
class LogTimerTask extends TimerTask{
public void run(){
要定时执行的任务
}
}
四、线程通信---生产者消费者模式
1、线程通信方式
(1)缓冲区
(2)信号灯法
2、实现线程通信方法
3、消费者生产者
(1)利用缓冲区解决:管程法
缓冲区空,消费者wait(),生产者生产,notify()唤醒消费者
缓冲区满,生产者wait(),消费者消费,notify()唤醒生产者
(2)信号灯法--flag
根据flag真假来决定某一个线程进行,真代表一个,假代表一个
五、线程调度
1、 协同式:线程的执行时间由线程本身来控制,线程任务执行完成之后主动通知系统切换到另一个线程去执行。(不推荐)
优点:实现简单,线程切换操作对线程本身是可知的,不存在线程同步问题。
缺点:线程执行时间不可控制,如果线程长时间执行不让出CPU执行时间可能导致系统崩溃。
2、 抢占式:每个线程的执行时间有操作系统来分配,操作系统给每个线程分配执行的时间片,抢到时间片的线程执行,
时间片用完之后重新抢占执行时间,线程的切换不由线程本身来决定(Java使用的线程调度方式就是抢占式调度)。
优点:线程执行时间可控制,不会因为一个线程阻塞问题导致系统崩溃。