前言
今天我进一步了解了java多线程的相关知识,根据几个相关问题进行学习并作出了解答,这篇文章将用来记录我学习到的有关知识。
问题列表
- 怎么实现多线程?
- 正确启动线程的方式
- 线程中常用的方法说明
- 如何停止线程?
- 如和中断线程?
- 线程的生命周期
- 线程的异常处理
- 死锁的解决方案
1. 怎么实现多线程?
实现多线程的方法有两种:
- 继承Thread类、并重写run方法。
- 实现Runnable接口、并重写run方法。
关于这两种方法有一个比较,如图:
2. 正确启动线程的方式
- 如果线程类继承了Thread类,那么在主方法中可以之间创建该线程类对象,使该对象调用start()方法即可启动线程。
- 如果线程类实现了Runnable接口,那么在主方法中同样需要创建该线程类对象,然后作为Thread的构造方法的参数创建一个线程,再调用start()方法即可启动线程。
现在对刚刚上面的两个问题进行总结:
3. 线程中常用的方法说明
sleep方法:
当在当前线程中调用Thread.sleep(long millis)方法时,当前线程会进入阻塞状态。millis参数指定了线程睡眠的时间,单位是毫秒。 当时间结束之后,线程会重新进入就绪状态。
代码演示:
public class SleepTest extends Thread {
static int i=0;
public void run(){
while(++i < 5) {
System.out.println(Thread.currentThread().getName()+ "输出" + i);
try {
Thread.sleep(1000); //间隔一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SleepTest t = new SleepTest();
t.start();
}
}
运行结果:
结果的输出是由间隔时间的。
join方法:
它是Thread的实例方法,在当前线程里的另外一个线程调用了join()方法时,当前线程会进入阻塞状态等待这个调用了join()方法的线程执行完毕后,再由阻塞状态进入到就绪状态。
代码演示:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new app("t1"));
Thread t2 = new Thread(new app("t2"));
Thread t3 = new Thread(new app("t3"));
t1.start();
t1.join(); //这里会等待t1线程执行完毕才会开启t2线程
t2.start(); //同上
t2.join();
t3.start();
t3.join();
}
static class app implements Runnable{
int i = 0;
String name;
public app(String name) {
this.name = name;
}
@Override
public void run() {
while(i < 5) {
System.out.println(name + "输出" + i++);
}
}
}
}
运行结果:
yield方法:
它是Thread的静态方法,如果在当前的线程中写了“Thread.yield”,那么就会使当前线程主动放弃cpu资源,进入就绪状态,让其它的线程来获取cpu资源,也有可能当前的线程还会获取cpu资源。
代码演示:
public class YieldTest {
public static void main(String[] args) {
Thread t1 = new Thread(new app("t1"));
Thread t2 = new Thread(new app("t2"));
t1.start();
t2.start();
}
static class app implements Runnable{
int i = 0;
String name;
public app(String name) {
this.name = name;
}
@Override
public void run() {
while(i < 8){
if(i == 5) {
Thread.yield();
System.out.println(name+"放弃cpu资源");
}
System.out.println(name+"输出了"+i);
i++;
}
}
}
}
运行结果:
wait、notify/notifyAll方法:
它们是Object的方法,需要用到synchronized关键字,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
1.wait()使当前线程阻塞,前提是必须先获得锁,当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
2.只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait(),再次释放锁。
代码演示:
public class LockTest {
static Object lock = new Object();
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new app1("t1"));
Thread t2 = new Thread(new app2("t2"));
t2.start();
Thread.sleep(3000);
t1.start();
}
static class app1 implements Runnable{
String name;
public app1(String name) {
this.name = name;
}
@Override
public void run() {
synchronized(lock) {
while(++i < 10) {
if(i == 5) {
System.out.println(name+"线程唤醒其他线程");
lock.notify();
}
System.out.println(name+"输出"+i);
}
}
}
}
static class app2 implements Runnable{
String name;
public app2(String name) {
this.name = name;
}
@Override
public void run() {
synchronized(lock){
if(i != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name+"线程被唤醒");
System.out.println(name+"输出"+i);
}
}
}
}
这段代码里面有两个线程,让t1线程一直输出直到i等于5的时候,唤醒被阻塞的t2线程,但唤醒t2线程不代表释放锁,会先等待t1线程执行完毕后才会执行t2线程。
运行结果:
4. 如何停止线程?
首先,stop()方法不推荐使用,因为stop()会让线程戛然而止,使得在实际开发中不能知道线程还有哪些工作还没做、完成了哪些工作,还有我们不能够去做一些应有的清理工作。
正确的停止线程方法:设置退出标志
public class StopThread extends Thread{
public void run() {
test01 t = new test01();
Thread thread = new Thread(t);
//开始线程
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//停止线程
t.flag = false;
}
public static void main(String[] args) {
Thread stopThread = new Thread(new StopThread());
stopThread.start();
}
static class test01 implements Runnable{
//设置线程停止的标志
//volatile保证了线程可以正确的读取其他线程写入的值
volatile boolean flag = true;
@Override
public void run() {
System.out.println("线程开始.......");
while(flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程正在进行.....");
}
System.out.println("线程停止.......");
}
}
}
运行结果:
5. 如何中断线程?
调用interrupt()方法:
当线程调用了某些方法,比如sleep()方法而进入一种阻塞状态,此时该线程如果调用了interrupt()方法,会产生两个结果:1.线程的中断状态被清除(退出阻塞状态)2.会抛出一个InterruptedException异常,表明线程被中断了。
代码演示:
public class InterruptThread extends Thread{
public void run() {
while(true) {
System.out.println("线程正在进行.....");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("中断阻塞状态");
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread stopThread = new Thread(new InterruptThread());
//开始线程
System.out.println("线程开始.......");
stopThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断线程
stopThread.interrupt();
}
}
运行结果:
虽然主线程已经执行结束,但stopThread 线程却还会继续进行,这里只是为了演示interrupt()方法中断阻塞状态的功能。
6. 线程的生命周期
相关解释:
1.就绪状态:创建了线程对象后,调用了线程的start()方法(注意此时线程只是进入了线程队列,等待获取cpu服务,具备了运行条件,但并不一定已经开始运行)。
2.运行状态:处于就绪状态的线程,一旦获取了cpu资源,便进入到运行状态,开始执行run()方法里面的逻辑。
3.终止状态:线程的run()方法执行完毕,或者线程调用了stop()方法(此方法已过时,不推荐使用),线程便进入终止状态。
4.阻塞状态:一个正在执行的线程在某种情况下,由于某种原因(比如线程调用了sleep()方法、wait()方法、join()方法)而暂时让出了cpu资源,暂停了自己的执行,便进入到阻塞状态。
7. 线程的异常处理
关于线程的异常处理我是这么理解的,每一个线程在运行的时候都是相互独立的,那么它出现的异常应该由各自的线程自己来处理。
下面尝试不在线程中处理异常,尝试在主线程中处理异常:
public class EpTest implements Runnable{
public static void main(String[] args) {
EpTest instance = new EpTest();
try{
Thread t1 = new Thread(instance);
t1.start();
}catch (Exception e){
System.out.println("主线程捕获线程中的异常");
}
}
@Override
public void run() {
throw new RuntimeException();
}
}
Exception in thread "Thread-0" java.lang.RuntimeException
at EpTest.run(EpTest.java:19)
at java.base/java.lang.Thread.run(Thread.java:834)
结果显示此时这个主线程并不能处理该异常
那么处理线程中出现的异常有以下我学习到的几种方法:
- 直接在线程中使用try、cath语句处理异常。
@Override
public void run() {
try{
...
}catch (Exception e){
...
}
}
- 对于不在线程中处理的异常,可以交给异常处理器处理UncaughtExceptionHandler
public class EpTest implements Runnable{
public static void main(String[] args) {
EpTest instance = new EpTest();
Thread t1 = new Thread(instance);
t1.setUncaughtExceptionHandler(new EpHandler());
t1.start();
}
@Override
public void run() {
throw new RuntimeException();
}
}
class EpHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t,Throwable e){
System.out.println("捕获未处理的异常");
}
}
首先需要自定义一个异常处理器:
class EpHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t,Throwable e){
System.out.println("捕获未处理的异常");
}
}
接着在主方法中设置这个异常处理器:
public static void main(String[] args) {
EpTest instance = new EpTest();
Thread t1 = new Thread(instance);
//调用Thread类的setUncaughtExceptionHandler()设置异常处理器
t1.setUncaughtExceptionHandler(new EpHandler());
t1.start();
}
3.使用线程池
public class EpTest2 implements Runnable {
public static void main(String[] args) {
Thread t1 = new Thread(new EpTest2());
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(t1);
executorService.shutdown();
}
@Override
public void run() {
//在run方法中设置异常处理器
Thread.currentThread().setUncaughtExceptionHandler(new EpHandler());
throw new RuntimeException();
}
}
需要注意的是,只有通过execute()方法提交任务,才能将它抛出的异常交给未捕获异常处理器。
另外的一种方法:
public class EpTest2 implements Runnable {
public static void main(String[] args) {
Thread t1 = new Thread(new EpTest2());
ExecutorService executorService = Executors.newCachedThreadPool();
Future<?> future=executorService.submit(t1);
executorService.shutdown();
try {
//抛出异常
future.get();
}catch (Exception e){
System.out.println("捕获未处理的异常");
}
}
@Override
public void run() {
throw new RuntimeException();
}
}
这种方法通过submit()方法提交的任务由于抛出了异常而结束,该异常将被Future.get()封装在ExecutionException中重新抛出。
8. 死锁的解决方案
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
产生死锁的必要条件:
(1)互斥条件:一个资源每次只能被一个线程使用,直到被该进程释放。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
解决死锁的问题:
- 保证加锁的顺序(按照一定的顺序给线程加锁)
- 设置加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
相关教程:
细说多线程之Thread VS Runnable
https://www.imooc.com/learn/312
Java Socket应用
https://www.imooc.com/learn/161
Java高并发之魂:synchronized深度解析
https://www.imooc.com/learn/1086
Java多线程之内存可见性
https://www.imooc.com/learn/352