一、认识线程及线程的创建
1.进程与线程的区别:
进程是系统分配资源的最小单位,线程是系统调度的最小单位。
进程中包含线程,每个进程至少有一线程存在,即主线程,一个进程内的线程之间是可以共享资源的。
注:每个进程至少有一个线程存在,即主线程(系统级别的,C语言的主线程)
java级别的主线程(自己写的入口函数main方法(可以没有这个线程)
对java进程来说,至少有一个非守护线程还没终止,进程就不会结束
2.线程和进程的特性
进程的特性:
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有进过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程是一个正在系统中活动的指令集合,具有自己的生命周期和各种不同的状态。
并发性:多个进程可以再单个处理器CPU上并发执行,多个进程之间不会相互影响。
线程的特性:
原子性: 即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就不执行。
可见性: 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性: 程序执行的顺序按照代码的先后顺序执行。
3.线程的创建方式
(1)继承Thread类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("继承Thread类创建线程");
}
}
public static void main(String[] args) {
//1.继承Thread类创建线程
MyThread t=new MyThread();
t.start();
}
(2)实现Runnable接口
1.将MyRunnable对象作为任务传入Thread中
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("继承Runnable接口,创建描述任务对象,实现多线程");
}
}
public static void main(String[] args) {
//2.实现Runnable接口
Thread t1=new Thread(new MyRunnable());
t1.start();
}
2.使用匿名内部类实现
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用Runnable接口,创建匿名内部类实现");
}
});
t2.start();
(3)实现Callable接口
实现Callable重现call方法,允许抛出异常,允许带有返回值,返回数据类型为接口上的泛型
class MyCallable implements Callable<String> {
//允许抛出异常,允许带有返回值,返回数据类型为接口上的泛型
@Override
public String call() throws Exception {
System.out.println("实现了Callable接口");
return "这不是一个线程类,而是一个任务类";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方法三:实现Callable接口,是一个任务类
//FutureTask底层也实现了Runnable接口
FutureTask<String> task=new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println(task.get());
}
二、线程的常用方法
(1)构造方法和属性的获取方法
构造方法
属性的获取方法:
(2)run()和start()
start()方法:调用start()方法,程序进入就绪状态。
run()方法:重写run方法是提供给线程要做的事情的指令清单。
public class Thread_Run_VS_Start {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true){
}
}
}).run();
/**
* main线程直接调用Thread对象的run方法会直接在main线程
* 运行Thread对象的run()方法---->传入的runnable对象.run()
* 结果,main线程直接运行while(true)
*
* start()是启动一个线程,调用新线程的while(true)方法
* 对比通过start()调用的结果区别
*/
new Thread(new Runnable() {
@Override
public void run() {
while (true){
}
}
}).start();
}
}
(3)interrupt()方法
通过interrupt()方法,通知线程中的中断标志位,由false变为true,但是线程什么时候中断,需要线程自己的代码来实现,通过中断标志位实现,可以避免线程处于阻塞状态下,无法中断的情况。
方法 | 说明 |
---|---|
interrupt | 置线程的中断状态;如果调用该方法的线程处于阻塞状态(休眠等),会抛出InterruptedException异常,并且会重置Thread.interrupted 返回当前标志位,并重置 |
isInterrupt | 判断当前线程是否中断,返回Boolean |
interrupted | 返回线程的上次的中断情况,并清除中断状态 |
public class Interrupt {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
//...执行任务,执行时间可能比较长
//运行到这里,在t的构造方法中不能引用t使用Thread.currentThread()方法,获取当前代码行所在线程的引用
for (int i = 0; i <10000&&!Thread.currentThread().isInterrupted() ; i++) {
System.out.println(i);
//模拟中断线程
try {
Thread.sleep(1000);
//通过标志位自行实现,无法解决线程阻塞导致无法中断
//Thread,sleep(100000)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();//线程启动,中断标志位=false
System.out.println("t start");
//模拟,t执行了5秒,进程没有结束,要中断,停止t线程
Thread.sleep(5000);
//未设置时,isInterrupt为false
//如果t线程处于阻塞状态(休眠等),会抛出InterruptedException异常
//并且会重置isInterrupt中断标志位位false
t.interrupt();//告诉t线程,要中断(设置t线程的中断标志位为true),由t的代码自行决定是否要中断
//isInterrupt设置为true
//t.isInterrupted(); Interrupted是线程中的标志位
System.out.println("t stop");
//注:Thread.interrupted(); 返回当前线程的中断标志位,然后重置中断标志位
}
}
(4)join方法
join方法是实例方法
等待一个线程执行完毕,才执行下一线程(调用该方法的线程等待)
//join方法:实例方法:
// 1.无参:t.join:当前线程无条件等待,直到t线程运行完毕
// 2.有参:t.join(1000)等待1秒,或者t线程结束,哪个条件满足,当前线程继续往下执行
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1");
}
});
t.start();
t.join();//当前线程main线程无条件等待,直到t线程执行完毕,当前线程再往后执行
// t.join(1000);当前线程等到1秒,或者等t线程执行完毕
System.out.println("ok");
}
}
(5)休眠当前线程sleep()方法
让程序等待一定时间后,继续执行,sleep无视锁的存在,休眠时间到了线程处于就绪状态。
Thread.sleep(1000);
(6)线程等待wait()方法
线程调用wait()方法,线程处于等待状态,并且放弃对锁的拥有,调用Interrupt()中断线程,先要重新启动线程,调用notify()方法,随机唤醒一个等待线程,或者调用notifyAll()方法,唤醒所有的等待线程,唤醒之后需要重新获取锁才能进入就绪状态。
三、线程的生命周期
主要有五种状态:
(1)新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
(2)就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行(3)运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态(4)阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种: 等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
四、创建线程池的四种方式
newFeilcThreadPool:创建一个固定长度的线程池,当提交任务时会创建一个新线程,直到达到线程池的最大容量,此时线程池中的线程数量不在发生变化,如果线程发生未预期的错误而结束时,线程池会创建新的线程来补充;
newCachedThreadPool:创建一个缓存的线程池,如果线程池的规模超过处理需求,会自动回收闲置的线程,如果处理请求增加时,会自动创建新的线程,线程池的规模不存在任何限制;
newSingleThreadExecutor:创建单个线程来执行任务,如果线程出现异常而结束时,会创建一个新的线程来代替,其特点是保证了任务在队列中按照顺序来执行
newScheduledThreadPool:创建一个固定长度的线程池,并且以延迟或者定时的方式来执行任务
五、线程池的五种状态
running:线程池被创建就处于running状态,并且线程池中的任务数为0;这个状态下可以接收新的任务,以及对已添加的任务进行处理;
shutdown:这个状态下线程池不在接收新任务,但可以处理已有任务;
drop:该状态下线程不接收新任务,也不处理已添加的任务,并且会中断正在处理的任务;running调用shutdownnow()方法进入drop状态下;
tidying:当所有的任务被终止,stl中记录的任务数量为0时,线程池进入该状态;当由shutdown状态进入tidying状态需要满足,阻塞队列为空并且线程池中执行的任务也为空时才可以;由drop状态进入tidying状态需满足线程池中执行的任务为空时才可以;(进入tidying状态会调用钩子函数terminated())
terminated:线程池彻底终止;当tidying执行完terminated()方法时,进入终止状态;