JavaSE|多线程
多线程概述
引入
假如一个程序只有一条执行路径,那么该程序就是单线程程序。
假如一个程序有多条执行路径,那么该程序就是多线程程序。
多线程概述
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。那么,什么是进程呢?通过任务管理器我们就可以看到进程的存在。
什么是进程?
进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
多进程有什么意义?
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
那么对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。
多进程的作用不是提高执行速度,而是提高CPU的使用率。
什么是线程?
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。线程是程序中单个顺序的控制流,是程序使用CPU的基本单位。
多线程有什么意义?
多线程的作用不是提高执行速度,而是为了 提高应用程序的使用率。
多线程却给了我们一个错觉:让我们认为多个线程是并发执行的。其实不是。因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈。所以他们仍然是在抢CPU的资源执行。一个时间点上只有能有一个线程执行。而且谁抢到,这个不一定,所以,造成了线程运行的随机性。
什么是并发?
注意两个词汇的区别:并行和并发。
- 并行是逻辑上同时发生,指在某一个时间内同时运行多个程序。
- 并发是物理上同时发生,指在某一个时间点同时运行多个程序。
那么,我们能不能实现真正意义上的并发呢,是可以的,多个CPU就可以实现,不过你得知道如何调度和控制它们。
请
举例
扫雷游戏是一个进程,在扫雷游戏中有倒计时线程、游戏线程等。
Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考:JVM的启动是单线程的还是多线程的?
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
小结
- 进程就是正在运行的程序,多进程的作用是提高CPU的使用率。
- 线程是程序使用CPU的基本单位。多线程的作用是为了 提高应用程序的使用率。
- 线程是抢CPU执行的,运行具有随机性。
- JVM的启动是多线程的,至少启动了垃圾回收线程和主线程。
多线程实现有两种方法:
- 继承Thread类
- 实现Runnable接口
Thread 类
通过继承Thread类实现多线程步骤:
- 自定义类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程对象
问题:
- 为什么要重写run()方法
run()方法里面封装的是被线程执行的代码 - 启动线程对象用哪个方法?
start() - run()和start()方法的区别?
run()直接调用仅仅是普通方法。
start()先启动线程,再由JVM调用run()方法
线程调度
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
线程的两种调度模型
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
- Java使用的是抢占式调度模型。
如何设置和获取线程优先级
- public final int getPriority():返回线程对象的优先级
- public final void setPriority(int newPriority):更改线程的优先级。
在设置优先级时如果不在Java规定的线程优先级范围内,会报错 IllegalArgumentException(非法参数异常),表明向方法传递了一个不合法或不正确的参数。
注意事项:
- 线程默认优先级是5。
- 线程优先级的范围是:1-10。
- 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();
tp1.setName("东方不败");
tp2.setName("岳不群");
tp3.setName("林平之");
// 获取默认优先级
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());
// 设置线程优先级
// IllegalArgumentException(
// tp1.setPriority(100000);
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);
tp1.start();
tp2.start();
tp3.start();
}
}
public class ThreadPriority extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
线程控制
线程休眠
public static void sleep(long millis)
线程加入
public final void join():等待该线程终止。
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李渊");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}
public class ThreadJoin extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(this.getName() + ":" + i);
}
}
}
执行结果如下:
tj1执行完后,tj2和tj3才开始执行。
线程礼让
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
此静态方法可以让多个线程执行更和谐,但不能保证一人一次。
public class ThreadYieldDemo {
/**
* @param args
*/
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("林青霞");
ty2.setName("刘意");
ty1.start();
ty2.start();
}
}
public class ThreadYield extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(this.getName() + ":" + i);
Thread.yield();
}
}
}
后台线程
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setDaemon(true);
td2.setDaemon(true);
td1.setName("张飞");
td2.setName("关羽");
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadDaemon extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(this.getName() + ":" + i);
}
}
}
执行结果如下:
刘备执行完了以后,张飞和关羽也停止执行了(张飞和关羽都没有执行到99)。没有立即停是因为要把那个时间片用完。
类似坦克大战:
中断线程
public final void stop()
强迫线程停止执行。已过时,但仍可以使用。
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,我就终止你。
try {
Thread.sleep(3000);
ts.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束执行:" + new Date());
}
}
执行结果如下:
执行了3s,程序就被终止了,jvm退出,走不到“结束执行”那句话。
public void interrupt()
中断程序。把线程终止,并抛出一个InterruptedException。
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,我就干死你
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束执行:" + new Date());
}
}
执行结果如下:
线程的生命周期
Runnable接口
通过实现Runnable接口实现多线程步骤:
- 自定义类MyRunnable实现Runnable接口
- 重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,并把上一步的对象作为构造参数传递
问题:
有了继承Thread类实现多线程的方式,为什么还需要通过实现Runnable接口实现多线程的方式呢?
- 可以避免由于Java单继承带来的局限性。
- 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
t1.start();
t2.start();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}