五、Java高级特性(多线程基础使用篇)
一、使用多线程有什么优点?
使用多线程可以最大限度的利用cpu的空闲时间来处理其他任务,比如一边让操作系统处理正在打印的数据,一边使用Word编辑文档。CPU在这些任务之间不停的切换,由于切换的速度非常快,给使用者的感受就是这些任务几乎是同时执行的。
image.png
以上图1-3我们发现在单任务也就是单线程的情况下,任务2必须要等待任务1执行完才能执行。而在1-4中我们使用多线程的情况下,任务2不需要等待任务1执行完毕,CPU可以在任务1和2之间来回的切换。
二、Java中使用多线程
在Java的JDK开发包中已经自带了对多线程技术的支持。实现多线程编程的方式主要有两种,一种是继承Thread,一种是实现Runnable接口。
1、继承Thread
package com.company;
public class Main {
public static void main(String[] args) {
InnerThread t = new InnerThread();
t.start();
}
}
class InnerThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("使用继承Thread启动了一个线程");
}
}
使用继承Thread启动了一个线程
我们创建了一个InnerThread 类,继承了Thread,并重写了run方法。在main函数中我们创建InnerThread 对象,并调用了start方法,这样就启动了一个线程。线程的工作就是在run方法中。
2、实现Runnable接口
package com.company;
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("使用继实现Runnable启动了一个线程");
}
}
使用继实现Runnable启动了一个线程
以上我们使用实现Runnable接口也启动了一个线程。线程具体的工作在Runnable接口的run方法中。我们可以看到Thread类其实也实现了Runnable接口
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
我们也可以通过传入Thread对象的方式启动一个线程,其实和实现Runnable是一样的。
package com.company;
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyThread());
t.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("通过传入Thread对象的方式启动一个线程");
}
}
通过传入Thread对象的方式启动一个线程
三、Thread的一些常用Api
1、Thread.currentThread()
currentThread方法,指的是获取当前代码块运行所在的线程。
package com.company;
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
System.out.println("1、当前线程:"+Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("2、当前线程:"+Thread.currentThread().getName());
}
}
1、当前线程:main
2、当前线程:Thread-0
我们可以看到main方法运行所在的线程的名字为main,而MyRunnable中的run方法运行所在的线程名字为Thread-0
2、isAlive()方法
判断当前线程是否处于活动的状态。活动状态指的是线程已经启动尚未终止,即线程处于正在运行或者准备开始运行的状态。
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
System.out.println("1当前线程状态:"+t.isAlive());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2当前线程状态:"+t.isAlive());
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
1当前线程状态:true
2当前线程状态:false
在代码中我们可以理解成调用start方法后线程变为活动的状态。以上我们看到调用start方法只会,由于我们启动的线程没有马上执行完毕,我们打印了isAlive结果是true,当我们在main方法中休眠了1000毫秒,之后再去打印isAlive,我们启动的线程已经执行完毕,所以结果是false。在上面的例子我们我们其实有两个线程,一个是main方法所在的main线程,一个是我们启动的线程,我们可以叫他做A线程。我们在main函数所在的线程中启动了一个A线程,这两个线程是异步执行任务,不需要排队执行。启动A线程之后,我们休眠了1000毫秒,我们休眠的线程是Main线程,对A线程没有影响,当我们再去打印A线程的isAlive方法的时候,结果是false,因为过了1000毫秒,A线程已经执行完毕,处于非活动状态。
3、sleep
让当前线程休眠(暂停执行)一段时间。
package com.company;
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
System.out.println("我们在"+Thread.currentThread().getName()+"休眠了1000毫秒");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我们在"+Thread.currentThread().getName()+"休眠了1000毫秒");
}
}
我们在main休眠了1000毫秒
我们在Thread-0休眠了1000毫秒
4、interrupt方法
使用interrupt方法来终止线程。
package com.company;
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 500000; i++) {
if (isInterrupted()) {
break;
}
System.out.println("我正在执行..." + i);
}
System.out.println("执行完毕");
}
}
我正在执行...
我正在执行...
我正在执行...
我正在执行...
我正在执行...
我正在执行...
我正在执行...
我正在执行...
true
执行完毕
在上面的例子中,我们在线程的run方法中,写了一个for循环,通过isInterrupted的返回值来判断是否跳出循环,结束循环里的工作。在Main方法中,我们启动了一个线程,休眠1000毫秒之后,我们调用interrupt方法,中断了线程。所以看到以上的打印结果是执行了一段时间for循环里的代码之后,跳出循环并打印了执行完毕。以上的方式我们可以结束掉for循环里的工作。但是线程没有真正被执行完毕,因为后面还是打印了执行完毕的字样,说明线程还是在工作。那我们怎么样才能真正的停止线程呢?
package com.company;
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 500000; i++) {
if (isInterrupted()) {
throw new InterruptedException();
}
System.out.println("我正在执行..." + i);
}
System.out.println("执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我正在执行...177357
我正在执行...177358
我正在执行...177359
我正在执行...177360
我正在执行...177361
我正在执行...177362
我正在执行...177363
我正在执行...177364
我正在执行...177365
我正在执行...177366
我正在执行...177367
我正在执行...177368
java.lang.InterruptedException
at com.company.MyThread.run(Main.java:22)
我们通过抛出InterruptedException异常的方式来停止线程,我没看到执行完毕这几个字样没有执行了。
5、yield
yield方法指的是放弃当前CPU资源,给其他线程去占用CPU资源。但是放弃时间不确定,有可能刚放弃又马上获得CPU时间片。因为CPU调度线程是随机的。
6、线程的优先级
通过setPriority方法可以设置线程的优先级,我们知道CPU调度线程是随机的,设置线程的优先级越高,可以使得线程获得CPU的资源越多。也就是CPU优先执行优先级高的线程。
优先级具有继承性
A线程启动B线程,那么A线程和B线程的优先级一样
优先级具有规则性和随机性
优先级具有规则性指的是:CPU会尽量将执行资源让给优先级比较高的,但是不是完全的。
优先级具有随机性指的是:优先级高的线程不代表任务一定能执行完,也有可能优先级低的任务先执行完了。但是大概率会是优先级高的会优先执行完。