提示:以下是本篇文章正文内容,下面案例可供参考
一个线程就是一个执行流。每个线程之间都可以按照顺序执行自己的代码,多个线程之间“同时”执行着多份代码。举个例子:
一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。
如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。
首先,“并发编程”成为“刚需”。
其次,虽然多进程也能实现并发编程,但是线程比进程更轻量。
最后,线程虽然比进程轻量,但是还是不能将多核cpu的性能发挥到极致,于是又有了“线程池”和“协程”
线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.
方法1 继承Thread类
class MyThread extends Thread {
@Override//重写run方法
public void run() {
System.out.println("这里是线程运行的代码");
}
}
public static void main(){
MyThread t = new MyThread();
t.start();//线程开始执行
}
方法2 实现Runnable接口
class MyRunnable implements Runnable{
@Override//重写run方法
public void run() {
System.out.println("my runnable");
}
}
public class Demo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
其他变形
/**
* 匿名内部类的方式, 创建Thread子类
*/
public class Demo4 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
System.out.println(4444);
}
};
thread.start();
}
}
-------------------------------------------------------------
/**
* 匿名内部类的方式, 创建Runnable的子类
*/
public class Demo5 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(5555);
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
--------------------------------------------------------------------
/**
* lambda表达式, 创建线程
*/
public class Demo6 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println(5555);
});
thread.start();
}
}
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
需要强调的是,通过覆写run()方法创建的是线程对象,但此时线程对象仅仅只是被创建出来了,只有调用了start()方法之后线程才真正独立去执行了,才真正的在操作系统的底层创建出一个线程。
以下是代码示例,演示线程的构造方法以及getName():
public class Demo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
Thread t2 = new Thread("一号线程"){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(this.getName());
}
};
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
//这里不能使用getName,因为Runnable没有name这个属性
// System.out.println(this.getName());
}
},"二号线程");
t3.start();
}
}
运行结果打印了主线程名称以及创建的两个线程(这里使用带名称参数的构造方法):
接着讲上面转账的例子,李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。
两种方式:
1.通过共享的标记进行沟通(volatile关键字)
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public 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.调用interrupt()方法进行通知
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位(使用最多) |
使用 thread 对象的 interrupted() 方法通知线程结束:
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public 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()
+ ": 有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
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收到通知的方式有两种:
有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。
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("王五工作结束了");
}
}
此时运行程序,程序会像我们预想的那样 按部就班的工作。如图:
解释一下为什么:
t1.join()后,main线程就必须等待t1执行完,才能接着往下执行,t1未执行完时,main线程阻塞,t2同理。 这里t2是在执行完t1后启动的。假如t1线程和t2线程同时启动,那么将这两个线程join后,main线程会阻塞的时间是这两个线程运行时间的最大值,即阻塞时是同时在等这两个线程执行完毕。
谁调用join() 谁阻塞,即join写在哪个线程内,该线程就阻塞。
线程的状态是一个枚举类型Thread.State
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
还是我们之前的例子:
刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态;
当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度;
当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入BLOCKED 、 WATING 、 TIMED_WAITING 状态,至于这些状态的细分,我们以后再详解;
如果李四、王五已经忙完,为 TERMINATED 状态。
所以,前面提到的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。
在具体的代码中,线程的状态是如何进行转移的呢?
在代码中通过isAlive方法判定线程的存活状态:
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100_000_000; i++) {
}
System.out.println("线程执行结束");
});
System.out.println("start 方法之前:" + t1.getState());
t1.start();
System.out.println("start方法之后:"+ t1.getState());
Thread.sleep(1000);
System.out.println(t1.getState());
}
}
在观察线程状态的具体转移过程时,我们可以使用JDK自带的调试工具Jconsole,通常在JDK的安装目录bin目录下,当我们运行一个多线程代码后 可以双击打开Jconsole查看各线程的状态。
点击Demo20 链接后可以看到该线程此时状态为Timed_Waitting,若将sleep内时间删除重新运行,状态则会变为Waitting状态。
我们通过加锁的方式,创建两个线程去竞争同一把锁,这样没有竞争到的就进入block状态。这里Thread-0是t1(因为默认从0号开始创建),此时Thread-1就没有竞争到锁进入Blocked状态,等待锁释放资源。
1、线程的优点
2、线程和进程的区别
以上就是今天要讲的内容,本文算是多线程的入门吧,介绍了什么是线程、线程的创建、常见方法以及线程的状态和转移。后续将继续深入学习多线程以及JavaEE的其他内容,感兴趣的朋友可以点点订阅~