java线程基本使用
java 线程基本使用
创建线程的两种方式
继承 Thread 类,重写 run 方法
1 | package com.hspedu.threaduse; |
实现 Runnable 接口,重写 run 方法
1 | package com.hspedu.threaduse; |
继承 Thread vs 实现 Runnable 的区别
-
从 java 的设计来看,通过击沉 Thread 或者实现 Runnable 接口来创建线程本质上没有区别,从 jdk 帮助文档我们可以看到 Thread 类本身就实现了 Runnable 接口 start() => start0() 。
-
实现 Runnable 接口方式更加适合多个线程共享一个资源,并且避免了单继承的限制。
-
[售票系统],编程模拟三个售票窗口售票 100,分别使用 继承 Thread 和实现 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74package com.hspedu.ticket;
/**
* 使用多线程,模拟三个窗口同时售票
*/
public class SellTicket {
public static void main(String[] args) {
// 测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// sellTicket01.start();
// sellTicket02.start();
// sellTicket03.start();
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第1个线程-窗口
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口
}
}
class SellTicket01 extends Thread{
private static int ticketNum = 100; //让多个线程共享 ticketNum
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
try {
//休眠50毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"+Thread.currentThread().getName() + "售出一张票" + "剩余票数="+(--ticketNum));
}
}
}
class SellTicket02 extends Thread{
private static int ticketNum = 100; //让多个线程共享 ticketNum
public void run() {
while (true){
if (ticketNum <= 0){
System.out.println("售票结束...");
break;
}
try {
//休眠50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"+Thread.currentThread().getName() + "售出一张票" + "剩余票数="+(--ticketNum));
}
}
}
线程终止
-
当线程完成任务后,会自动退出。
-
还可以通过**使用变量来控制 un 方法退出的方式停止线程,即通知方式**
案例:启动一个线程 t,要求在 main 线程中去停止线程 t。
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
41package com.hspedu.exit;
import javax.swing.plaf.TableHeaderUI;
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
new Thread(t1).start();
//如果希望main线程去控制t1 线程的终止,必须可以修改 loop
//让t1 退出run方法,从而终止 t1线程 -> 通知方式
//让主线程休眠 10 秒,再通知 t1线程退出
System.out.println("main线程休眠10s");
Thread.sleep(10*1000);
t1.setLoop(false);
}
}
class T extends Thread{
private int count = 0;
private boolean loop = true;
public void run() {
while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hi~"+(++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用方法
常用方法第一组
-
setName: 设置线程名称,使之与参数 name 相同
-
getName:返回该线程的名称
-
start: 使该线程开始执行;java 虚拟机底层调用该线程的 start0 方法
-
run: 调用线程对象的 run 方法;
-
setPriority: 更改线程的优先级
-
getPriority: 获取线程的优先级
-
sleep: 在指定的毫秒数内让当前执行的线程休眠(暂停执行)
-
interrupt: 中断线程
1 | package com.hspedu.method; |
常用方法第二组
-
yield: 线程的礼让。让出 cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。
-
join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
案例:main 线程创建一个子线程,每隔 1s 输出 hello,输出 20 次,主线程每隔 1 秒,输出 hi,输出 20 次。要求:两个线程同时执行,当主线程输出 5 次后,就让子线程运行完毕,主线程再继续。
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
44package com.hspedu.method;
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程唠嗑 "+i+"次");
if(i == 5){
System.out.println("主线程礼让中ing~~~");
// System.out.println("让子线程插队先执行~");
// join,线程插队
// t2.join();
// System.out.println("插队完毕,主线程继续执行");
Thread.yield();
System.out.println("礼让线程,主线程继续执行");
}
}
}
}
class T2 extends Thread{
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程运行唠嗑 "+i+"次");
}
}
}
线程常用方法
-
用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
-
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
案例:
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
31package com.hspedu.method;
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 1; i <= 10; i++) {//main线程
System.out.println("主线程在工作中ing~" + i);
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
public void run() {
for (; ; ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程死循环中ing~");
}
}
}
-
线程的生命周期
-
JDK 中用 Thread.State 枚举表示了线程的几种状态
public static enum Thread.State
extends Enum<Thread.State>
线程状态,线程可以处于以下状态之一
-
NEW
尚未启动的线程处于此状态。
-
RUNNABLE
在 Java 虚拟机中执行的线程处于此状态
-
BLOCKED
被阻塞等待监视器锁定的线程处于此状态
-
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
-
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
-
TERMINATED
已退出的线程处于此状态
-
-
线程状态转换图
-
查看线程状态代码
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
33package com.hspedu.state_;
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + " 状态 " + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class T extends Thread {
public void run() {
while (true) {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hi~" + i);
}
break;
}
}
}
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
-
同步具体方法 - Synchronized
-
同步代码块
1
2
3
4synchronized(对象){ //得到对象的锁,才能操作同步代码
// 需要被同步代码
} -
Synchronized 还可以放在方法声明中,表示整个方法-为同步方法
1
2
3
4public synchronized void m (String name){
// 需要被同步的代码
} -
使用 synchronized 解决售票问题
package com.hspedu.syn; /** * 使用多线程,模拟三个窗口同时售票 */ public class SellTicket { public static void main(String[] args) { SellTicket03 sellTicket03 = new SellTicket03(); new Thread(sellTicket03).start();//第1个线程-窗口 new Thread(sellTicket03).start();//第2个线程-窗口 new Thread(sellTicket03).start();//第3个线程-窗口 } } //实现接口方式,使用synchronized实现线程同步 class SellTicket03 extends Thread { private static int ticketNum = 100; //让多个线程共享 ticketNum private boolean loop = true; //控制run方法变量 public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行run方法 if (ticketNum <= 0) { System.out.println("售票结束..."); loop = false; return ; } try { //休眠50毫秒 Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数=" + (--ticketNum)); } @Override public void run() { while (loop) { sell();//sell方法是一个同步方法 } } }
-