Java多线程整理1-概念-优势-创建-Thread 类及常见方法
多线程整理1
1. 概念
进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的。
每个进程至少有一个线程存在,即主线程。
多进程与多线程本质区别:每个进程拥有自己的一整套变量,而线程则共享数据。在有些操作系统中,与进程相比较,线程的创建与撤销比启动新进程开销要小的多。
2. 优势
2.1 阻塞处理
一个业务正在处理时不影响其他人正常使用
import java.util.Scanner;
public class Test {
//递归计算第N个斐波那契数
private static long fib (int n) {
if(n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
//创建线程类
private static class FibThread extends Thread {
private int n;
FibThread(int n) {
this.n = n;
}
@Override
public void run() {
System.out.println(n+ "-fib->" + fib(n));
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
int n = sc.nextInt();
//每接收一个数就创建一个线程去计算
FibThread thread = new FibThread(n);
thread.start();
}
}
}
例如斐波那契数列第45个在计算时并不影响其继续使用
2.2 提升速度
多线程在一些场合下是可以提高程序的整体运行效率的。并非所有多线程都可以提升运行速度。
观察:
public class Test{
private static final long COUNT = 10_0000_0000L;
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() {
@Override
public 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);
}
}
结果
3. 创建线程
3.1 方法一—继承Thread类
不推荐,如果有很多任务,要为每个任务创建一个独立的线程所付出的代价太大
class MyThread extends Thread {
@Override
public void run() {
//code
}
}
MyThread t = new MyThread();
t.start(); // 线程开始运行
3.2 方法二—实现 Runnable 接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。
该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用。
class MyRunnable implements Runnable {
@Override
public void run() {
//code
}
}
Thread t = new Thread(new MyRunnable());
t.start(); // 线程开始运行
3.3 方法三—使用匿名类_lambda表达式创建线程对象
//使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
//code
}
};
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//code
}
});
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> {
//run() 方法
});
4. Thread 类及常见方法
4.1 Thread 的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组 |
4.2 Thread 的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面进一步说明
4.3 启动一个线程
调用 start 方法
4.4 中断一个线程
4.4.1 使用自定义的标志位通知停止
class Condition {
public volatile boolean running = true;
}
class B extends Thread {
private Condition condition;
B(Condition condition) {
this.condition = condition;
}
@Override
public void run() {
while (condition.running) {
System.out.println("znpy");
}
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Condition condition = new Condition();
B b = new B(condition);
//定义变量并传入引用,使得 B线程看的条件和 main 修改的条件是同一个对象
//B 线程才能看到修改
b.start();
sc.nextLine();
condition.running = false;
}
}
4.4.2 使用Java提供的方式通知停止
class B extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {
System.out.println("znpy");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public class Main {
public static void main(String[] args) {
B b = new B();
b.start();
Scanner sc = new Scanner(System.in);
sc.nextLine();
b.interrupt();
}
}
通过 thread 对象调用 interrupt() 方法通知该线程停止运行时
thread 收到通知的方式有两种:
- 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志
- 如不是第一种,则thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
4.5 等待一个线程
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
4.6 获取当前线程引用
public static Thread currentThread(); 返回当前线程对象的引用
4.7 休眠当前线程
因为线程的调度是不可控的,所以,这个方法只能保证休眠时间是大于
等于休眠时间的。
方法 | 说明 |
---|---|
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |