线程的创建

继承Thread类

  • 编写程序,开启一个线程,该线程每隔1秒。 在控制台输出“喵喵, 我是小猫咪”
  • 对上题改进:当输出8 次喵喵,我是小猫咪,结束该线程

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
package com.threaduse;

public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start(); //启动线程
//当main线程启动一个子线程时,主线程不会阻塞,会继续执行。
System.out.println(Thread.currentThread().getName());
for (int i = 0; i < 8; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程执行");
}
}
}

//当一个类继承了Thread类,该类就可以当成线程用
//run() 方法是Runable接口里的方法
class Cat extends Thread {

@Override
public void run() { //重写run方法
int time = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("喵喵, 我是小猫咪" + (++time) + " " + Thread.currentThread().getName());
if (time == 8) {
break;
}
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main
主线程执行
喵喵, 我是小猫咪1 Thread-0
主线程执行
喵喵, 我是小猫咪2 Thread-0
主线程执行
喵喵, 我是小猫咪3 Thread-0
喵喵, 我是小猫咪4 Thread-0
主线程执行
主线程执行
喵喵, 我是小猫咪5 Thread-0
主线程执行
喵喵, 我是小猫咪6 Thread-0
主线程执行
喵喵, 我是小猫咪7 Thread-0
主线程执行
喵喵, 我是小猫咪8 Thread-0

Process finished with exit code 0

cat.start()方法调用了ublic synchronized void start()方法,其中的start0()方法是本地方法由JVM调用。

真正实现多线程的是start0()

start()调用start0()方法后,该线程不一定会立刻执行,只是将线程变成了可运行状态。什么时候执行,取决于CPU什么时候调度。


实现Runnable接口

java是单继承的,所以某个类在继承了其他父类的情况下需要实现多线程可以继承接口。

  • 编写程序,该程序每隔一秒输出“hi”,输出10次自动退出。使用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
package com.threaduse;

/**
* 通过接口开发线程
*/
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{

@Override
public void run() {
int count = 0;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hi "+(++count)+""+Thread.currentThread().getName());
if(count==10){
break;
}
}
}
}

这种写法的启动方式和第一种不一样,Thread用了代理模式。

1
2
3
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();

用代码模拟一下这种模式,ThreadProxy模拟简化版的Thread

就是dog通过Thread代理启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ThreadProxy implements Runnable {
private Runnable target = null;

public ThreadProxy(Runnable target) {
this.target = target;
}

public void start() {
start0();
}

public void start0() {
run();
}

@Override
public void run() {
if (target != null) {
target.run();
}
}
}

多个子线程案例

编写一个程序创建两个线程,一个线程每隔1秒输出”hello,world” 10次退出。一个线程每隔1秒输出”hi” ,输出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
44
45
46
47
48
49
50
51
package com.threaduse;

public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
Thread thread1 = new Thread(t1);
T2 t2 = new T2();
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
}
}

class T1 implements Runnable {

@Override
public void run() {
int count = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello world");
if (++count == 10) {
break;
}
}
}
}

class T2 implements Runnable {

@Override
public void run() {
int count = 0;
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hi");
if (++count == 5) {
break;
}
}
}
}


通知线程退出

可以在主线程中修改循环条件控制子线程

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
package com.exit_;

public class ThreadExit_ {
public static void main(String[] args) {
T t = new T();
t.start();
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.setLoop(false);
}
}

class T extends Thread {
private boolean loop = true;

@Override
public void run() {
int count = 0;
while (loop) {
System.out.println(++count);
}
}

public void setLoop(boolean loop) {
this.loop = loop;
}
}

线程常用方法

序号 方法描述
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。

线程中断

演示interrupt()函数,可以中断线程休眠。捕获异常后,执行catch内语句。

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
package com.method;

public class ThreadMethod01 {
public static void main(String[] args) {
T t = new T();
t.setName("小明");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
//主线程打印5次hi,然后就打断子线程的休眠
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hi "+i);
}
t.interrupt();

}
}

class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "吃包子" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中");
Thread.sleep(20000);
} catch (InterruptedException e) {
//当该线程执行到一个interrput方法时,就会catch一个异常,可以加入自己的代码
//想让线程提前终止休眠,可以调用interrput方法
System.out.println(Thread.currentThread().getName() + "被interrupt了");
}

}
}
}


线程插队

yield():线程的礼让。让出CPU给其他线程,但礼让时间不确定,所以不一定礼让成功。

join():线程的插队。插队的线程一旦插队成功,就先执行完插入的线程的所有任务。


案例: main线程创建一个子线程,每隔1秒吃一个包子,吃10次,主线程隔1秒吃一个包子 ,吃10次。

要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。


join()实现

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
package com.method;

public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();

for (int i = 1; i <= 10; i++) {
Thread.sleep(1000);
System.out.println("主线程吃了" + i + "个包子");
if (i == 5) {
System.out.println("主线程让子线程先吃");
t2.join();
System.out.println("子线程吃完😋");
}
}
}
}

class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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
主线程吃了1个包子
子线程吃了1个包子
主线程吃了2个包子
子线程吃了2个包子
子线程吃了3个包子
主线程吃了3个包子
主线程吃了4个包子
子线程吃了4个包子
子线程吃了5个包子
主线程吃了5个包子
主线程让子线程先吃
子线程吃了6个包子
子线程吃了7个包子
子线程吃了8个包子
子线程吃了9个包子
子线程吃了10个包子
子线程吃完😋
主线程吃了6个包子
主线程吃了7个包子
主线程吃了8个包子
主线程吃了9个包子
主线程吃了10个包子

Process finished with exit code 0

yield

1
2
3
4
5
6
//main函数修改为
if (i == 5) {
System.out.println("主线程让子线程先吃");
Thread.yield();
System.out.println("子线程吃完😋");
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
主线程吃了1个包子
子线程吃了1个包子
主线程吃了2个包子
子线程吃了2个包子
主线程吃了3个包子
子线程吃了3个包子
主线程吃了4个包子
子线程吃了4个包子
主线程吃了5个包子
主线程让子线程先吃
子线程吃了5个包子
子线程吃完😋
主线程吃了6个包子
子线程吃了6个包子
子线程吃了7个包子
主线程吃了7个包子
子线程吃了8个包子
主线程吃了8个包子
主线程吃了9个包子
子线程吃了9个包子
主线程吃了10个包子
子线程吃了10个包子

Process finished with exit code 0

不一定会成功


守护线程

一般为工作线程(用户线程)服务,当所有的用户线程结束,守护线程自动结束

垃圾回收机制是常见的守护线程

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
package com.method;

public class ThreadMethod04 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
myDaemonThread.setDaemon(true);
myDaemonThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程");
Thread.sleep(1000);
}
}
}

class MyDaemonThread extends Thread {
@Override
public void run() {
for (; ; ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程");
}
}
}

这段代码实现了主线程退出后,子线程自动退出

myDaemonThread.setDaemon(true);,将线程变成守护进程

线程的生命周期

java.lang.Thread.State枚举类中定义了六种线程的状态,可以调用线程Thread中的getState()方法获取当前线程的状态

线程状态 解释
NEW 尚未启动的线程状态,即线程创建,还未调用start方法
RUNNABLE 就绪状态(调用start,等待调度)+正在运行
BLOCKED 等待监视器锁时,陷入阻塞状态
WAITING 等待状态的线程正在等待另一线程执行特定的操作(如notify)
TIMED_WAITING 具有指定等待时间的等待状态
TERMINATED 线程完成执行,终止状态

线程同步

在多线程编程中,一些敏感数据不允许被多个线程同时访问。采用同步访问技术,保证数据在任何时刻,最多只有一个线程访问,保证数据的完整性。

使用synchronized关键字上锁。

售票系统

模拟三个售票窗口,售票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
package com.syn;


/**
* 使用多线程模拟三个窗口同时售票
*/

public class SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}

class Ticket implements Runnable {
private static int ticketNum = 100;
private boolean loop = true;

public void sell() {
if (ticketNum <= 0) {
System.out.println("售票结束");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出一张,当前票数" +
+(--ticketNum));
}

@Override
public void run() {
while (loop) {
sell();
}
}
}

1
2
3
4
5
6
7
8
9
Thread-0售出一张,当前票数3
Thread-1售出一张,当前票数1
Thread-0售出一张,当前票数1
Thread-2售出一张,当前票数2
Thread-2售出一张,当前票数-1
Thread-0售出一张,当前票数0
售票结束
Thread-1售出一张,当前票数0
售票结束

不加synchronized关键字会出现多卖和重复卖的情况。给sell()方法加上synchronized关键字就可以解决。

改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14


public synchronized void sell() {
//同步方法
//这时锁在this对象
}
//也可以在代码块上写synchronized,互斥锁还是在this对象
//this也可以换成其他对象
public void sell(){
synchronized(this){
...//方法体
}
}


运行结果,问题解决。

1
2
3
4
5
6
7
Thread-2售出一张,当前票数3
Thread-2售出一张,当前票数2
Thread-2售出一张,当前票数1
Thread-2售出一张,当前票数0
售票结束
售票结束
售票结束

1
2
3
4
5
6
7
8
9
10
11
//同步方法(静态的)锁为当前类本身
1.public synchronized static void m1(){} 锁是加在方法所在的类上的
2.如果在静态方法中实现一个同步代码块:
class A implenments Runnable{
public synchronized static void m(){
synchronized (A.class){ //注意这里不能写this
System.out.println("m");
}
}
}

线程死锁

死锁是多个线程由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。如果无外力作用,永远无法推进下去。


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
package com.syn;



public class DeadLock {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
}
}
//线程

class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}

这段代码模拟了一个死锁,两个线程只能得到一个锁,程序会一直卡住


下面操作会释放锁:

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中执行了对象的wait()方法,当前线程暂停并释放锁

下面操作不会释放锁:

  • 当前线程同步代码块、同步方法中调用Thread.sleep(),Thread.yield()方法暂停线程不会释放锁
  • 线程执行同步代码块时,其他 线程调用了该线程的suspend()方法将改线程挂起不会释放锁

应尽量避免使用suspend()resume()来kong’zhi