Java基础 第四节 第一课
多线程原理
在第三节中我们已经写过一个多线程的代码, 大家可能多原理并不是很清楚. 那么现在我们来具体画个多线程执行时序图来体验一下多线程程序的执行流程. 代码如下:
自定义线程类
public class MyThread extends Thread {
/**
* 利用继承中的特点将线程名称传递, 进行设置
*/
public MyThread(String name) {
super(name);
}
/**
* 重写run方法, 定义线程要执行的代码
*/
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// getName()方法来自父亲
System.out.println(getName() + i);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
测试类
public class Test {
public static void main(String[] args) {
System.out.println("这里是 main 线程!");
MyThread mt = new MyThread("小白");
mt.start(); // 开启了一个新的线程
for (int i = 0; i < 20; i++) {
System.out.println("啦啦啦啦: " + i);
}
}
}
输出结果:
这里是 main 线程!
小白0
小白1
小白2
小白3
小白4
小白5
小白6
小白7
小白8
小白9
小白10
小白11
小白12
小白13
小白14
小白15
小白16
小白17
小白18
小白19
啦啦啦啦: 0
啦啦啦啦: 1
啦啦啦啦: 2
啦啦啦啦: 3
啦啦啦啦: 4
啦啦啦啦: 5
啦啦啦啦: 6
啦啦啦啦: 7
啦啦啦啦: 8
啦啦啦啦: 9
啦啦啦啦: 10
啦啦啦啦: 11
啦啦啦啦: 12
啦啦啦啦: 13
啦啦啦啦: 14
啦啦啦啦: 15
啦啦啦啦: 16
啦啦啦啦: 17
啦啦啦啦: 18
啦啦啦啦: 19
- 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
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
流程图
程序启动运行 main 时候, java 虚拟机启动一个进程, 主线程 main 在 main() 调用的时候被创建. 随着调用 mt 的对象的 start 方法, 另一个新的线程也启动了, 这样整个应用就再多线程下运行.
通过这张图我们可以很清晰的看到多线程的执行流程, 那么为什么可以完成并发执行呢?
原理
多线程执行时, 到底在内存中是如何运行的呢? 以上个程序为例, 进行图解说明:
多线程执行的时候, 在栈内存中, 每一个执行线程都有一片字节所属的内存空间. 进行方法的压栈和弹栈.
当执行线程的任务结束了, 线程自动在栈内存中释放了. 当所有的执行线程都结束了, 那么进程也就结束了.
Thread 类
在上一节中我们已经可以完成最基本的线程开启. 那么在我们完成操作过程中用到了java.lang.Thread
类, API 中该类中定义了有关线程的一些方法, 具体如下:
构造方法
public Thread()
: 分配一个新的线程对象public Thread(String name)
: 分配一个指定名字的新的线程对象public Thread(Runnable target)
: 分配一个带有指定目标的新的线程对象public Thread(Runnable target, String name)
: 分配一个带有指定目标的新的线程对象并指定名字
常用方法
public String getName()
: 获取当前线程名称public void start()
: 开始执行此线程, Java 虚拟机调用此线程的 run 方法public void run()
: 此线程要执行的任务在此处定义代码public static void sleep(long millis)
: 使当前正在执行的线程以指定的毫秒数暂停 (暂时停止执行)public static Thread currentThread()
: 返回对当前正在执行的线程对象的引用
翻阅 API 后得知创建线程的方式总共有两种, 一种是基础 Thread 类的方式, 一种是实现 Runnable 接口方式. 方式一我们前面已经完成, 接下来讲解方式二的实现方式.
Runnable 实现线程
采用java.lang.Runnable
也是非常常见的一种, 我们只需要重写 run 方法即可.
步骤如下:
- 定义 Runnable 接口的实现类, 并重写接口的 run() 方法. 该 run() 方法的方法体同样是该线程的线程执行体
- 创建 Runnable 实现类的实例, 并以此实例作为 Thread 的 target 来创建 Thread 对象. 该实例对象才是真正的线程对象
- 调用线程对象的 start() 方法来启动线程
代码如下:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
public class Test {
public static void main(String[] args) {
// 创建自定义类对象, 线程任务对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t = new Thread(mr, "小白");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("啦啦啦啦:" + i);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
通过实现 Runnable 接口, 使得该类有了很多线程类的特征. run() 方法是多线程程序的一个执行目标. 所有的多线程代码都在 run 方法里面. Thread 实际上也是实现了 Runnable 接口的类.
在启动的多线程的时候, 需要先通过 Thread 类的构造方法 Thread(Runnable target) 构造出对象, 然后调用 Thread 对象的 start() 方法来运行多线程代码.
实际上所有多线程代码都是通过运行 Thread 的 start() 方法来运行的. 因此, 不管是继承 Thread 类还是实现 Runnable 接口来实现多线程, 最终还是通过 Thread 的对象的 API 来控制线程的. 属性 Thread 类的 API 是进行多线程编程的基础.
注: Runnable 对象仅仅作为 Thread 对象的 target, Runnable 实现类里包含的 run() 方法仅作为线程执行体. 而实际的线程对象依然是 Thread 实例, 只是该 Thread 线程负责执行其 target 的 run() 方法.
Thread 和 Runnable 的区别
如果一个类继承 Thread, 则不合适资源共享. 但是如果实现了 Runnable 接口的话, 则很容易的实现资源共享.
实现 Runnable 接口比继承 Thread 类所具有的优势:
- 适合多个相同的程序代码的线程去共享一个资源
- 可以避免 java 中的单继承的局限性
- 线程池只能放入实现 Runnable 或 Callable 类线程. 不能直接放入继承 Thread 的类.
注: 在 java 中, 每次程序运行至少启动 2 个线程. 一个是 main 线程, 一个是垃圾收集线程. 因为每当使用 java 命令执行一个类的时候, 实际上都会启动一个 JVM. 每一个 JVM 其实在就是在操作系统中启动一个进程.
匿名内部类方式实现线程的创建
使用线程的匿名内部类方式, 可以方便的实现每个线程执行不同的线程任务操作.
使用匿名内部类的方式实现 Runnable 接口, 重写 Runnable 接口中的 run 方法:
public class Test {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("子线程: " + i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程: " + i);
}
}
}
输出结果:
主线程: 0
主线程: 1
主线程: 2
主线程: 3
主线程: 4
主线程: 5
主线程: 6
主线程: 7
主线程: 8
主线程: 9
主线程: 10
主线程: 11
主线程: 12
主线程: 13
主线程: 14
主线程: 15
主线程: 16
主线程: 17
主线程: 18
主线程: 19
子线程: 0
子线程: 1
子线程: 2
子线程: 3
子线程: 4
子线程: 5
子线程: 6
子线程: 7
子线程: 8
子线程: 9
子线程: 10
子线程: 11
子线程: 12
子线程: 13
子线程: 14
子线程: 15
子线程: 16
子线程: 17
子线程: 18
子线程: 19
- 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
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
文章来源: iamarookie.blog.csdn.net,作者:我是小白呀,版权归原作者所有,如需转载,请联系作者。
原文链接:iamarookie.blog.csdn.net/article/details/111413421
- 点赞
- 收藏
- 关注作者
评论(0)