Java多线程(一)-基础篇

基本概念

线程

  • 程序中单独顺序的控制流
  • 线程本身依靠程序进行运行
  • 线程是程序中的顺序控制流,只能用分配给程序的资源和环境

进程

  • 正在执行的程序
  • 一个进程可以包含一个或多个线程
  • 至少包含一个线程

单线程

  • 进程中只存在一个线程,实际上主方法就是一个主线程

多线程

  • 一个进程中运行多个线程
  • 目的:更好的使用CPU资源

并行

  • 真正的同时,多个cpu实例或者多台机器同时执行一段处理逻辑。

并发

  • 通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。使用TPS或者QPS来反应这个系统的并发处理能力

线程安全

  • 并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果

同步

  • 保证共享资源的多线程访问成为线程安全

死锁

  • 两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁
  • 结果就是这些线程都陷入了无限的等待中

线程的实现

继承java.lang.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
25
26
27
28
29
public class MyThread01 extends Thread{
// 自定义线程名称
private String name;
public MyThread01(String name){
this.name = name;
}

@Override
public void run() {
super.run();
for (int i = 0; i < 1000; i++) {
System.out.println(name+":"+i);
}
}

public static void main(String[] args) {
MyThread01 t1 = new MyThread01("A");
MyThread01 t2 = new MyThread01("B");
// 此时程序依然是顺序执行
//t1.run();
//t2.run();

// System.out.println("----------------华丽分割线----------------");

// 通过start()方法启动线程,此时t1、t2线程交替执行
t1.start();
t2.start();
}
}

结论

  1. 线程的启动是通过start()方法。通过对象调用run()方法程序依然是顺序执行。
  2. t1、t2线程交替执行,也就是并发执行。
  3. 查看Thread类的源码,可以发现Thread类是Runable接口的一个实现类。

实现Runable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyRunable02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}

public static void main(String[] args) {
MyRunable02 r1 = new MyRunable02();

Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);

// 通过start()方法启动线程,此时t1、t2线程交替执行
t1.start();
t2.start();
}
}

结论:由上一条第3点结论得出,结论同上。

实现Callable接口

  1. 这里先简单普及一下,在Java中Runnable的run()方法没有返回值,而Callable接口里的call()方法可返回值。
  2. Java常用Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类。
  3. Future类同时实现了Future、Runnable接口。
并发执行同一个FutureTask
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
public class MyCallable03 implements Callable<String> {
private int num = 5;
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum+=i;
System.out.println(Thread.currentThread().getName()+": "+sum);
}

// 虽然是并发执行,但是最终只会执行其中一个
if("Thread-0".equals(Thread.currentThread().getName())){
this.num = 0; // 此处this就是c1
System.out.println("now Thread: Thread-0");
}else if("Thread-1".equals(Thread.currentThread().getName())){
this.num=10; // 此处this就是c1
System.out.println("now Thread: Thread-1");
}
return Thread.currentThread().getName()+" result:"+sum;
}

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

MyCallable03 c1 = new MyCallable03();
FutureTask<String> f1 = new FutureTask<String>(c1);

// t1、t2并发执行
Thread t1 = new Thread(f1);
Thread t2 = new Thread(f1);

t1.start();
t2.start();

System.out.println(f1.isDone()); // f1执行完毕才是true

Thread.sleep(2000); // main线程sleep,保证t1、t2执行完毕
if(f1.isDone()){
System.out.println("结果: "+f1.get()); // 结果: Thread-1 result:45
System.out.println(t1.getState().toString()); // TERMINATED
System.out.println(t2.getState().toString()); // TERMINATED
}

System.out.println("num:"+c1.num); // 执行的t1为num:0,执行的t2为num:10
}
}

结论

  1. 由t1、t2的状态得出,两个线程都执行了。
  2. 根据num的值与输出的“now Thread: Thread-?”结果得出,一个FutureTask只会被执行一次。
先后执行同一个FutureTask
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
public class MyCallable0302 implements Callable<String> {

private static int num = 5;

@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum+=i;
System.out.println(Thread.currentThread().getName()+": "+sum);
}

// 最终只会执行其中一个
if(Thread.currentThread().getName().equals("Thread-0")){
this.num = 0; // 此处this就是c1
System.out.println("now Thread: Thread-0");
}else if("Thread-1".equals(Thread.currentThread().getName())){
this.num=10; // 此处this就是c1
System.out.println("now Thread: Thread-1");
}

return Thread.currentThread().getName()+" result:"+sum;
}

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

MyCallable0302 c1 = new MyCallable0302();
FutureTask<String> f1 = new FutureTask<String>(c1);

Thread t1 = new Thread(f1);

t1.start();

// 此时会继续执行主线程的代码。
System.out.println(f1.isDone()); // f1执行完毕才是true

Thread.sleep(2000); // main线程sleep,保证t1执行完毕
if(f1.isDone()){
System.out.println("第一次: "+f1.get()); // 第一次: Thread-0 result:45
System.out.println("num:"+c1.num); // num:0
System.out.println(t1.getState().toString()); // TERMINATED

Thread t2 = new Thread(f1);// 此时,f1中的call方法都不会被执行,相当于没传f1
t2.start();
Thread.sleep(2000); // main线程sleep,保证t2执行完毕
System.out.println("第二次: "+f1.get()); // 第二次: Thread-0 result:45(依然是t1执行f1的结果)
System.out.println(t2.getState().toString()); // TERMINATED
}

System.out.println("num:"+c1.num); // num:0 (依然是t1执行f1的结果)

// 第二个线程执行第一个线程已经执行完的f1,第二个线程会执行,但是不是执行f1中的call方法
}
}

结论

  1. 由两个“依然是t1执行f1的结果”进一步验证了:一个FutureTask只会被执行一次。

线程中常用方法

方法名作用详解
getName()获取线程名称Thread.currentThread().getName()
currentThread()获取当前线程对象Thread.currentThread()
isAlive()线程是否存活如果线程已经启动,并且没有died返回true
join()线程的强行运行在一个线程中调用other.join(),将等待other执行完后才继续本线程。(见补充说明)
sleep()线程的休眠sleep()方法是Thread类的静态方法,如果调用线程对象.sleep()方法并不是该线程就休眠,反正在哪一个线程里面执行了sleep()方法哪一个线程就休眠
yield()线程礼让当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)(见补充说明)
interrupte()友好的终止线程执行保证程序逻辑完整性(见补充说明)
wait()线程挂起,进入阻塞状态会释放对象锁,与notify()配套使用
notify()唤醒挂起线程与wait()配套使用,随机解除一个调用过wait()方法线程的阻塞状态
suspend()线程挂起,进入阻塞状态不会释放对象锁,不推荐使用,常与resume()配套使用
resume()唤醒挂起线程不推荐使用,常与suspend()配套使用。如果 resume() 操作出现在 suspend() 之前执行,很容易造成死锁

补充说明
join():不是预期效果,需进一步验证
yield():不是预期的效果,需进一步验证
wait():使线程停止运行,会释放对象锁

interrupte()
实质是设置中断标识值,正常运行的程序不去检测状态,就不会终止。
只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。
stop()
是一种”恶意” 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的。

综合interrupte()、stop(),建议使用自定义的标志位决定线程的执行情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SafeStopThread extends Thread {
//此变量必须加上volatile
private volatile boolean stop = true;
@Override
public void run() {
//判断线程体是否运行
while (stop) {
// Do Something
System.out.println("Stop");
}
}
//线程终止
public void terminate() {
stop = false;
}
}

线程优先级

Thread.MIN_PRIORITY => 1
Thread.MAX_PRIORITY=> 10
Thread.NORM_PRIORITY=> 5(默认)
说明
线程的优先级有可能影响线程的执行顺序,不是绝对的。

线程同步

有共享数据时就需要同步!!!

同步代码块

在代码块上加上”synchronized” 关键字,则此代码块就成为同步代码块

1
2
3
synchronized(同步对象){
需要同步的代码块;
}

同步方法

在方法返回修饰符之前加上”synchronized” 关键字,则此方法就成为同步方法

1
2
3
synchronized void 方法名(){
....
}

线程生命周期

  • 新建(New):新建一个线程对象

  • 可运行(Runable)
    其他线程调用该线程的start()方法。不能对同一线程对象两次调用start()方法。
    该线程位于可运行线程池中,等待获取cpu使用权。

  • 运行(running):获取了cpu使用权,执行程序代码

  • 阻塞(block):因某种原因[]放弃了cpu使用权,暂时停止运行。直到线程再次进入可运行状态才有可能获取cpu使用权,转为运行状态。

    • 等待阻塞:执行了wait()方法。jvm把线程放入等待队列,释放锁。被notify(), notifyAll进入锁池中
    • 同步阻塞:获取同步锁时,该锁被别的线程占用。jvm把线程放入锁池中
    • 其他阻塞:执行sleep(毫秒)、join方法、获取发出I/O请求。不释放锁
  • 死亡(dead):线程执行完成或因异常退出,该线程结束生命周期。死亡的线程不可再次恢复
    多线程

------------- 本文结束  感谢您的阅读 -------------
评论