第二十四章 多线程
1 多线程概述
1.1 什么是进程?什么是线程?
进程就是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程
1.2 对于Java程序来说,当在Dos命令容器中输入:
java HelloWorld回车之后
会先启动JVM, 而JVM就是一个进程
JVM再启动一个主线程调用main方法
同时再启动一个垃圾回收线程负责看护, 回收垃圾
最起码, 现在的java程序中至少有两个线程并发
1.3 进程和线程是什么关系? 举个例子
阿里巴巴: 进程
马云: 阿里巴巴的一个线程
童文红: 阿里巴巴的一个线程
进程可以看做是现实生活当中的公司
线程可以看做是公司当中的某个员工
注意:
进程A和进程B的内存独立不共享
线程A和线程B呢?
在java语言中:
线程A和线程B, 堆内存和方法区内存共享
但是栈内存独立, 一个线程一个栈
假设启动10个线程, 会有10栈空间,每个栈和每个栈之间,互不干扰, 各自执行各自的, 这就是多线程并发
火车站, 可以看做是一个进程
火车站中的每一个售票窗口可以看做是一个线程
我在窗口1购票, 你可以在窗口2购票, 你不需要等我, 我也不需要等你
所以多线程并发可以提高效率
java中之所以有多线程机制, 目的就是为了提高程序的处理效率
思考一个问题:
使用了多线程机制之后, main方法结束, 是不是有可能程序也不会结束, main方法结束只是主线程结束了, 主栈空了, 其它的栈(线程)可能还在压栈弹栈
分析一个问题: 对于单核的CPU来说, 真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说, 真正的多线程并发是没问题的
4核CPU表示同一个时间点上, 可以真正的有4个进程并发执行
什么是真正的多线程并发?
t1线程执行t1的
t2线程执行t2的
t1不会影响t2, t2也不会影响t1, 这叫做真正的多线程并发
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发, 但是可以做到给人"伪并发"的感觉
对于单核的CPU说来, 在某一个时间点上实际只能处理一件事情, 但是由于CPU的处理速度极快, 多个线程之间频繁切换执行, 给人的感觉是: 多个事情在同时做
1.4 开启线程的方式
第一种方式: 编写一个类, 直接继承java.lang.Thread, 重写run方法
// 定义线程类
public class MyThread extends Thread {
public void run() {
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程
t.start();
第二种方式: 编写一个类, 实现java.lang.Runnable接口, 实现run方法
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run() {
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
注意: 第二种方式实现接口比较常用, 因为一个类实现了接口, 它还可以去继承其它的类, 更灵活
1.5 开启线程的第一种方式
package com.bjpowernode.java.thread;
/*
实现线程的第一种方式:
编写一个类, 直接继承java.lang.Thread,重写run方法
怎么创建线程对象? new就行了
怎么启动线程呢? 调用线程对象的start()方法
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 这里是main方法,这里的代码属于主线程,在主栈中运行
// 新建一个分支线程对象
MyThread myThread = new MyThread();
// myThread.run(); // 不会启动线程, 不会分配新的分支栈
// 启动线程
// start()方法的作用是: 启动一个分支线程, 在JVM中开辟一个新的栈空间, 这段代码任务完成之后, 瞬间就结束了
// 这段代码的任务只是为了开启一个新的栈空间, 只要新栈空间开出来, start()方法就结束了, 线程就启动成功了
// 启动成功的线程会自动调用run()方法, 并且run方法在分支线程的栈底部(压栈)
// run方法在分支栈的栈底部, main方法在主栈的栈底部, run和main是平级的
myThread.start();
// 这里的代码还是运行在主线程中
for(int i = 0; i < 1000; i++) {
System.out.println("主线程 ---> " + i);
}
}
}
class MyThread extends Thread {
1.6 开启线程的第二种方式
package com.bjpowernode.java.thread;
/*
实现线程的第二种方式, 编写一个类实现java.lang.Runnable接口
*/
public class ThreadTest03 {
public static void main(String[] args) {
/*
// 创建一个可运行的对象
MyRunnable r = new MyRunnable();
// 将可运行的对象封装成一个线程对象
Thread t = new Thread(r);
// 启动线程
t.start();
*/
// 合并代码
Thread t = new Thread(new MyRunnable());
for(int i = 0; i < 100; i++) {
System.out.println("主线程---> " + i);
}
}
}
// 这并不是一个线程类, 是一个可运行的类, 它还不是一个线程
class MyRunnable implements Runnable {
1.7 采用匿名内部类方式创建线程
package com.bjpowernode.java.thread;
/*
采用匿名内部类可以吗?
*/
public class ThreadTest04 {
public static void main(String[] args) {
// 创建线程对象, 采用匿名内部类方式
Thread t = new Thread(new Runnable(){
1.8 线程生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
2.1 获取当前线程对象
package com.bjpowernode.java.thread;
/*
1 怎么获取当前线程对象?
Thread t = Thread.currentThread();
返回值t就是当前线程
2 获取线程对象的名字
String name = 线程对象.getName();
3 修改线程对象的名字
线程对象.setName();
4 当线程没有设置名字的时候, 默念的名字有什么规律?
Thread-0
Thread-1
Thread-2
...
*/
public class ThreadTest05 {
public void doSome() {
// 这样就不行了
// this.getName();
// super.getName();
// 但是这样可以
String name = Thread.currentThread().getName();
System.out.println("=====>" + name);
}
public static void main(String[] args) {
ThreadTest05 tt = new ThreadTest05();
tt.doSome();
// currentThread就是当前线程对象
// 这个代码出现在main方法当中, 所以当前线程就是主线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()); // main
// 创建线程对象
MyThread2 t = new MyThread2();
System.out.println(t.getName()); // Thread-0
// 设置线程的名字
t.setName("t1");
// 获取线程的名字
String tName = t.getName();
System.out.println(tName); // tttt
// 启动线程
t.start();
MyThread2 t2 = new MyThread2();
System.out.println(t2.getName()); // Thread-1
t2.setName("t2");
t2.start();
}
}
class MyThread2 extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
// currentThread就是当前线程对象, 当前线程是谁呢?
// 当t1线程执行run方法, 那么这个当前线程就是t1
// 当t2线程执行run方法, 那么这个当前线程就是t2
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "---> " + i);
System.out.println(super.getName() + "---> " + i);
System.out.println(this.getName() + "---> " + i);
}
}
}