JAVA之旅(十二)——Thread,run和start的特点,线程运行状态,获取线程对象和名称,多线程实例演示,使用Runnable接口
开始挑战一些难度了,线程和I/O方面的操作了,继续坚持
一.Thread
如何在自定义的代码中,自定义一个线程呢?
我们查看API文档,我们要启动一个线程,先实现一个子类,
1
2
3
4
5
6
7
8
9
10
11 1package com.lgl.hellojava;
2
3public class MyThread extends Thread {
4
5 @Override
6 public void run() {
7 System.out.println("我是一个线程");
8 }
9}
10
11
我们要调用它的话,只需要start就可以
1
2
3 1MyThread thread = new MyThread();
2thread.start();
3
这样就会执行我们的run方法
我们来理一下思路,线程有两种创建方式,我们先将第一种:
-
1.继承Thread 类
-
2.复写Thread类的run方法
-
3.调用线程的start方法
-
该方法有两个作用,启动线程和调用run方法
我们可以把代码改一下
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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7
8 MyThread thread = new MyThread();
9 thread.start();
10 for (int i = 0; i < 1140; i++) {
11 System.out.println("Hello JAVA");
12 }
13 }
14
15}
16
17class MyThread extends Thread {
18
19 @Override
20 public void run() {
21 for (int i = 0; i < 1140; i++) {
22 System.out.println("我是一个线程");
23 }
24 }
25}
26
27
这样输出多少?
你注意一下,他们交叉输出了,这种,就是多线成,两个线程同时在跑,都在获取cpu的使用权,cpu执行到谁,谁就执行,明确一点,在某一个时刻,只能有一个程序在运行(多核除外),cpu在做着快速的切换以达到看上去是同时运行的效果
我们可以形象的把多线成的运行,形容为在互相抢夺CPU的资源,那么这就是多线成的一个特性叫随机性,谁抢到谁执行,但是执行多长,cpu说了算
二.run和start的特点
为什么要覆盖run方法
Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法
也就是说Thread类汇总的run方法是用于存储线程执行的代码
目的:将自定义的代码存储在run方法中让线程运行
三.线程运行状态
线程运行会有几种状态,我们要了解他的状态,才能了解他运行的原理,我们先来看一张图
我们一步步来分析,首先线程的第一种状态是创建,你new一个线程就是被创建了,紧接着,就是运行的状态,他们的过程,就是start,当然,线程还有一种为冻结,处于某一种状态,就交冻结,他们通过sleep来交替。最后就是线程结束了,通过stop,当然,还有其他一些状态,比如阻塞,这是临时状态,这是具备运行资格,但是没有执行权
四.获取线程对象和名称
线程都是有名称的,通过格式Thread-编号来区分,我们可以这样来验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7
8 MyThread thread = new MyThread();
9 thread.start();
10
11 }
12
13}
14
15class MyThread extends Thread {
16
17 @Override
18 public void run() {
19
20 System.out.println("线程名称:"+this.getName());
21 }
22}
23
24
它输出的结果就是
他是从0开始,当然,他既然有getrName,那肯定有setName方法,其实他初始化的时候就有方法,父类已经给我们提供好了
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 1package com.lgl.hellojava;
2
3//公共的 类 类名
4public class HelloJJAVA {
5
6 public static void main(String[] args) {
7
8 MyThread thread = new MyThread("hello");
9 thread.start();
10
11 }
12
13}
14
15class MyThread extends Thread {
16
17 public MyThread(String name) {
18 super(name);
19 }
20
21 @Override
22 public void run() {
23
24 System.out.println("线程名称:"+this.getName());
25 }
26}
27
28
那么我们输出的结果
它还可以通过Thread.currentThread()来获取对象名称,它等同于this.getName();
五.多线程实例演示
我们来一个简单的实例来结束本篇blog,那就是卖票了,很多窗口都能卖票,这就是同时运行
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 1package com.lgl.hellojava;
2
3import javax.security.auth.callback.TextInputCallback;
4
5//公共的 类 类名
6public class HelloJJAVA {
7
8 public static void main(String[] args) {
9
10 /**
11 * 需求:简单的卖票程序,多个线程同时卖票
12 */
13 MyThread my1 = new MyThread();
14 MyThread my2 = new MyThread();
15 MyThread my3 = new MyThread();
16 MyThread my4 = new MyThread();
17
18 my1.start();
19 my2.start();
20 my3.start();
21 my4.start();
22 }
23
24}
25
26/**
27 * 卖票程序
28 *
29 * @author LGL
30 *
31 */
32class MyThread extends Thread {
33
34 // 票数
35 private int tick = 100;
36
37 @Override
38 public void run() {
39 while (true) {
40 if (tick > 0) {
41 System.out.println(currentThread().getName()+"卖票:" + tick--);
42 }
43 }
44 }
45}
46
47
我们这样就实现了票卖了,但是这里出了一个问题,四个线程,他一共卖了400张票,那可不行,火车就一百张票,这是不符合规则的,我们需要怎么改?让四个对象共享一个票数,那我们就需要静态了
1
2
3 1// 票数
2private static int tick = 100;
3
但是我们一般不定义静态,他的生命周期有点长,我们换一种角度考虑,其实这就关乎到创建方法了,我们在之前就讲个,线程创建有两种方法。
六.Runnable接口
我们需要使用第二种方法,所以是这样写的,实现Runnable的接口
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 1package com.lgl.hellojava;
2
3import javax.security.auth.callback.TextInputCallback;
4
5//公共的 类 类名
6public class HelloJJAVA {
7
8 public static void main(String[] args) {
9
10 /**
11 * 需求:简单的卖票程序,多个线程同时卖票
12 */
13 MyThread myThread = new MyThread();
14 Thread t1 = new Thread(myThread);
15 Thread t2 = new Thread(myThread);
16 Thread t3 = new Thread(myThread);
17 Thread t4 = new Thread(myThread);
18
19 t1.start();
20 t2.start();
21 t3.start();
22 t4.start();
23 }
24
25}
26
27/**
28 * 卖票程序
29 *
30 * @author LGL
31 *
32 */
33class MyThread implements Runnable {
34
35 // 票数
36 private int tick = 100;
37
38 @Override
39 public void run() {
40 while (true) {
41 if (tick > 0) {
42 System.out.println("卖票:" + tick--);
43 }
44 }
45 }
46}
47
48
我们得到的输出结果就正确了
创建线程的第二种方式,实现Runnable接口
-
1.定义类实现Runnable接口
-
2.覆盖Runnable接口的run方法
-
将线程要运行的代码存放在该run方法中
-
3.通过Thread类建立线程对象
-
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
-
为什么要将Runnable接口的子类对象传递给Thread的构造函数,因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属的对象
-
5.调用Thread类的start方法开启线程并调用Runnable接口的run方法
这两种方式有什么区别呢?
- 实现方式好处,避免了单继承的局限性,在定义线程时,建议使用实现方式
- 线程代码存放的位置不一样