7. 多线程
7.1 多线程的概述
7.1.1 线程的概念
什么是线程呢?想要知道线程,就必须先知道进程。进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
线程是进程中的单个顺序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程程序;一个进程如果有多条执行路径,则称为多线程程序。
一个进程可以有一到多个线程,而一个线程只能属于一个进程。
思考题1:jvm虚拟机的启动是单线程的还是多线程?
多线程。因为JVM启动时相当于启动了一个进程,然后这个进程会创建一个主线程和垃圾回收线程以及其他的一些线程,主线程会调用main方法,垃圾回收线程会用来回收一些垃圾。
思考题2:并行和并发的区别
并行是逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发是物理上同时发生,指在某一个时间点同时运行多个程序。
7.1.2 多线程的利弊
利处:解决了多部分代码同时运行的问题。
弊端:线程太多,会导致效率的降低。
7.2 多线程的创建
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来,而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类或者接口供我们使用,这样就可以实现多线程了。创建线程有两种方法:一种是继承Thread类;另一种是通过实现Runnable接口。
7.2.1 继承Thread类
1)定义一个类并继承Thread。
2)重写Thread类的 run()方法。
3)建立自定义类的一个对象(此时一个线程也被建立)。
4)调用线程的start()方法。
示例:
<span style="font-size:18px;">public class MyThreadDemo {
public static void main(String[] args) {
//创建两个线程对象
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
// 自己写代码
// System.out.println("好好学习,天天向上");
// 一般来说,被线程执行的代码肯定是比较耗时的,所以我们用循环改进
for(int x = 0;x<100;x++){
System.out.println(x);
}
}
}</span>
思考1:为什么要重写run()方法呢?
创建一个线程类,run()方法里面包含的是需要被线程执行的代码。
思考2:run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
7.2.2 实现Runnable接口
1)定义类实现Runnable接口
2)覆盖Runnable接口中的run()方法。
3)通过Thread类建立线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5)调用Thread类的star()t方法开启线程
示例:
<span style="font-size:18px;">public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// Thread(Runnable target, String name)
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(x);
}
}
}</span>
7.3 多线程的设置和控制
7.3.1 获取和设置线程对象名
获取:
public final String getName():获取线程的名称。
针对不是Thread类的子类中获取线程对象名的方法
public static Thread currentThread():返回当前正在执行的线程对象
Thread.currentThread().getName()
设置:
1)public final void setName(String name):设置线程的名称
2)通过构造方法
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
// 无参构造+setXxx()
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
// 调用set方法设置名称
my1.setName("林青霞");
my2.setName("东方不败");
my1.start();
my2.start();
// 带参构造方法给线程起名字
MyThread my3 = new MyThread("李亚鹏");
MyThread my4 = new MyThread("令狐冲");
my3.start();
my4.start();
// 我要获取main方法所在的线程对象的名称,该怎么办呢?
// 遇到这种情况,Thread类提供了一个很好玩的方法:
// public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
}
}
class MyThread extends Thread {
public MyThread() {
}
// 通过构造方法设置对象名
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 获取对象名
System.out.println(getName() + ":" + x);
}
}
}
7.3.2 获取和设置线程优先级
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
获取线程对象的优先级:
public final int getPriority():返回线程对象的优先级
设置线程对象的优先级
public final void setPriority(int newPriority):更改线程的优先级。线程默认优先级是5(NORM_PRIORITY),可设置线程优先级的范围是:1(MIN_PRIORITY)-10(MAX_PRIORITY)。
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("线程1");
tp2.setName("线程2");
tp3.setName("线程3");
//设置线程优先级
tp1.setPriority(Thread.MAX_PRIORITY);
tp2.setPriority(Thread.MIN_PRIORITY);
tp3.setPriority(Thread.NORM_PRIORITY);
tp1.start();
tp2.start();
tp3.start();
}
}
class ThreadPriority extends Thread{
@Override
public void run() {
for(int x =0;x<100;x++){
System.out.println(getName()+":"+x);
}
}
}
运行后发现基本上先是线程1线执行的,最后是线程3执行的。
7.3.3 线程休眠(sleep)
public static void sleep(long mil