目录
一、线程、进程
什么是进程?
进程是操作系统对正在运行程序的一种抽象,是操作系统进行资源分配的基本单位,我们可以认为每个运行于操作系统上的应用程序都是一个进程。
什么是线程?
线程是一个“执行流”,每个线程都可以按照循序执行自己的代码,而在一个进程中可以有多个线程,也就是说可以支持同时执行多份代码。
多线程的API是由操作系统提供,Java来进行抽象和封装后再由程序员来使用的,具体为Java标准库中的Thread类(在Java默认引用的java.lang包中)。
线程和进程的区别
- 进程与线程是包含与被包含的关系,每个进程至少有一个线程存在,即主线程
- 进程和进程之间相互独立,不共享内存空间,同一个进程的线程之间共享一份内存空间,形象来说就是对于一个建筑工程,每个人负责不同的项目,但都是在操作同一份空间。
- 进程是系统分配资源的最小单位,线程是系统图调度的最小单位
- 一个进程挂了一般不会影响其他进程(相互独立),但是一个线程挂了,就可能把进程内的其他线程都一波带走
二、创建线程类
继承Thread类可以创建一个线程类,我们可以通过在主线程中实例化它,随后创建一个不同于主线程的另一个线程来同时执行多份代码,两份代码同时执行,每条代码执行的先后循序取决于系统调度器。
线程执行的代码通过重写run()方法来实现,具体创建方法如下:
方法1 继承Thread类
class MyTread extends Thread {
@Override //重写run方法
public void run() {
//以下为线程执行的具体代码
while(true) {
System.out.println("hello thread");
try {
//线程休眠
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//实列化线程类
Thread t = new MyThread();
//启动线程
t.start();
}
}
方法2 实现Runnable接口
Thread的构造方法除了Thread( ),还有Thread(Runnable target)
我们可以在实现了Runnable接口的类中实现run方法,将该类的实列作为参数,同样能达到创建线程的目的。
class MyRunnable implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("hello runnable");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//实列化线程类
Thread t = new Thread(new MyRunnable());
//启动线程
t.start();
}
}
方法3 匿名内部类方式
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("重写run方法");
}
};
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("重写run方法");
}
});
//启动线程
t.start();
}
方法4 Lambda表达式方式(常用)
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("重写Runnable中的run方法"));
//启动线程
t.start();
}
}
三、调用线程:Thread类的方法
构造方法
- Thread( ) 创建线程对象
- Thread(Runnable target ) 使用Runnable对象创建线程对象
- Thread(String name) 创建线程对象,并对线程进行命名
- Thread(Runnable target, String name) 使用Runnable对象创建线程对象,并命名
- Thread(ThreadGroup group, Runnable target) 线程可以被用来进行分组管理
未对线程进行命名时,线程默认的命名格式为“Thread-数字”(使用Java自带的jconsole进行观察)
启动线程
使用构造方法创建出线程类并实列化,并不是真的创建出了一个线程,只有在调用了start()方法后才会真正在操作系统底层创建出一个线程出来,
Thread.start( );
使用线程实例调用start方法,创建相应的线程,并执行run中的代码
中断线程
调用start方法后,线程不将代码执行完毕是不会结束的,如果想要将线程中断,那么就需要调用interrupt()方法对线程进行通知,告诉它你想要结束线程,至于线程是否结束,该怎样结束还要依据具体代码来分情况讨论。
- public void interrupt( );
- Thread内部包含了⼀个boolean类型的变量作为线程是否被中断的标记,调用interrupt中断线程
- public static boolean interrupted( )
- 判断当前线程标志位是否被设置,被设置(调用了interrupt方法)则返回true,未设置则返回false
- public boolean isInterrupted( )
- 在使用Runnable接口实现线程类时,通过 " Thread.currentThread( ).isInterrupted( ) "达到interrupted方法的效果,Thread.currentThread( )方法可以获取当前线程的引用
调用interrupt可能出现以下两种情况:
情况1:线程因为调用sleep/wait/join等方法而阻塞挂起时调用
该情况下将抛出interruptedException异常,并清除标志位(不直接结束),是否继续执行取决于catch中代码的写法
public static void main(String[] args) {
//实列化线程对象,lambda方式
Thread t1 = new Thread(() -> {
//每秒输出
while(true) {
System.out.println("t1 dashb");
try {
//线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
//可以break退出该循环,跳出本次循环
//也可以不进行处理,继续执行
}
}
});
//启动线程
t1.start();
//两秒后中断线程
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//中断线程
t1.interrupt();
System.out.println("中断线程");
while(true) {
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
当不在cath中对异常进行任何处理,可以观察到线程仍旧在执行代码,表明标志位被清除。
当然,我们也可以break直接退出本次循环,之后可以顺利结束线程,又或者执行一些别的代码都可以
这种异常通知的方式,给予了我们程序员更大的操作空间。
情况2:正常情况调用
调用interrupt方法后标志位由false被设置为true,该boolean类型值可通过isInterrupted或interrupted获取,可通过该方法的返回值判断是否要中断线程
public static void main(String[] args) {
//实列化线程对象,lambda方式
Thread t1 = new Thread(() -> {
//每秒输出
while(!Thread.currentThread().isInterrupted()) {
System.out.println("t1 dashb");
}
});
//启动线程
t1.start();
//1毫秒后中断线程
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//中断线程
t1.interrupt();
System.out.println("中断线程");
while(true) {
System.out.println("main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
等待线程
有些时候我们需要让某个线程完成其工作后才能继续向下工作,例如当过年吃年夜饭时,只有当所有亲人都到位了才能开饭,在所有人到齐之前,开饭操作只能等待人齐操作完成后才能执行。
- public void join( )
- 等待调用线程结束
- public void join(long millis)
- 等待调用线程结束,最多等待millis秒
- public void join(long millis, int nanos)
- 更高精度等待
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
int s = 0;
for(int i = 0; i < 3; i++) {
System.out.println("t1执行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
System.out.println("t1线程启动");
//调用join方法
t1.join();
System.out.println("t1线程结束");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
int s = 0;
for(int i = 0; i < 3; i++) {
System.out.println("t1执行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
System.out.println("t1线程启动");
//调用join方法
t1.join(2);
System.out.println("等待结束");
}
休眠线程
Thread类中的方法。因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的
- public static void sleep(long millis)
- 休眠指定毫秒
- public static void sleep(long millis, int nanos)
- 更高精度
获取当前线程对象的引用
当使用Runnable接口实现线程类时,一些Thread类的方法无法直接调用,这时候就可以通过Thread类的currentThread()方法来获取当前线程对象的引用,从而可以调用Thread类中的方法。
- public static Thread currentThread( )
- 获取当前线程的引用
在中断线程部分我们用它来获取了当前线程对象,并通过返回的引用调用了isInterrupted方法
在需要大量该操作时也可以这样:
Thread thread = Thread.currentThread();
获取线程相关属性方法
- ID getId()
- ID是线程的唯⼀标识,不同线程不会重复
- 名称 getName( )
- 名称是各种调试⼯具⽤到
- 状态 getState( )
- 状态表⽰线程当前所处的⼀个情况
- 优先级 getPriority( )
- 优先级⾼的线程理论上来说更容易被调度到
- 是否是后台线程 isDaemon( )
- 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
- 是否存活 getAlive( )
- 是否存活,即简单的理解,为run⽅法是否运⾏结束了
- 是否被中断 isInterrupted( )
- 判断线程是否被中断
博主是Java新人,每位同志的支持都会给博主莫大的动力,如果有任何疑问,或者发现了任何错误,都欢迎大家在评论区交流“ψ(`∇´)ψ