Java多线程入门1

Java多线程入门(1)

假如你没有一点Java多线程的基础,可以花10分钟看看我之前在知乎写的关于多线程的知识点:多线程简单入门

线程概述

多线程,简单来说就是多个线程(哈哈哈哈哈啊)。当我们使用计算的时候,可以同时打开QQ音乐和QQ和微信。这几个程序可以理解为多个进程,而一个进程又可以存在多个线程。实际应用中,对线程是非常游泳的,一个浏览器必须能同时下载多个照片;一个web服务器必须能同时响应多个用户请求。

线程的创建和启动

继承Thread类创建线程类

直接继承Thread,然后调用线程对象的start()方法来启动该线程。

直接上代码

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
//通过继承实现多线程
public class FirstThread extends Thread{
private int i;

//重写run方法,run方法就是线程执行体
public void run() {
for(;i<100;i++) {

//当线程类继承Thread类时,直接用this即可获取当前线程
//Thread对象的getName返回当前线程的名字
//因此可以直接调用getName方法返回当前线程的名字
System.out.println(getName()+":"+i);
}
}

public static void main(String[] args) {
for(int i =0;i<100;i++) {

//调用Thread的currentThread方法获取当前线程
System.out.println(Thread.currentThread().getName()+":"+i);
if(i == 20) {

//创建并启动第一个线程
new FirstThread().start();

//创建并启动第二个线程
new FirstThread().start();
}
}
}

}

实现Runnable接口创建线程类

定义Runnable接口得实现类,并重写该接口的run方法,这个方法就是该线程的线程执行体,最后还是调用线程对象的start方法来开启线程。注意实现Runnable的类本身并不能调用start方法,而是需要new Thread(Runable runable)来开启。使用同一个接口开启的线程会共享这个接口中的变量。

下面程序中可以看到线程1和线程2的 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
31
32
33
//通过接口实现多线程
public class SecondThread implements Runnable{
private int i;

//run方法同样是线程执行体
@Override
public void run() {

for(;i<100;i++) {

//当线程类实现Runable接口时
//如果想获取当前线程,只能用Thread.currentThread方法
System.out.println(Thread.currentThread().getName()+":"+i);
}

}

public static void main(String[] args) {

for(int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);

if(i == 20) {
SecondThread st = new SecondThread();
SecondThread st1 = new SecondThread();

//通过new Thread(target,name)来创建新线程
new Thread(st,"新线程1").start();
new Thread(st1,"新线程2").start();
}
}
}
}

使用Callable接口来创建线程

这个类提供了call方法来作为线程执行体,但是call方法可以有返回值,且可以声明抛出异常。不过Callable不是Runnable接口的子接口,所以不能用上面的方法启动。Java5提供了Future接口来代表Callable接口的返回值,FutureTask实现类实现了Future接口,并实现了Runable接口,所以可以用这个实现类作为Thread类的内容。

该接口所提供的几个方法:

方法 描述
boolean cancel( boolean mayInterruptIfRunning) 试图取消该 Future 里关联的 Callable 任务。
V get() 返回 Callable 任务里 call() 方法的返回值。调用该方法将导致程序阻塞, 必须等到子线程结束后才会得到返回值。
V get( long timeout, TimeUnit unit) 返回 Callable 任务里 call() 方法的返回值。 该方法让程序最多阻塞 timeout 和 unit 指定的时间,如果经过指定时间后 Callable 任务依然没有返回值,将会抛出 TimeoutException 异常。
boolean isCancelled() 如果在 Callable 任务正常完成前被取消,则返回 true。
boolean isDone() 如果 Callable 任务已完成,则返回 true。

所以使用Callable来创建多线程的步骤如下

  1. 创建实现Callable接口的类并实现call()方法,该方法作为线程执行体。
  2. 使用FutureTask对象封装Callable对象的call()方法的返回值
  3. 使用FutureTask多为Thread对象的target创建并启动新线程
  4. 使用FutureTask的get()方法来获得返回值
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
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThirdThread {
public static void main(String[] args) {

//创建Callable对象
ThirdThread rt = new ThirdThread();

//使用Lambda表达式创建Callable<Integer>对象
//使用FutureTask来包装Callable对象
FutureTask<Integer> task = new FutureTask<>((Callable<Integer>)()->{
int i = 0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
}

//call方法有返回值
return i;
});

for(int i = 0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
if(i==20) {

//实质还是以Callable对象来创建并启动线程
new Thread(task,"有返回值的线程").start();
}
}
try {
//获取线程返回值
System.out.println("子线程的返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

三种创建方法的对比

  • 由于Java是单继承的,所以一个类继承了Thread就无法再继承其他类了,不过书写比较简单,如果需要访问当前线程,直接使用this就可以获得。
  • 线程只是实现了Runable接口或Callable接口,还可以继承其他类,在这种情况下,多个线程可以共享同一份资源。不过访问当前线程的话需要使用Thread.cuuentThread()方法。

综上所述:尽量使用接口创建。

线程的声明周期

线程的各种状态

本篇在知乎中写的还是比较详细的了:多线程简单入门,我就丢几个demo吧

新建和就绪状态

使用start()方法来把线程改变为就绪状态,只能对处于新建状态的线程调用该方法,否则就会引发IllegalThreadStateException异常。而且不是调用Run方法,是调用start方法。

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
public class InvokeRun extends Thread{
private int i;

//重写run方法
public void run() {
for(;i<100;i++) {

//直接调用Run方法时,Thread的this.getName返回的是该对象的名字
//而不是当前线程的名字
//使用Thread.currentThread的getName总是获取当前线程的名字
System.out.println(Thread.currentThread().getName()+":"+i);

}
}

public static void main(String[] args) {
for(int i = 0;i<100;i++) {

//调用Thread的currentThread方法来获取当前线程
System.out.println(Thread.currentThread().getName()+":"+i);
if(i == 20) {

//直接调用对象run方法,系统会把run当成普通方法
//只能对处于新建状态的线程调用 start() 方法, 否则将引发 IllegalThreadStateException 异常。
new InvokeRun().run();
new InvokeRun().run();
}
}
}
}

运行和阻塞状态

运行和阻塞状态

线程死亡

run或者call方法执行完成后,线程正常结束,或者线程抛出一个未捕获的异常,直接调用线程的stop()方法,但是不推荐。可能造成死锁。而且不能对已经死亡的线程重写调用start方法

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
public class StartDead extends Thread{
private int i;

//重写run方法
public void run() {
for(;i<100;i++) {

System.out.println(getName()+":"+i);

}
}

public static void main(String[] args) {

StartDead sd = new StartDead();
for(int i = 0;i<300;i++) {

//调用Thread的currentThread方法来获取当前线程
System.out.println(Thread.currentThread().getName()+":"+i);
if(i == 20) {

//启动线程
sd.start();

//判断启动后线程的isAlive值,输出true
System.out.println(sd.isAlive());
}

if(i>20&&!sd.isAlive()) {

//试图再次启动该线程
sd.start();
}
}
}
}

控制线程

join方法

当 在某 个 程序 执行 流 中 调用 其他 线程 的 join() 方法 时,调用线程将被阻塞,直到被 join() 方法加入的 join 线程执行完为止。join方法有三种重载形式

  • join():等待被执行完
  • join(long millis):最长执行millis毫秒
  • join(long millis,int nanos):最长等待mills毫秒加nanos毫微秒
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
public class JoinThread extends Thread {

//提供一个有参数的构造器,用于设置该线程的名字
public JoinThread(String name) {
super(name);
}

//重写run方法
public void run() {
for(int i=0;i<100;i++) {

System.out.println(getName()+":"+i);

}
}

public static void main(String[] args) throws InterruptedException {

new JoinThread("新线程").start();
for(int i = 0;i<100;i++) {
if(i == 20) {
JoinThread jt = new JoinThread("被join的线程");
jt.start();

//main线程调用了jt线程的join方法,main线程必须等jt执行结束才会向下执行
jt.join();
}

System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

后台线程

JVM的垃圾回收线程就是典型的后台线程。如果所有的前台线程都死亡,则后台线程会自动死亡。必须在线程启动之前就设置为后台线程,否则会引发IllegalThreadStateException 异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DaemonThread extends Thread {
//重写run方法
public void run() {
for(int i=0;i<100;i++) {

System.out.println(getName()+":"+i);

}
}

public static void main(String[] args) throws InterruptedException {

DaemonThread t = new DaemonThread();

//将此线程设置为守护线程
t.setDaemon(true);
t.start();
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}

//----程序执行到此处,main线程结束,后台线程也结束
}
}

线程睡眠

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态。sleep方法有两种重载方法:

  • static void sleep(long millis)暂停millis毫秒
  • static void sleep(long millis,int nanos)暂停millis毫秒+nanos毫微秒
1
2
3
4
5
6
7
public class SleepTest{
public static void main(String[] args) throws Exception{
for(int i =0;i<10;i++){
Thread.sleep(1000);
}
}
}

改变线程优先级

Thread 类提供了 setPriority( int newPriority)、 getPriority() 方法来设置和返回指定线程的优先级,其中 setPriority() 方法的参数可以是一个整数,范围是 1 ~ 10 之间,也可以使用 Thread 类的如下三个静态常量。

  • MAX_ PRIORITY: 其值是 10。
  • MIN_ PRIORITY: 其值是 1。
  • NORM_ PRIORITY: 其值是 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class Improtant {
public static void main(String[] args) {
//定义多个线程
//线程1
Thread t1=new Thread(()->{
System.out.println("线程1");
});
t1.start();
//设置优先级最低
t1.setPriority(1);
//设置线程的名字
t1.setName("线程1");
System.out.println("线程名称:" + t1.getName());
System.out.println("线程的状态:" + t1.getState());
System.out.println("判断线程是否被激活:" + t1.isAlive());
System.out.println("当前正在执行的线程:" + Thread.currentThread());
//线程休眠,单位是毫秒
try {
t1.sleep(5000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}

//线程2
Thread t2=new Thread(()->{
System.out.println("线程2");
});
t2.start();
//设置优先级最高
t2.setPriority(10);

//线程3
Thread t3=new Thread(()->{
System.out.println("线程3");
});
t3.start();
//优先级默认为5
t3.setPriority(5);
//抢占CPU使用
try {
t3.join();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
//线程4
Thread t4=new Thread(()->{
System.out.println("线程4");
});
t4.start();
//移交运行权
t4.yield();

//线程5
Thread t5=new Thread(()->{
System.out.println("线程5");
});
t5.start();

//线程6
Thread t6=new Thread(()->{
System.out.println("线程6");
});
t6.start();

//设置优先级不一定会让线程按照顺序执行,只是增加了线程被选中的概率
//查看该线程的优先级
System.out.println(t1.getPriority());
//查看线程的id
System.out.println(t1.getId());
//查看线程的名字,线程默认名字Thread-编号(12345)
System.out.println(t2.getName());

//关闭线程
t1.interrupt();//t1.stop(); t1.destroy();
//判断线程是否被中断
System.out.println(t1.interrupted());
System.out.println("线程的状态:" + t1.getState());

}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!