1.线程的创建方式

1、继承Thread类

  1. public class ThreadDemo2 extends Thread {
  2. public static void main(String[] args) {
  3. ThreadDemo2 threadDemo2 = new ThreadDemo2();
  4. threadDemo2.start();
  5. }
  6. //重写run方法
  7. @Override
  8. public void run() {
  9. System.out.println("hello i am a thread" + this.getName());
  10. }
  11. }

在调用了start()方法后,才真正启动了线程,但是调用start()方法后线程并不是马上执行,而是处于就绪状态,这个就绪状态是指该线程已经获取了除CPU资源外的其他资源,等待获取到CPU资源后才会真正的处于运行状态,一旦run方法执行完毕,该线程就处于终止状态。
继承Thread方法的好处是可以直接使用this获取当前线程的信息,而不需要在用Thread.currentThread(),限制条件是java只支持单继承,如果创建线程的时候使用继承,则没法继承别的其他类。

  1. //可以看到Thread类其实也是实现了Runnable 接口,实际上创建线程都是走的Runnable接口
  2. public class Thread implements Runnable {}

2、实现Runnable接口

  1. public class ThreadDemo2 implements Runnable {
  2. public static void main(String[] args) {
  3. ThreadDemo2 threadDemo2 = new ThreadDemo2();
  4. new Thread(threadDemo2).start();
  5. new Thread(threadDemo2).start();
  6. }
  7. //重写run方法
  8. @Override
  9. public void run() {
  10. System.out.println("hello i am a thread" + Thread.currentThread().getName());
  11. }
  12. }

3、使用FutureTask方式可以获取线程的返回值

  1. public class ThreadDemo2 implements Callable<String> {
  2. @Override
  3. public String call() throws Exception {
  4. return "call 线程执行完毕";
  5. }
  6. public static void main(String[] args) {
  7. // 创建异步任务,
  8. FutureTask<String> futureTask = new FutureTask(new ThreadDemo2());
  9. // 启动线程
  10. new Thread(futureTask).start();
  11. try {
  12. String result = futureTask.get();
  13. System.out.println(result);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } catch (ExecutionException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }

小结: 使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable 方式,则只能使用主线程里面被声明为final 的变量。不好的地方是Java 不支持多继承,如果继承了Thread 类,那么子类不能再继承其他类,而Runable 则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask 方式可以。

2.线程通知与等待

1、wait()方法

  1. public static void main(String[] args) throws InterruptedException {
  2. //单独用wait()方法执行会报异常 java.lang.IllegalMonitorStateException
  3. Object o = new Object();
  4. o.wait();
  5. }

1.1 IllegalMonitorStateException异常

调用wait()方法的对象需要先获取该对象的监视器锁,否则会报IllegalMonitorStateException异常。

1.2 使用方式

获取监视器锁的方法是需要和synchronized关键字进行组合使用,如下:
(1)执行synchronized同步代码块时,使用该共享变量作为参数;
(2)调用该共享变量的且带有synchronized关键字的方法。

  1. public static void main(String[] args) throws InterruptedException {
  2. new Thread(()->{
  3. synchronized (o){
  4. try {
  5. System.out.println("线程被阻塞");
  6. o.wait();
  7. System.out.println("jjjj");
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }).start();
  13. Thread.sleep(1000);
  14. synchronized (o){
  15. o.notifyAll();
  16. System.out.println("唤醒");
  17. }
  18. System.out.println(11);
  19. }

1.3 wait的唤醒方式

当一个线程调用另外一个线程的wait方法时,该调用线程会被阻塞挂起,唤醒的方式:
(1)别的线程调用该共享对象的notify/notifyAll方法;
(2)其他线程调用该线程的interrupt()方法。

1.4 虚假唤醒

因阻塞的线程未经别的线程调用notify/notifyAll方法进行通知时导致的由挂起状态转为运行状态,可能是由于中断或者等待超时原因引起的现象。
解决办法:需要不停的测试该线程被唤醒的条件是否满足,即循环判断

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

1.5生产者消费者案例

  1. private static int MAX_SIZE =1;
  2. static LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue(MAX_SIZE);
  3. public static void main(String[] args) throws InterruptedException {
  4. for (int i = 0; i < 5; i++) {
  5. //消费者
  6. new Thread(()->{
  7. synchronized (linkedBlockingQueue){
  8. while (linkedBlockingQueue.isEmpty()){//空队列
  9. try {
  10. System.out.println("消费者准备阻塞");
  11. //挂起当前线程,并释放通过同步块获取的 linkedBlockingQueue 上的锁,
  12. //让生产者线程可以获取该锁,将生产元素放入队
  13. linkedBlockingQueue.wait();
  14. System.out.println("消费者被唤醒");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. System.out.println("消费者:"+Thread.currentThread().getName()+":::"+linkedBlockingQueue.toString());
  20. //消费元素,并通知唤醒生产者线程
  21. linkedBlockingQueue.poll();
  22. linkedBlockingQueue.notifyAll();
  23. }
  24. },"B"+i).start();
  25. }
  26. Thread.sleep(3000);
  27. //生产者
  28. for (int i = 0; i < 5; i++) {
  29. new Thread(()->{
  30. synchronized (linkedBlockingQueue){
  31. while (linkedBlockingQueue.size()==MAX_SIZE){
  32. try {
  33. System.out.println("生产者准备阻塞");
  34. // 挂起当前线程, 并释放通过同步块获取的linkedBlockingQueue上的锁,
  35. //让消费者线程可以获取该锁,然后获取队列里面的元素
  36. linkedBlockingQueue.wait();
  37. System.out.println("生产者被唤醒");
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. // 空闲则生成元素, 并通知消费者线程
  43. try {
  44. linkedBlockingQueue.put("a");
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. System.out.println("生产者:"+Thread.currentThread().getName()+":::"+linkedBlockingQueue.toString());
  49. linkedBlockingQueue.notifyAll();
  50. }
  51. },"A"+i).start();
  52. }
  53. }

2、wait(long timeout)方法

wait方法带参数,表示在参数所设置的时间内没有接受到notify/notifyAll方法将其唤醒,则会因超时而接着往下执行,默认被唤醒的意思。传参若为负值,则报异常IllegalArgumentException,若参数为0,则和wait()等价,默认wait()方法底层就是调用的wait(0);

  1. public static void main(String[] args) {
  2. Object o = new Object();
  3. new Thread(()->{
  4. synchronized (o){
  5. try {
  6. System.out.println("开始等待");
  7. o.wait(1000);
  8. System.out.println("超时了");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }).start();
  14. System.out.println("主线程执行");
  15. }

3、wait(long timeout, int nanos)方法

如下代码,即该方法对应的底层逻辑,默认还是调用了wait(long timeout)方法,
timeout- 要等待的最长时间(以毫秒为单位)。
nanos - 额外时间(以毫微秒为单位,范围是 0-999999)。

  1. // o.wait(1000,2); 这样传入的参数,在最后的运行结果中为wait(1001)
  2. public final void wait(long timeout, int nanos) throws InterruptedException {
  3. if (timeout < 0) {
  4. throw new IllegalArgumentException("timeout value is negative");
  5. }
  6. if (nanos < 0 || nanos > 999999) {
  7. throw new IllegalArgumentException(
  8. "nanosecond timeout value out of range");
  9. }
  10. if (nanos > 0) {
  11. timeout++;
  12. }
  13. wait(timeout);
  14. }

4、notify()方法

一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait 系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。(下面的例子输出的是放在前面的线程)

  1. public static void main(String[] args) throws InterruptedException {
  2. Object o = new Object();
  3. new Thread(()->{
  4. synchronized (o){
  5. try {
  6. System.out.println("开始等待"+Thread.currentThread().getName());
  7. o.wait();
  8. System.out.println("被唤醒 "+Thread.currentThread().getName());
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. },"B").start();
  14. new Thread(()->{
  15. synchronized (o){
  16. try {
  17. System.out.println("开始等待"+Thread.currentThread().getName());
  18. o.wait();
  19. System.out.println("被唤醒 "+Thread.currentThread().getName());
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. },"A").start();
  25. Thread.sleep(3000);
  26. new Thread(()->{
  27. synchronized (o){
  28. System.out.println("准备唤醒"+Thread.currentThread().getName());
  29. o.notify();
  30. try {
  31. System.out.println("唤醒后睡三秒");
  32. Thread.sleep(3000);
  33. //沉睡三秒,此时wait方法后的线程还不会继续执行,因此刻还未释放监视器锁,
  34. //等三秒过去后才能继续执行之前等待的线程
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. },"C").start();
  40. System.out.println("主线程执行");
  41. }

输出结果如下,此时程序仍然处于等待状态,因A线程还未被释放

  1. 开始等待B
  2. 开始等待A
  3. 主线程执行
  4. 准备唤醒C
  5. 唤醒后睡三秒
  6. 被唤醒 B

5、notifyAll()方法

不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll() 方法则会唤醒所有在该共享变量上由于调用wait 系列方法而被挂起的线程。
如上述的例子中将notify方法换为notifyAll(),则输出结果为(程序是正常执行完毕的,不会阻塞):

  1. 开始等待A
  2. 开始等待B
  3. 主线程执行
  4. 准备唤醒C
  5. 唤醒后睡三秒
  6. 被唤醒 B
  7. 被唤醒 A

6、join方法

join 的重载方法有:
join():等待该线程终止。
join(long millis):等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
join(long millis, int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

  1. Thread AThread = new Thread(()->{
  2. System.out.println("A Thread start");
  3. },"A");
  4. Thread BThread = new Thread(()->{
  5. System.out.println("B Thread start");
  6. try {
  7. Thread.sleep(3000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. },"B");
  12. System.out.println("主线程执行");
  13. AThread.start();
  14. BThread.start();
  15. AThread.join();
  16. BThread.join();
  17. System.out.println("over A and B");//此时的输出会是在A和B线程执行完毕之后才会执行

下面的例子中,线程A死循环,主线程调用线程A的join方法,等待线程A执行完毕,待B线程休眠3秒后调用主线程的interrupt()方法,设置主线程的中断标志,从结果看到是在AThread.join();处抛出InterruptedException异常。

  1. Thread AThread = new Thread(()->{
  2. System.out.println("A Thread start");
  3. while (true){
  4. }
  5. },"A");
  6. Thread mainThread = Thread.currentThread();
  7. Thread BThread = new Thread(()->{
  8. System.out.println("B Thread start");
  9. try {
  10. Thread.sleep(3000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. mainThread.interrupt();
  15. },"B");
  16. System.out.println("主线程执行");
  17. AThread.start();
  18. BThread.start();
  19. AThread.join();
  20. System.out.println("over A and B");

结果:

  1. 主线程执行
  2. B Thread start
  3. A Thread start
  4. Exception in thread "main" java.lang.InterruptedException
  5. at java.lang.Object.wait(Native Method)
  6. at java.lang.Thread.join(Thread.java:1252)
  7. at java.lang.Thread.join(Thread.java:1326)
  8. at com.yuancheng.boot.thread.ThreadDemo4.main(ThreadDemo4.java:42)

7、sleep方法

休眠,需要注意的一点是,如果A线程处于sleep状态,此时别的线程调用A线程的interrupt()方法,则会抛出InterruptedException异常。还有参数不可以传入负值,会报IllegalArgumentException。

8、yield方法

暂停当前正在执行的线程对象,并执行其他线程。(让出CPU 执行权)
当一个线程调用y ield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU 的那个线程来获取CPU 执行权。

  1. public class ThreadDemo5 implements Runnable {
  2. public ThreadDemo5() {
  3. Thread thread = new Thread(this);
  4. thread.start();
  5. }
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 5; i++) {
  9. if(i % 5 ==0){//即首次的时候进入
  10. System.out.println(Thread.currentThread()+"yield cpu...");
  11. Thread.yield();//让出cpu的执行权
  12. }
  13. }
  14. System.out.println(Thread.currentThread()+"over...");
  15. }
  16. public static void main(String[] args) throws InterruptedException {
  17. new ThreadDemo5();
  18. new ThreadDemo5();
  19. new ThreadDemo5();
  20. }
  21. }

输出结果:

  1. Thread[Thread-1,5,main]yield cpu...
  2. Thread[Thread-2,5,main]yield cpu...
  3. Thread[Thread-0,5,main]yield cpu...
  4. Thread[Thread-0,5,main]over...
  5. Thread[Thread-1,5,main]over...
  6. Thread[Thread-2,5,main]over...

若将上面的那个Thread.yield(); 注释掉,则情况为下面:

  1. Thread[Thread-0,5,main]yield cpu...
  2. Thread[Thread-0,5,main]over...
  3. Thread[Thread-2,5,main]yield cpu...
  4. Thread[Thread-2,5,main]over...
  5. Thread[Thread-1,5,main]yield cpu...
  6. Thread[Thread-1,5,main]over...

总结: sleep与yield 方法的区别在于,当线程调用sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。

9、线程中断方法

9.1 void interrupt()方法

中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess) 方法就会被调用,这可能抛出 SecurityException
如果线程在调用 Object 类的 wait())、wait(long)) 或 wait(long, int)) 方法,或者该类的join())、join(long))、join(long, int))、sleep(long)) 或 sleep(long, int)) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException
当线程A 运行时,线程B 可以调用钱程A的interrupt() 方法来设置线程A 的中断标志为true 并立即返回。设置标志仅仅是设置标志, 线程A 实际并没有被中断, 它会继续往下执行。

9.2 boolean isinterrupted() 方法

测试线程是否已经中断。线程的中断状态 不受该方法的影响,如果该线程已经中断,则返回 true;否则返回 false

9.3 boolean interrupted() 方法

测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
与isInterrupted不同的是,该方法如果发现当前线程被中断, 则会清除中断标志,并且该方法是static 方法, 可以通过Thread 类直接调用。另外从下面的代码可以知道, 在interrupted()内部是获取当前调用线程的中断标志而不是调interrupted()方法的实例对象的中断标志。

例子:

  1. public static void main(String[] args) throws InterruptedException {
  2. Thread thread = new Thread(()->{
  3. while(true){
  4. }
  5. });
  6. thread.start();
  7. thread.interrupt();//设置中断标志
  8. // 获取中断标志 true
  9. System.out.println("-isInterrupted-------"+thread.isInterrupted());
  10. // 获取中断标志并重置
  11. System.out.println("-interrupted-------"+thread.interrupted());
  12. // 获取中断标志并重置
  13. System.out.println("-interrupted-------"+Thread.interrupted());
  14. // 获取中断标志
  15. System.out.println("-isInterrupted-------"+thread.isInterrupted());
  16. thread.join();
  17. System.out.println("main is over");
  18. }
  1. public static boolean interrupted() {
  2. return currentThread().isInterrupted(true);//获取的是当前的线程,上面的例子都是取得主线程
  3. }
  1. public static void main(String[] args) throws InterruptedException {
  2. Thread thread = new Thread(()->{
  3. while(!Thread.currentThread().interrupted()){//清除了中断标志
  4. }
  5. System.out.println("Thread is "+Thread.currentThread()+"===::::"+Thread.currentThread().isInterrupted());
  6. });
  7. thread.start();
  8. thread.interrupt();//设置中断标志
  9. //
  10. thread.join();
  11. System.out.println("main is over");
  12. }

结果为(由输出结果可知,调用interrupted() 方法后中断标志被清除了。):

  1. Thread is Thread[Thread-0,5,main]===::::false
  2. main is over

10、死锁

10.1 死锁发生的条件

互斥条件: 指线程对己经获取到的资源进行排它性使用, 即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
请求并持有条件: 指一个线程己经持有了至少一个资源, 但又提出了新的资源请求,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己己经获取的资源。
不可剥夺条件: 指线程获取到的资源在自己使用完之前不能被其他线程抢占, 只有在自己使用完毕后才由自己释放该资源。
环路等待条件: 指在发生死锁时, 必然存在一个线程→资源的环形链, 即线程集合{T0 , T1 T2 ,…, Tn }中的T0 正在等待一个T1 占用的资源, Tl 正在等待T2 占用的资源,……Tn 正在等待己被T0 占用的资源。

10.2 如何避免线程死锁

要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可, 但是学过操作系统的读者应该都知道,目前只有请求并持有和环路等待条件是可以被破坏的。
更新资源的有序性即可,访问资源的顺序都保持一致,不允许随意访问资源就可保证环路等待的条件不会发生。

11、用户线程和守护线程

用户线程:main线程就属于用户线程。
守护线程:thread . setDaemo(true) ;在启动线程之前调用该方法则会将线程设置为守护线程,在主线程或者所有的用户线程全部结束后,JVM会直接销毁,不会管守护线程是否执行等的状态
如果你希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线程结束后子线程继续工作,等子线程结束后再让JVM 进程结束,那么就将子线程设置为用户线程。

12、Threadlocal

如果你创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多
个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存。
在每个线程内部都有一个名为threadLocals 的成员变量, 该变量的类型为Hash Map , 其中key 为我们定义的ThreadLocal 变量的this 引用, value 则为我们使用set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals 中,如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因
此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应线程的threadLocals 中的本地变量。

12.1 代码示例

  1. public class ThreadDemo9 {
  2. static void print(String str){
  3. System.out.println(str+":"+threadLocal.get());
  4. // threadLocal.remove();//删掉本地线程的存储的数据
  5. }
  6. static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  7. public static void main(String[] args) throws InterruptedException {
  8. new Thread(()->{
  9. threadLocal.set("Thread A set");
  10. print("Thread A ");
  11. System.out.println("thread A remove after "+threadLocal.get());
  12. },"A").start();
  13. new Thread(()->{
  14. threadLocal.set("Thread B set");
  15. print("Thread B ");
  16. System.out.println("thread B remove after "+threadLocal.get());
  17. },"B").start();
  18. }
  19. }

输出结果:

  1. Thread A :Thread A set
  2. thread A remove after Thread A set
  3. Thread B :Thread B set
  4. thread B remove after Thread B set

将注释放开:

  1. Thread A :Thread A set
  2. thread A remove after null
  3. Thread B :Thread B set
  4. thread B remove after null

12.2 InheritableThreadLocal类

InheritableThreadLocal继承自ThreadLocal , 其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量,适用场景:子线程需要使用存放在threadlocal 变量中的用户登录信息,再比如一些中间件需要把统一的id 追踪的整个调用链路记录下来,会用到该类。

  1. public class ThreadDemo10 {
  2. static ThreadLocal<String> threadLocal = new ThreadLocal<>();
  3. public static void main(String[] args) throws InterruptedException {
  4. threadLocal.set("Thread A set");
  5. new Thread(()->{
  6. System.out.println("A Thread :"+ threadLocal.get());
  7. },"A").start();
  8. System.out.println("main Thread :"+threadLocal.get());
  9. }
  10. }

输出结果为:

  1. main Thread :Thread A set
  2. A Thread :null

更新ThreadLocal:**

  1. static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

结果为(子线程可以获取到父线程的数据):

  1. main Thread :Thread A set
  2. A Thread :Thread A set

相关问题:
image.png

12.3 源码分析

1.每个线程都有一个ThreadlocalMap对象(threadLocals),key为threadlocal对象的弱引用,value为对应的副本值;
2.ThreadLocalMap的数据结构:类似于HashMap的key-value键值对,底层实现是数组,没有链表结构,适用开放定址法解决hash冲突。
3.set()方法

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();//获取当前线程
  3. ThreadLocalMap map = getMap(t);//获取当前线程的map对象,即线程类的属性threadLocals。
  4. if (map != null)
  5. //底层new Entry(key, value)给数组赋值,且继承自WeakReference,创建弱引用的对象指向key,有效 //防止内存泄露。
  6. map.set(this, value);
  7. else
  8. createMap(t, value);
  9. }

4.get()方法

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

5.remove()方法

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }