Featured image of post java并发编程基础

java并发编程基础

什么是线程?

在说线程之前,就要先说一下什么是进程,因为线程是进程中的一个实体,线程并不会独立而存在的。进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程是进程的一个执行路径,一个进程中至少含有一个线程,进程中的多个线程是共享进程的资源。

操作系统把CPU资源分配给进程,但是真正占用CPU的线程,所以才会说线程是CPU分配的基本单位。在我们启动java中的main函数时,就是启动了一个进程,而main函数所在的线程就是这个进程中的一个线程,这个线程也被称为主线程。

Java中线程的状态和线程的生命周期

thread-state

新建状态:新创建一个线程对象之后,还没有对用start()方法

1
Thread thread = new Thread(new MyThread());//新建状态

就绪状态:线程对象在调用start()之后进入就绪状态,等待CPU调度

1
thread.start();

运行状态:线程得到CPU调度,获得CPU的执行时间

阻塞状态:遇到需要获得锁的代码块,而线程没能获得锁,进入阻塞状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
synchronized{
···
}
or
ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }

等待状态:调用了wait()方法,线程进入等待时间。而且会释放当前的锁,需要唤醒才能重新进入就绪状态(notify() or notifyAll()),等待CPU调度

超时等待状态:在调用sleep(time)之后进入超时等待状态,在time时间内过后会再次运行,并且在等待的过程中并不会释放锁。使用不当可能会造成事故

终止状态:线程正常执行完毕(run()方法执行完)或者主线程的main()方法执行完毕,线程进入终止状态

注意:

wait()和notify()/notifyAll()需要在synchronized代码块内使用

notify()/notifyAll()是随机唤醒

await()和signal()/signalAll()需要在lock内执行

signal()可以做到精确唤醒

线程的创建

继承Thread类,重写run()方法

因为Thread的构造参数是要求传入Runnable类型,所以可以传入继承了Thread的类,因为Thread就已经实现Runnable接口,只要自定义的类再去重写run()方法即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class CreateThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("thread run by extends Thread");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new CreateThread1());
        thread.start();
    }
}

实现Runnable接口

也可以传入一个实现了Runnable接口的类,重写完run()方法之后便可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class CreateThread2 implements Runnable {

    @Override
    public void run() {
        System.out.println("Thread run by implements Runnable");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new CreateThread2());
        thread.start();
    }
}

Runnable接口匿名内部类

更加直接的就是直接使用匿名内部类,有比较传统的写法和jdk1.8之后的lambda写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class CreateThread3 {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread run by inner class");
            }
        }).start();
        //lambda写法
        new Thread(()-> System.out.println("Thread run by inner class")).start();
    }
}

如何正确开始线程

在上面的线程的创建看出来,线程运行了,会去执行run方法。那么看一下下面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Main {
    public static void main(String[] args) {
        Runnable runnable = ()->{
            System.out.println(Thread.currentThread().getName());
        };
        runnable.run();
        new Thread(runnable).start();
    }
}

输出结果
main
Thread-0

从输出结果可以看出来,直接调用run方法,那只是属于方法调用。调用start方法,才是开始一个线程。当然来调用了start方式并不是意味着线程就会直接运行,而是等待CPU的调度。注意:thread不能调用两次start方法,不然会报错。因为调用了start方法之后,会检查线程的状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
		// start方法的第一行代码
		/**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

正确停止线程

原理介绍:使用interrupt来通知,而不是强制。

普通情况下停止线程:run方法内没有sleep或wait方法时,停止线程。下面可以通过调整Thread.sleep(2000),看控制台输出就可以看得出否是我们做出的停止。因为在调用interrupt()之后,控制台打印的数确实是少了,被我们中断成功。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class RightWayStopThreadWithoutSleep implements Runnable {

    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

run方法中有sleep,会出现异常:java.lang.InterruptedException: sleep interrupted,很明显是在线程休眠过程中,被中断就会抛出异常,这样我们就可以在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
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}

输出结果
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.msr.Main.lambda$main$0(Main.java:20)
	at java.lang.Thread.run(Thread.java:748)

在每次的迭代中都调用sleep方法,并不需要每次都去try/catch,直接try/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
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

输出结果
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.msr.Main.lambda$main$0(Main.java:19)
	at java.lang.Thread.run(Thread.java:748)

与上面的情况相反,每次迭代都去try/catch,sleep方法抛出的异常。从输出结果中可以看出,虽然抛出异常,但是线程依然在继续执行并没有被停止。原因是在抛出了InterruptedException异常之后,中断标志位就会被重置,所以while内的try/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
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

输出结果
0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.msr.Main.lambda$main$0(Main.java:19)
	at java.lang.Thread.run(Thread.java:748)
500是100的倍数
600是100的倍数
700是100的倍数
800是100的倍数

Process finished with exit code -1

解决方法:

  1. catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常,那么在run()就会强制try/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
43
public class Main implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                // 调用方法执行业务
                throwInMethod();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                //保存日志、停止程序
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    /**
    *  该方法是被调用的,是较低层次的方法,不应该自己try/catch处理异常,应该抛出异常让调用者处理
    **/
    private void throwInMethod() throws InterruptedException {
        // 业务逻辑
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Main());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

输出结果
go
保存日志
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.msr.Main.throwInMethod(Main.java:31)
	at com.msr.Main.run(Main.java:16)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

2.当不想或无法传递中断的时候:恢复中断,在catch子语句中调用Thread.currentThread().interrupt()

 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
public class Main implements Runnable {
    @Override
    public void run() {
        System.out.println("go");
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        // 自己处理中断
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // 如果不调用,中断信息就会被独吞,上层调用者就会无法感知
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Main());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

输出结果
go
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.msr.Main.reInterrupt(Main.java:24)
	at com.msr.Main.run(Main.java:18)
	at java.lang.Thread.run(Thread.java:748)
Interrupted程序运行结束

Thread和Object类中的重要方法

方法名 介绍
Thread sleep 线程休眠,不释放锁
join 等待其他线程执行完毕
yield 放弃已经获取到的CPU资源
currentThread 获取当前执行线程的引用
start,run 启动线程相关
interrupt 中断线程
stop(),suspend(),resuem() 已废弃
Object wait()/notify/notifyAll() 让线程暂时休息和唤醒

wait,notify,notifyAll函数

当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起来,直至发生一下的事情之一:

  • 其他线程调用该共享对象的notify()或者notifyAll()方法;
  • 其他线程调用该线程的interrupt()方法,该线程抛出InterruptedException异常返回。

如果调用wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程会抛出IllegalMonitorStateException异常。

如果一个线程可以从挂起状态变为可以运行的状态(也就是被唤醒),即时该线程没有被其他线程调用notify()notifyAll()方法进行通知,或者中断,或者等待超时,这就是虚假唤醒。虽然虚假唤醒在应用实践中很少发生,但是防范于未然,做法就是不停地区测试该线程被唤醒的条件是否满足,不满足则继续等待,也就是说在一个循环中调用wait()方法进行防范。退出的条件是满足唤醒该形成的条件。

1
2
3
4
5
synchronized(object){
    while(条件不满足){
		object.wait();
    }
}

预防虚假唤醒的用法就是最经典的生产者和消费者例子:

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
public class Main {
    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();
        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

class Producer implements Runnable {

    private EventStorage storage;

    public Producer(
            EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

class Consumer implements Runnable {

    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

class EventStorage {

    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }

    public synchronized void put() {
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("仓库里有了" + storage.size() + "个产品。");
        notify();
    }

    public synchronized void take() {
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
        notify();
    }
}

仓库里有了1个产品
仓库里有了2个产品
仓库里有了3个产品
仓库里有了4个产品
仓库里有了5个产品
仓库里有了6个产品
仓库里有了7个产品
仓库里有了8个产品
仓库里有了9个产品
仓库里有了10个产品
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下9
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下8
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下7
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下6
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下5
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下4
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下3
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下2
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下1
拿到了Tue Nov 24 19:52:33 SGT 2020现在仓库还剩下0
....

Process finished with exit code 0

在如上代码中假如生产者线程A首先通过synchronized 获取到了queue上的锁,那么后续所有企图生产元素的线程和消费线程将会在获取该监视器锁的地方被阻塞挂起。线程A获取锁后发现当前队列己满会调用queue.wait()方法阻塞自己,然后释放获取的queue上的锁,这里考虑下为何要释放该锁?如果不释放,由于其他生产者线程和所有消费者线 程都己经被阻塞挂起,而线程A也被挂起,这就处于了死锁状态。这里线程A 挂起自己后释放共享变量上的锁,就是为了打破死锁必要条件之一的持有并等待原则。关于死锁后面的章节会讲。线程A释放锁后,其他生产者线程和所有消费者线程中会有一个线程获 取queue上的锁进而进入同步块,这就打破了死锁状态。

使用synchronized 实现线程交替执行

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
public class Main {
    private static volatile int count = 0;
    private static Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            while (count < 100) {
                synchronized (lock) {
                    if ((count & 1) == 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + count++);
                    }
                }
            }
        }, "ThreadA").start();
        new Thread(() -> {
            while (count < 100) {
                synchronized (lock) {
                    if ((count & 1) == 1) {
                        System.out.println(Thread.currentThread().getName() + ":" + count++);
                    }
                }
            }
        }, "ThreadB").start();
    }
}

public class Main {

    private static int count;

    private static final Object lock = new Object();

    //新建2个线程
    //1个只处理偶数,第二个只处理奇数(用位运算)
    //用synchronized来通信
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100) {
                    synchronized (lock) {
                        if ((count & 1) == 0) {
                            System.out.println(Thread.currentThread().getName() + ":" + count++);
                        }
                    }
                }
            }
        }, "偶数").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100) {
                    synchronized (lock) {
                        if ((count & 1) == 1) {
                            System.out.println(Thread.currentThread().getName() + ":" + count++);
                        }
                    }
                }
            }
        }, "奇数").start();
    }
}

输出结果
ThreadA:0
ThreadB:1
ThreadA:2
ThreadB:3
ThreadA:4
ThreadB:5
ThreadA:6
ThreadB:7
ThreadA:8
ThreadB:9
ThreadA:10
ThreadB:11
ThreadA:12
ThreadB:13
ThreadA:14
ThreadB:15
ThreadA:16
ThreadB:17
ThreadA:18
ThreadB:19
ThreadA:20
ThreadB:21
ThreadA:22
ThreadB:23
ThreadA:24
ThreadB:25
ThreadA:26
ThreadB:27
ThreadA:28
ThreadB:29
ThreadA:30
ThreadB:31
ThreadA:32
ThreadB:33
ThreadA:34
ThreadB:35
ThreadA:36
ThreadB:37
ThreadA:38
ThreadB:39
ThreadA:40
ThreadB:41
ThreadA:42
ThreadB:43
ThreadA:44
ThreadB:45
ThreadA:46
ThreadB:47
ThreadA:48
ThreadB:49
ThreadA:50
ThreadB:51
ThreadA:52
ThreadB:53
ThreadA:54
ThreadB:55
ThreadA:56
ThreadB:57
ThreadA:58
ThreadB:59
ThreadA:60
ThreadB:61
ThreadA:62
ThreadB:63
ThreadA:64
ThreadB:65
ThreadA:66
ThreadB:67
ThreadA:68
ThreadB:69
ThreadA:70
ThreadB:71
ThreadA:72
ThreadB:73
ThreadA:74
ThreadB:75
ThreadA:76
ThreadB:77
ThreadA:78
ThreadB:79
ThreadA:80
ThreadB:81
ThreadA:82
ThreadB:83
ThreadA:84
ThreadB:85
ThreadA:86
ThreadB:87
ThreadA:88
ThreadB:89
ThreadA:90
ThreadB:91
ThreadA:92
ThreadB:93
ThreadA:94
ThreadB:95
ThreadA:96
ThreadB:97
ThreadA:98
ThreadB:99
ThreadA:100

Process finished with exit code 0

线程的各个属性

属性名称 用途
编号(ID) 每个线程有自己的ID,用于标识不同的线程
名称(Name) 让用户或开发者在开发、调试或运行过程中,更容易区分每个不同的线程、定位问题等。
是否是守护线程(isDaemon) true代表该线程是【守护线程】,false代表【非守护线程】也就是用户线程
优先级(Priority) 优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行、哪些少运行

未捕获异常如何处理

1.主线程可以轻松发现异常,子线程却不行

 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 Main implements Runnable {

    public static void main(String[] args) {
        new Thread(new Main()).start();
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }

    @Override
    public void run() {
        throw new RuntimeException();
    }
}
运行结果
0
1
2
3
4
5
6
7
8
9
Exception in thread "Thread-0" java.lang.RuntimeException
	at com.msr.study.concurrent.disruptor.Application.run(Application.java:19)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

以上的程序运行之后,虽然子线程抛出了,异常信息。但主线程丝毫不受影响。因为子线程的异常无法用传统方法捕获,即无法通过try/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
public class Application implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        try {
            new Thread(new Application(), "MyThread-1").start();
            Thread.sleep(300);
            new Thread(new Application(), "MyThread-2").start();
            Thread.sleep(300);
            new Thread(new Application(), "MyThread-3").start();
            Thread.sleep(300);
            new Thread(new Application(), "MyThread-4").start();
        } catch (RuntimeException e) {
            System.out.println(Thread.currentThread().getName()+" Caught Exception.");
        }

    }

    @Override
    public void run() {
        try {
            throw new RuntimeException();
        } catch (RuntimeException e) {
            System.out.println(Thread.currentThread().getName()+" Caught Exception.");
        }
    }
}

输出结果
MyThread-1 Caught Exception.
MyThread-2 Caught Exception.
MyThread-3 Caught Exception.
MyThread-4 Caught Exception.

第一个是如上面的代码那样,在每一个的run方法中都用try/catch进行处理,但是这会很麻烦。第二个方法也是最推荐的方法。利用UncaughtExceptionHandler。看下面的示例代码

 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
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

    private String name;

    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING, "线程异常终止" + t.getName());
        System.out.println(name + "捕获了异常" + t.getName() + "异常");
    }
}


public class Application implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));

        new Thread(new Application(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new Application(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new Application(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new Application(), "MyThread-4").start();
    }

    @Override
    public void run() {
        throw new RuntimeException();
    }
}

输出结果
十一月 25, 2020 9:03:00 下午 com.msr.study.concurrent.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止MyThread-1
捕获器1捕获了异常MyThread-1异常
十一月 25, 2020 9:03:01 下午 com.msr.study.concurrent.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止MyThread-2
捕获器1捕获了异常MyThread-2异常
十一月 25, 2020 9:03:01 下午 com.msr.study.concurrent.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止MyThread-3
捕获器1捕获了异常MyThread-3异常
捕获器1捕获了异常MyThread-4异常
十一月 25, 2020 9:03:01 下午 com.msr.study.concurrent.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止MyThread-4

多线程会导致的问题

本文不讲解JUC的用法。

线程安全问题

什么是线程安全问题?

《Java Concurrency In Practice》:当一个线程访问一个对象时,如果不用考虑这些线程在运行环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

通俗的解释:不管业务中遇到怎样的多个线程访问对象或某些方法的情况,而在编写业务逻辑的时候,都不需要额外的处理,程序也可以正常运行,不会因为多线程而出错,就可以称之为线程安全。

造成线程安全问题:

1.数据争用:数据书写由于同时写,会造成错误数据

2.竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入之前读取

运行结果错误

 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 Application implements Runnable {
    private static int i = 0;
    public static void main(String[] args) {
        Application application = new Application();
        Thread thread1 = new Thread(application);
        Thread thread2 = new Thread(application);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }

    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            increment();
        }
    }
    
    public void increment(){
        i++;
    }
}
输出结果
177

运行多次发现每次的记过都不一样。会出现少加的情况。那是因为i++并不是原子操作,i++在多线程下,出现小时的请求现象,属于read-modify-write。所以很明显出现问题的时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
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
// 最懒得方法
public class Application implements Runnable {
    private static int i = 0;
    public static void main(String[] args) {
        Application application = new Application();
        Thread thread1 = new Thread(application);
        Thread thread2 = new Thread(application);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }


    @Override
    public void run() {
        for (int j = 0; j < 100; j++) {
            increment();
        }
    }

    public synchronized void increment(){
        i++;
    }
}

// 最推荐常用:volatile+原子类
public class Application implements Runnable {
    private static volatile AtomicInteger i = new AtomicInteger(0);
    public static void main(String[] args) {
        Application application = new Application();
        Thread thread1 = new Thread(application);
        Thread thread2 = new Thread(application);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }


    @Override
    public void run() {
        for (int j = 0; j < 10; j++) {
            increment();
        }
    }

    public void increment(){
        i.incrementAndGet();
    }
}

死锁问题

 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
public class ThreadDeadLock {

    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new HoldThread(lockA, lockB), "threadA").start();
        new Thread(new HoldThread(lockB, lockA), "threadB").start();
    }

}

class HoldThread implements Runnable {

    private final String source1;
    private final String source2;

    public HoldThread(String source1, String source2) {
        this.source1 = source1;
        this.source2 = source2;
    }

    @Override
    public void run() {
        synchronized (source1) {
            System.out.println(Thread.currentThread().getName() + "\t 持有锁" + source1 + "尝试获得" + source2);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (source2) {
                System.out.println(Thread.currentThread().getName() + "\t 持有锁" + source2 + "尝试获得" + source1);
            }
        }
    }
}

输出
threadA	 持有锁lockA尝试获得lockB
threadB	 持有锁lockB尝试获得lockA

最后程序会卡着不动,使用jstack命令查看,很明显出现了死锁

 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
jstack 12000

Found one Java-level deadlock:
=============================
"threadB":
  waiting to lock monitor 0x000000001c700c88 (object 0x000000076b6dc760, a java.lang.String),
  which is held by "threadA"
"threadA":
  waiting to lock monitor 0x000000001c703678 (object 0x000000076b6dc798, a java.lang.String),
  which is held by "threadB"

Java stack information for the threads listed above:
===================================================
"threadB":
        at com.msr.study.concurrent.deadlock.HoldThread.run(ThreadDeadLock.java:42)
        - waiting to lock <0x000000076b6dc760> (a java.lang.String)
        - locked <0x000000076b6dc798> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
"threadA":
        at com.msr.study.concurrent.deadlock.HoldThread.run(ThreadDeadLock.java:42)
        - waiting to lock <0x000000076b6dc798> (a java.lang.String)
        - locked <0x000000076b6dc760> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

性能问题

调度:上下文切换

上下文:保存线程执行现场

缓存开销:缓存失效

关于JUC请看百度