• Java高并发5-守护线程、ThreadLocal和死锁四个必要条件


    一、复习

    • void interrupt()、boolean isInterrupted()、boolean interrupted()
    • interrupt()方法只是为了做一个“中断”的标记,而不会真的停止该线程、isInterrupted是返回线程的中断状态、interrupted()方法也是一样的,但是这是一个static方法,可以直接调用而且根据源码,是所在的线程的中断状态,而且如果结果为true,它会立即消除“中断”状态。

    二、上下文切换

    • 定义:CPU会给各个线程分配时间片,当一个线程的时间片使用结束后,它会处于就绪状态,此时CPU会切换到另外一个线程,这就是上下文切换。
    • 上下文切换的时机有两个:(1)线程使用完时间片完全处于就绪状态时;(2)线程被中断;

    三、死锁

    • 定义:两个或者两个以上的线程,当他们进行争夺资源用于执行的时候,形成一种互相等待的局面,就是死锁。

    1.死锁产生的四个必要条件:

    • (1)互斥条件:线程对已经获得的资源进行排他性使用,也就是资源被占用,只能由一个线程占用,如果其他线程想要获得该资源,就必须等待,除非该线程释放了资源。
    • (2)请求并持有条件:一个线程占有了资源,并且还想要其他资源,但是此时其他资源被其他线程占用了,那么此时在等待资源释放,并且并不会释放自己已经持有的资源
    • (3)不可剥夺条件:一个线程占用了资源,直到它使用完才会释放,在此期间如果由其他线程想要该资源,是不能抢占的
    • (4)环路等待条件,也就是有一个线程集合{t1,t2,t3,......tn},t1想要t2的资源,t2想要t3的资源......直到tn想要t1的资源,形成一个环路
    • 举个死锁的例子
    package com.ruigege.threadFoundation1;

    public class DeadLockExample {
     //创建资源
     private static Object object1 = new Object();
     private static Object object2 = new Object();

     public static void main(String[] args) throws InterruptedException {
      Thread thread1 = new Thread(new Runnable() {
       @Override
       public void run() {
        synchronized(object1) {
         System.out.println("线程1获得了资源1");
         try {
          Thread.sleep(1000);
         } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
         }
         synchronized(object2) {
          System.out.println("线程1获得了资源2");
         }
         
        }

       }
      });
      Thread thread2 = new Thread(new Runnable() {
       @Override
       public void run() {
        synchronized(object2) {
         System.out.println("线程2获得了资源2");
         try {
          Thread.sleep(1000);
         } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
         }
         synchronized(object1) {
          System.out.println("线程2获得了资源1");
         }     
        }

       }
      });
      thread1.start();
      thread2.start();
      thread1.join();
      thread2.join();
     }
    }
    5.1
    5.1
    • 两者互相等待上了,死锁的四个条件都满足了,形成了死锁

    2.如何避免死锁

    • 至少需要破坏一个必要条件,学过操作系统的小伙伴知道,目前只用请求并持有条件和环路等待条件是可以破坏的。上面的代码稍微改动,把第二个线程的object1和object2,位置互换,也就是和线程1的顺序一致。运行: 5.2

    四、守护线程

    • 在Java中有两种线程一种是用户线程,一种是守护线程。我们使用的main函数就是一个用户线程,垃圾回收器就是一个守护线程。守护线程的描述就是JVM的退出并不取决于守护线程是否还在运行,也就是说,当用户线程结束的时候,退出JVM,此时是否有守护线程都无所谓,依然会退出。

    1.创建一个守护线程

    线程.setDaemon(true);
    线程.start();
    • 调用setDaemon方法即可。我们来测试一下,上面描述的特性是否成立
    package com.ruigege.threadFoundation1;

    public class DaemonThreadTest {

     public static void main(String[] args) {
      Thread thread1 = new Thread(new Runnable() {
       @Override
       public void run() {
        while(true) {
         //我们这里搞一个死循环也就是让子线程不停
        }
       }
      });
      
      thread1.setDaemon(true);
      thread1.start();
      
      System.out.println("主线程已经结束了,看一看后面还有没有光标在动");
      
     }
    }

    5.3 5.4

    • 第一个没设置为守护线程,可以看到虽然主线程已经结束了,但是子线程没有结束,程序仍然在运行;第二个就是设置了,但是主线程一结束,守护线程也结束了。

    linux命令 ps -ef | grep java 这个Linux命令代表什么 ps代表的是显示某个进程;grep全称为global regular expression print代表是查找的意思,它的后面可以接正则表达式,这个grep java就是代表查找出所有带有java的进程然后显示出来; |是管道符合,这里代表的意思就是前后两个命令同时执行; ps后面跟一些参数,-e代表所有的进程;-f代表全称线程。还有其他参数,百度了一下。-h : 不显示标题;-l : 长格式-w : 宽输出;a :显示终端上的所有进程,包括其他用户的进程;r :只显示正在运行的进程;u :以用户为主的格式来显示程序状况;x :显示所有程序,不以终端机来区分。

    • 总结:如果你想要主线程结束之后马上退出JVM,那么就可以把子线程设置为守护线程。反之,就把子线程设置为用户线程。

    五、ThreadLocal解析

    • 多线程访问同一个共享对象的时候,往往会有并发问题,那么我们可以通过加锁的方式来保证对象的内容统一,但是这种效率很低,于是Java的JDK提供了一个类ThreadLocal,该共享变量的工作原理是:一旦创建了一个共享变量之后,它会在每个线程中创建一个副本,用于本线程使用,实际操作的是自己线程独有的副本,保证线程安全,不会和其他线程混淆
    • 我们接下来举个例子:

    package com.ruigege.threadFoundation1;

    public class ThreadLocalTest {
     
     public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
     
     public static void printContentOfThreadLocal(ThreadLocal threadLocal) {
      System.out.println(threadLocal.get());
      threadLocal.remove();
      
     }
     
     public static void main(String[] args) {
      Thread thread1 = new Thread(new Runnable() {
       @Override
       public void run() {
        threadLocal.set("共享变量1");
        System.out.println("线程1的共享变量是:");
        printContentOfThreadLocal(threadLocal);
        System.out.println("线程1去除共享变量后的值为:" + threadLocal.get());
       }
      });
      
      Thread thread2 = new Thread(new Runnable() {
       @Override
       public void run() {
        threadLocal.set("共享变量2");
        System.out.println("线程2的共享变量是:");
        printContentOfThreadLocal(threadLocal);
        System.out.println("线程2去除共享变量后的值为:" + threadLocal.get());
       }
      });
      
      thread1.start();
      try {
       Thread.sleep(100);
      }catch(InterruptedException e) {
       e.printStackTrace();
      }
      
      thread2.start();
      
     }
    }

    5.5 5.6

    • 第一个是把语句threadLocal.remove注释掉的结果,第二个是没有注释的结果。

    六、源码:

  • 相关阅读:
    7. JavaScript学习笔记——DOM
    6. Javscript学习笔记——BOM
    3. Javascript学习笔记——变量、内存、作用域
    2. Javscript学习笔记——引用类型
    1. JavaScript学习笔记——JS基础
    计算机网络学习笔记——网络层
    python小数据池,代码块知识
    pycharm快捷键
    字典
    04
  • 原文地址:https://www.cnblogs.com/ruigege0000/p/13934755.html
Copyright © 2020-2023  润新知