为什么要使用多线程
- 耗时的操作另起一个线程,提高应用程序的响应。
- 提高计算机系统CPU的利用率。
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改。
应用场景
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序。
Thread类
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread
类来体现
Thread类的特性
每个线程都是通过某个特定Thread
对象的run()
方法来完成操作的,经常把run()
方法的主体称为线程体,通过该Thread
对象的start()
方法来启动这个线程,而非直接调用run()
。
常用创建线程的两种方式
继承Thread类
步骤:
- 创建一个继承于Thread类的子类。
- 重写
Thread
类的run()
--> 将此线程执行的操作声明在run()
中。 - 创建
Thread
类的子类的对象。 - 通过此对象调用
start()
:①启动当前线程 ② 调用当前线程的run()
。
需要注意的点:
- 我们启动一个线程,必须调用
start()
,如果自己手动调用run()
方法,那么就只是普通方法,没有启动多线程模式。 如果再启动一个线程,必须重新创建一个Thread
子类的对象,调用此对象的start()
。 -
run()
方法由JVM调用,什么时候调用,执行的过程控制都由操作系统的CPU调度决定。 - 想要启动多线程,必须调用
start()
方法。 - 一个线程对象只能调用一次
start()
方法启动,如果重复调用了,则将抛出异常lllegalThreadStateException
。
代码示例:
// 1.继承Thread类
class MyThread extends Thread {
// 2.重写run方法
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3.新建Thread对象
// MyThread t1 = new MyThread();
// 4.调用start方法
// t1.start();
//
// MyThread t2 = new MyThread();
// t2.start();
//方法二:这个方法用匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}.start();
}
}
实现Runnable接口的方式
步骤:
- 创建一个实现了
Runnable
接口的类。 - 实现类去实现
Runnable
中的抽象方法:run()
。 - 创建实现类的对象。
- 将此对象作为参数传递到
Thread
类的构造器中,创建Thread
类的对象。 - 通过
Thread
类的对象调用start()
。
代码示例:
// 1.创建一个实现了Runnable接口的类
class MyRunnable implements Runnable {
// 实现Runnable接口中的抽象方法run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class RunnableTest {
public static void main(String[] args) {
// 3.创建实现类的对象
MyRunnable runnable = new MyRunnable();
// 4.将此对象作为参数传递到Thread类的构造其中,创建Thread对象
Thread thread = new Thread(runnable);
// 5.通过Thread类的对象调用start()
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "**main**");
}
// 还可以这么写,和Thread类的匿名子类很相似,但是注意区别。
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i = 0; i < 100; i++) {
// System.out.println(Thread.currentThread().getName() + ":" + i);
// }
// }
// }).start();
}
}
两种方式的对比
开发中优先选择:实现Runnable
接口的方式。
原因:
- 实现的方式没有类的单继承性的局限性。
- 实现的方式更适合来处理多个线程共享数据的情况。
// Thread类实现了Runnable
public class Thread implements Runnable
相同点:
两种方式都需要重写run()
,将线程要执行的逻辑声明在run()
中。 目前两种方式,要想启动线程,都是调用的Thread
类中的start()
。
Thread类的常用方法
常用方法
-
start()
:启动当前线程;调用当前线程的run()
,只有Thread
类和他的子类才能调用start()
方法。 -
run()
:通常需要重写Thread
类中的此方法,将创建的线程要执行的操作声明在此方法中。 -
currentThread()
:静态方法,返回执行当前代码的线程。 -
getName()
:获取当前线程的名字。 -
setName()
:设置当前线程的名字。 -
yield()
:释放当前cpu的执行权。 -
join()
:在线程a中调用线程b的join()
,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。 -
stop()
:已过时。当执行此方法时,强制结束当前线程。 -
sleep(long millitime)
:让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。 -
isAlive()
:判断当前线程是否存活。
线程的优先级
总共有1~10
,10个级别
-
MAX_PRIORITY
:10 -
MIN _PRIORITY
:1 -
NORM_PRIORITY
:5 -->默认优先级
获取和设置当前线程的优先级:
-
getPriority()
:获取线程的优先级 -
setPriority(int p)
:设置线程的优先级
说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
线程通信:wait()
、notify()
、notifyAll()
:此三个方法定义在Object
类中的。
线程的分类
- 守护线程,如:垃圾回收线程,依赖于主线程而存在。
- 用户线程,如:
main
方法的线程。
Thread的生命周期
线程的五种状态:
- 新建:当一个
Thread
类或其子类的对象被声明并创建时,新生的线程对象处于新建状态 - 就绪:处于新建状态的线程被
star()
后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源 - 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,
run()
方法定义了线程的操作和功能 - 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
说明:
- 生命周期关注两个概念:状态、相应的方法。
- 关注状态a切换为状态b:哪些方法执行了;或者某个方法主动调用导致状态a切换到状态b。
- 阻塞:临时状态,不可以作为最终状态。
- 死亡:最终状态。
网友评论