多线程的常用操作方法
一、多线程的命名和获取
由于多线程的运行状态是不确定的, 那么在程序的开发之中为了可以获取到一些需要使用线程就只能靠线程的名字来操作了。所以线程的名字是一个特别关键的重要概念, 那么Thread类中就有操作线程名称的处理。
- 构造方法: public Thread(Runnable target, String name)
- 设置名字: public final synchronized void setName(String name)
- 取得名字: public final String getName()
当我们要获取到线程的时候, 不能只使用this来使用, 因为线程不可控,但是所有的线程对象一定要执行run() 方法,那么这个时候可以考虑获取当前线程,在Thread类里面提供有获取当前线程的方法。
- 获取当前线程的方法: public static native Thread currentThread();
// 当前这个类实现Runnable接口
class MyThread implements Runnable{
// 重写Run方法
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Day02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 设置线程A名字
new Thread(myThread, "线程A").start();
// 不设置名字
new Thread(myThread).start();
// 设置线程B名字
new Thread(myThread, "线程B").start();
}
}
// 运行结果
线程A
线程B
Thread-0
结论: 当我们指定了线程的名字的时候,当前线程的名称就会变成我们指定的名字, 如果我们不指定线程名字的时候, 线程池就会自动生成一个线程的名字。这种自动的属性命名主要是依靠了static关键字实现的, 在Thread类里面有如下操作, 见源码:
// 当我们new Thread类的时候, 就会自动新增一个变量,叫做nextThreadNum
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// Thread这个类定义了一个int类型的静态变量
private static int threadInitNumber;
// 方法nextThreadNum 每当调用一个线程 threadInitNumber 这个变量++
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
思考如下代码:
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Day02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 设置线程A名字
new Thread(myThread, "线程A").start();
// 这里注意, 我们直接直接调用run方法
myThread.run();
}
}
// 运行结果
main
线程A
当我们使用 “myThread.run()” 方法直接在我们主方法之中调用线程类对象中的run() 方法, 所获得的线程名称为“main” , 结论主方法也是一个线程。 那么现在问题来了,所有线程都是在进程上的划分, 那么进程在哪里呢? 其实, 每当我们使用java启动一个命令执行程序的时候就表示启动了一个JVM的进程,一台电脑上可以启动若干个JVM进程, 每个JVM进程都会有各自的线程。在我们开发的过程中,任何一个主线程可以创建若干个子线程, 创建子线程的原因就是将一些复杂的逻辑处理。
比如说我们需要后台上传一个视频, 比如说视频10个G,那为了保证我们在处理上传文件的时候,不影响我们其他操作, 那么我们就启动一个所谓的子线程去上传的操作,而不是我们等待10个G的视频上传完毕后再执行其他操作。
最后:主线程其实是负责处理整体的流程,而子线程负责处理耗时的操作。
二、多线程的休眠
如果我们希望某一个线程可以暂缓执行, 那么就可以使用休眠的处理,在Thread类中定义的休眠方法如下:
- 休眠: public static native void sleep(long millis) throws InterruptedException;
- 休眠2: public static void sleep(long millis, int nanos) throws InterruptedException;
在休眠的时候会出现中断异常“InterruptedException” ,中断异常属于Exception的子类,所以我们必须得处理(捕获 or 抛异常)。
public class Day02 {
public static void main(String[] args) {
// 创建线程,使用Lambda表达式
new Thread(() -> {
// 创建三条线程
for (int i = 0; i < 3; i++) {
// 打印线程的名称
System.out.println(Thread.currentThread().getName() + "、线程i = " + i);
try {
// 休眠一秒钟
Thread.sleep(1000);
} catch (InterruptedException e) {
// 休眠失败抛异常
throw new RuntimeException();
}
}
},"线程对象").start();
}
}
// 打印结果
线程对象、线程i = 0
线程对象、线程i = 1
线程对象、线程i = 2
休眠的特点就是可以自动实现线程的唤醒, 以继续后续的操作, 但是这里要注意, 如果现在有多个线程对象,那么休眠也是有先后顺序的。
public class Day02 {
public static void main(String[] args) {
// 创建线程,使用Lambda表达式
Runnable runnable = () -> {
// 创建三条线程
for (int i = 0; i < 3; i++) {
// 打印线程的名称
System.out.println(Thread.currentThread().getName() + "、线程i = " + i);
try {
// 休眠一秒钟
Thread.sleep(1000);
} catch (InterruptedException e) {
// 休眠失败抛异常
throw new RuntimeException();
}
}
};
for (int y = 0; y < 3; y++) {
new Thread(runnable, "线程对象 :" + y).start();
}
}
}
// 运行结果
线程对象 :0、线程i = 0
线程对象 :2、线程i = 0
线程对象 :1、线程i = 0
线程对象 :2、线程i = 1
线程对象 :1、线程i = 1
线程对象 :0、线程i = 1
线程对象 :0、线程i = 2
线程对象 :2、线程i = 2
线程对象 :1、线程i = 2
此时,我们从程序的感觉上来说看起来像是同是休眠, 过了一秒后又同时被唤醒了,但是实际上是有区别的 。从执行后的结果发现,我们线程是 021、210、021的方式执行的, 既然我们线程执行顺序是有先后顺序的, 那么我们输出线程信息也是有先后顺序的。
三、多线程中断
我们刚刚在线程上面执行的过程中发现,我们其实休眠的时候(sleep)是可以被打断的,而这个打断肯定是由其他线程完成的。而在Thread类里面提供有这种中断执行的处理方法有:
- 判断线程是否被中断:public boolean isInterrupted()
- 中断线程执行: public void interrupt()
public class Day02 {
public static void main(String[] args) throws Exception{
Thread thread = new Thread(() -> {
System.out.println("Start 开始执行~~~~~~~~");
try {
// 这里暂停10秒钟
Thread.sleep(10000);
System.out.println("End 执行结束 ~~~~~~~~");
} catch (InterruptedException e) {
System.out.println("O.o? 被中断了!! ~~~~");
}
});
// 开始执行
thread.start();
// 这里将线程暂停一秒钟
Thread.sleep(1000);
// 判断当前线程是否被中断了, 因为我们睡10秒,所以一秒后没有被中断
if (!thread.isInterrupted()) {
// 一秒后线程被中断了, 调用线程中断的方法
thread.interrupt();
}
}
}
// 运行结果
Start 开始执行~~~~~~~~
O.o? 被中断了!! ~~~~
结论: 我们正在执行的线程都是可以被中断的, 中断的线程必须进行异常的处理!
四、线程的强制执行
所谓线程的强制执行是指当满足某些条件后, 某一个线程对象将可以一直独占资源,一直到该线程的程序执行结束才会释放。那么Thread类当中给我们提供了一个方法
-
强制执行方法: public final void join() throws InterruptedException
public class Day02 {
public static void main(String[] args) throws Exception {
// 获取到当前的线程 当前线程为main ps:不知道为什么翻上面
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(() -> {
try {
// 主线程独占, 优先执行主线程
mainThread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "执行、 i = " + i);
}
}, "线程执行");
// 多线程启动
thread.start();
// 主线程霸占执行, 优先执行该方法
for (int y = 0; y < 3; y++) {
Thread.sleep(1000);
System.out.println("线程独占");
}
}
}
// 运行结果
线程独占
线程独占
线程独占
线程执行执行、 i = 0
线程执行执行、 i = 1
线程执行执行、 i = 2
结论: 在执行线程强制执行的时候一定要获取强制执行线程对象,才可以执行join 的使用, 在线程独占的时候, 一定会等主线程执行完毕后才会执行其他线程。
四、线程的礼让执行
线程的礼让是指将线程资源先让出去让别人先执行。 线程的礼让可以用Thread 中提供的方法:
- 线程礼让方法: public static native void yield();
public class Day02 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
// i 除以2 不等于0 的时候礼让主程序
if (i / 2 != 0) {
// 礼让方法
Thread.yield();
System.out.println("礼让主线程 ~~~~");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行、 i = " + i);
}
}, "线程执行");
// 多线程启动
thread.start();
// 主线程执行
for (int y = 0; y < 3; y++) {
Thread.sleep(1000);
System.out.println("主线程 y :" + y);
}
}
}
// 运行结果, 这里只要满足条件就等其他线程执行
线程执行执行、 i = 0
主线程 y :0
线程执行执行、 i = 1
礼让主线程 ~~~~
主线程 y :1
主线程 y :2
线程执行执行、 i = 2
礼让主线程 ~~~~
线程执行执行、 i = 3
礼让主线程 ~~~~
线程执行执行、 i = 4
结论: 礼让执行的时候,每调用Thread.yield() 我们都只会礼让一次当前的资源。
五、线程的优先级
从理论上来说, 线程的优先级越高越有可能先执行(越有可能先抢占到资源)。在Thread类里面针对这个操作有两个方法:
- 设置优先级:public final void setPriority(int newPriority)
- 获取优先级:public final int getPriority()
在优先级定义的时候, 是通过int类型的数字来完成的, 而对于数字的选择在Thread类里面就定义有三个常量:
- 最高优先级:public final static int MAX_PRIORITY = 10;
- 中等优先级:public final static int NORM_PRIORITY = 5;
- 最低优先级:public final static int MIN_PRIORITY = 1;
public class Day02 {
public static void main(String[] args) {
Runnable runnable = () ->{
for (int i = 0; i < 5; i++) {
try {
// 这里睡一秒方便观察
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "执行完毕。");
}
};
Thread thread1 = new Thread(runnable, "线程A");
Thread thread2 = new Thread(runnable, "线程B");
Thread thread3 = new Thread(runnable, "线程C");
// 设置第一个线程优先级最大
thread1.setPriority(Thread.MAX_PRIORITY);
// 设置第二个线程优先级最小
thread2.setPriority(Thread.MIN_PRIORITY);
// 设置第三个线程优先级最小
thread3.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
// 运行结果
线程C执行完毕。
线程A执行完毕。
线程B执行完毕。
线程A执行完毕。
线程B执行完毕。
线程C执行完毕。
线程A执行完毕。
线程B执行完毕。
线程C执行完毕。
线程A执行完毕。
线程B执行完毕。
线程C执行完毕。
线程A执行完毕。
线程C执行完毕。
线程B执行完毕。
现在我们创建线程后设置完成线程优先级后, A线程的优先级始终会是比较高的,三个线程里面不是第二个就是第一个先执行(不绝对),那么我们主线程(main)方法的优先级是三种优先级里面的那种呢? 创建的线程又是三种优先级的哪一种呢?
public class Day02 {
public static void main(String[] args) {
// 主方法优先级
System.out.println(Thread.currentThread().getPriority());
// 开启线程的默认优先级
System.out.println(new Thread().getPriority());
}
}
// 运行结果
5
5
结论: 通过运行方法我们发现, 不管是主线程还是创建的线程,线程的默认优先级都是中等优先级(5)。但是要切记,优先级高的只是有可能会优先执行,并不是绝对优先执行。
注: 此资源来自阿里云培训中心 - 阿里云