创建线程
方法一 继承Thread类
继承 Thread 来创建一个线程类.
class MyThread extends Thread {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
创建 MyThread 类的实例
MyThread t = new MyThread();
调用 start 方法启动线程
t.start(); // 线程开始运行
方法2 实现 Runnable 接口
实现 Runnable 接口
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这里是线程运行的代码");}
}
创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
Thread t = new Thread(new MyRunnable());
调用 start 方法
t.start(); // 线程开始运行
对比上面两种方法:
- 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用
- 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()
其他变形
匿名内部类创建 Thread 子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使用匿名类创建 Thread 子类对象");}
};
匿名内部类创建 Runnable 子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使用匿名类创建 Runnable 子类对象");}
});
lambda 表达式创建 Runnable 子类对象
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {System.out.println("使用匿名类创建 Thread 子类对象");
});
多线程的优势
可以观察多线程在一些场合下是可以提高程序的整体运行效率的。
- 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
- serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {// 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的private static final long count = 10_0000_0000;public static void main(String[] args) throws InterruptedException {// 使用并发方式concurrency();// 使用串行方式serial();}private static void concurrency() throws InterruptedException {long begin = System.nanoTime();// 利用一个线程计算 a 的值Thread thread = new Thread(new Runnable() {@Overridepublic void run() {int a = 0;for (long i = 0; i < count; i++) {a--;}}});thread.start();// 主线程内计算 b 的值int b = 0;for (long i = 0; i < count; i++) {b--;}// 等待 thread 线程运行结束thread.join();// 统计耗时long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("并发: %f 毫秒%n", ms);}private static void serial() {// 全部在主线程内计算 a、b 的值long begin = System.nanoTime();int a = 0;for (long i = 0; i < count; i++) {a--;}int b = 0;for (long i = 0; i < count; i++) {b--;}long end = System.nanoTime();double ms = (end - begin) * 1.0 / 1000 / 1000;System.out.printf("串行: %f 毫秒%n", ms);}
}
运行结果:
并发: 399.651856 毫秒
串行: 720.616911 毫秒
Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
Thread 的常见构造方法
Thread 的几个常见属性
启动一个线程-start()
之前我们已经看到了如何通过重写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程就开始运行了。run方法只是告诉描述了多线程要去执行的任务内容。
调用 start 方法, 才真的在操作系统的底层创建出一个线程.
中断一个线程
A一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那B该如何通知A停止呢?这就涉及到我们的停止线程的方式了。
目前常见的有以下两种方式:
- 通过共享的标记来进行沟通
- 调用 interrupt() 方法来通知
示例1:使用自定义的变量来作为标志位.
public class ThreadDemo {private static class MyRunnable implements Runnable {public volatile boolean isQuit = false;@Overridepublic void run() {while (!isQuit) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");target.isQuit = true;}
}
示例2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定
义标志位.
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.
使用 thread 对象的 interrupted() 方法通知线程结束.
public class ThreadDemo {private static class MyRunnable implements Runnable {@Overridepublic void run() {// 两种方法均可以while (!Thread.interrupted()) {//while (!Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName()+ ": 别管我,我忙着转账呢!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println(Thread.currentThread().getName()+ ": 有内鬼,终止交易!");// 注意此处的 breakbreak;}}System.out.println(Thread.currentThread().getName()+ ": 啊!险些误了大事");}}public static void main(String[] args) throws InterruptedException {MyRunnable target = new MyRunnable();Thread thread = new Thread(target, "李四");System.out.println(Thread.currentThread().getName()+ ": 让李四开始转账。");thread.start();Thread.sleep(10 * 1000);System.out.println(Thread.currentThread().getName()+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");thread.interrupt();}
}
thread 收到通知的方式有两种
- 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择
忽略这个异常, 也可以跳出循环结束线程.
2. 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
等待一个线程
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {Runnable target = () -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName()+ ": 我还在工作!");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我结束了!");};Thread thread1 = new Thread(target, "李四");Thread thread2 = new Thread(target, "王五");System.out.println("先让李四开始工作");thread1.start();thread1.join();System.out.println("李四工作结束了,让王五开始工作");thread2.start();thread2.join();System.out.println("王五工作结束了");}
}
获取当前线程引用
方法:public static Thread currentThread();
说明:返回当前线程对象的引用
public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}
休眠当前线程
也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
public static void sleep(long millis) throws InterruptedException
休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException
可以更高精度的休眠