Java多线程学习笔记(一)

Java多线程入门

进程和多线程的概念

什么是进程?

进程是操作系统结构的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程;它是系统进行资源分配和调度的一个独立单位;是受操作系统管理的基本运行单元。

什么是线程?

线程是在进程中独立运行的子任务。我们也要记住,多线程是异步的,线程被调用的时机是随机的,与CPU的调度有关,代码的执行顺序(代码的书写顺序)与代码的调用顺序无关。

线程的状态:

  • 新建状态(New):新创建了一个线程对象
  • 就绪状态(Runnable):线程对象调用了start()方法后,该线程处于可运行线程池中,等待CPU的调度
  • 运行状态(Running):CPU调度可运行池中的线程,执行线程的代码
  • 阻塞状态(Blocked):线程因某种原因放弃CPU的使用权

使用多线程

创建多线程对象

我们在运行Java代码的时候,有一个最基本的线程,就是在调用public static void main()方法的时候,我们其实在通过一个名为main的线程在执行main()方法,该线程由JVM创建。

创建多线程有2种方式,一种是继承Thread类,另一种是实现Runnable接口。这2种方式在工作时的性质是一样的,没有本质区别。

通过继承Thread类创建多线程:

1
2
3
4
5
6
7
8
9
10
11
12
public class Thread extends java.lang.Thread {
@Override
public void run() {
super.run();
System.out.println("Thread");
}
public static void main(String[] args) {
Thread thread = new Thread();
thread.start();
System.out.println("运行结束!");
}
}

输出:

运行结束!

Thread

我们可以看到,继承Thread类的方式创建新线程时,最大的局限就是不支持多继承,所以为了支持多继承,完全可以实现Runnable接口的方式。

我们可以看到Thread类的源代码,如下:

public class Thread implements Runnable

从这里可以看出,Thread类实现了Runnbale接口,它们之间具有多态关系。

我们注意到Thread.java类中start()方法与run()方法都可以调用线程对象中的run()方法,这2个方法有什么区别呢?

Thread.java类中的start()方法通知”线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码的thread.run()方法就不是异步执行了,而是同步,那么此线程对象并不交给”线程规划器”来处理,而是由main主线程来调用run()方法,这个时候与多线程无关与普通调用方法一样,必须等run()方法中的代码执行完毕后才能执行main函数后面的代码。

通过实现Runnable接口创建多线程:

如果欲创建的线程类已经有一个父类了,这时就不能再继承Thread类了,所以我们需要实现Runnable接口来应对这样的情况。

1
2
3
4
5
6
7
8
9
10
11
12
public class Runnable implements java.lang.Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
public static void main(String[] args) {
Runnable runnable = new Runnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("运行结束!");
}
}

输出:

运行结束!

Runnable

另外需要说明的是,我们已经知道了Thread类实现了Runnbale接口,那也就意味着构造函数Thread(Runnable target)方法不光可以传入Runnable接口的对象,还可以传入一个Thread类对象,这样做完全可以将一个Thread对象中的run()方法交给其他的线程进行调用。

多线程对象的常用函数

  • Thread.currentThread()方法返回代码段正在被哪个线程调用的信息
  • Thread对象实例.isAlive()方法的功能是判断当前线程是否处于活跃状态(正在运行状态或者准备开始运行状态)

  • Thread.sleep()方法的作用是在指定的毫秒数内让”当前执行的线程”休眠(暂停执行)

  • getId()方法可以获取线程的唯一标识

  • yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。当放弃的时间不确定,有可能刚刚放弃,马上又获取CPU时间片。

接下来,我们重要讨论一下如何停止正在运行的线程。停止一个线程意味着在线程处理完任务之前停掉正在做的事情,也就是放弃当前的操作。

在Java中有3种方法可以终止正在运行的线程:

  1. 使用退出标志,是线程正常退出,也就是run方法完成之后线程终止
  2. 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend以及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果
  3. 使用interrupt()方法中断线程

接下来,介绍如何使用interrupt()方法来停止线程。

如何判断线程是否处于停止状态呢?

  1. Thread.interrupted():测试当前线程是否已经中断,static方法,调用这个方法后线程的中断状态被清除。换句话说,如果连续2次调用该方法,则第二次调用将返回false。
  2. this.isInterrupted():测试线程Thread对象实例是否已经中断,非static方法,不清除状态标识

调用interrupt()方法不会真的停止线程,而是在当前线程中打了一个停止的标记,还需要配合其他的代码来停止线程。

我们可以在线程对象的run()方法中加入判断语句,判断如果线程处于停止状态那么就可以通过异常来跳过后面的代码执行,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyThread extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 500; i++) {
if (Thread.interrupted()) {
System.out.println("停止状态");
throw new InterruptedException();
}
System.out.println("i = " + i);
}
} catch (InterruptedException e) {
System.out.println("线程进入异常处理模块,停止运行run方法");
}
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
System.out.println("end");
}
}

输出:

i = 16

i = 17

end

i = 18

停止状态

线程进入异常处理模块,停止运行run方法

除了使用异常,我们还可以通过线程sleep的状态来停止线程以及通过interrupt方法与return结合来停止线程。不过,我们还是建议使用“抛异常”的方法来实现线程的停止,因为在catch快中还可以将异常向上抛,使线程停止的事件得到传播。

线程优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。

在Java中,线程的优先级分为1-10这个10个等级,如果超出范围,则JDK抛出异常。

优先级具有继承性质,比如A线程启动了B线程,则B线程的优先级与A是一样的。

但是,优先级具有随机性,高优先级的线程总是大部分先执行完,但不代表高优先级的线程全部都先执行完,CPU是尽量将执行资源让给优先级比较高的线程而已。

守护线程

在Java中有2种线程:一种是用户线程,另一种是守护线程。

守护线程是一种特殊的线程,当进程中不存在非守护线程了,守护线程就自动销毁。典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。Deamon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器)。

0%