线程与进程基础理论知识

236 阅读16分钟

线程与进程理论知识入门

  • 什么是进程,什么是线程

    • 概述:进/线程是伴随操作系统出现的

    • 应用程序(死的)安装在硬盘里面,操作系统打开此应用后,应用程序变为进程(活的)

    • 进程:独立,分配资源单位,不执行具体任务的

      • 进程之间是相互独立的(可以扯到zygot fork子进程为应用程序分配独立JVM)

      • 操作系统进行资源分配的最小单位

        • 资源:将应用程序变成进程所需要的资源

          • CPU
          • 内存
          • 磁盘I/O
    • 线程:无所不在,执行具体任务,资源拿给线程用

      • CPU调度的最小单位,不能独立于进程存在
      • 启动一个进程后,至少有一个线程
      • 同一个进程中的线程之间可以共享进程的资源(内存,磁盘I/O)
  • CPU的核心数与线程的关系

    • 什么是多核?

      • 早期计算机中一个芯片只能放一个逻辑核心/物理核心,随着摩尔定律失效,CPU的制程不断缩小;出现量子限制(量子遂川,导致提升晶体管的密度不可行)--->将多个物理核心集成到一个芯片上面(一块物理核心上,有多个处理器--->多核)
    • CPU的核心数与线程的关系

      • 概述:

        • 一般情况下内核数与线程数(真正执行任务的)对应
        • 但是Inter引入了超线程技术:物理核心数 : 逻辑核心数 = 1 :2
      • 示意图:意味着逻辑核心数就是当前可以跑的线程数

        image-20220219134927841

      • 但是为什么在开发过程中启动的线程数不受逻辑处理器数的限制?

        • 操作系统提供了CPU时间片轮转机制(RR调度)
  • CPU时间片轮转机制:使得线程数不在受限

    • 概述:看起来是一瞬间,但是CPU切的太快了

      • 人眼的反应时间:0.1S,CPU(1.6G)执行一条指令:0.6ns(一秒对应十亿纳秒);
      • 将CPU时间切片,只要切得快,用户就感觉不到
    • 时间片轮转机制的代价:

      • 操作系统一般是需要在几个进程之间进行切换,进程本身就要占用资源(内存,CPU里面的寄存器),当一个进程的时间片到了,那么就需要让出CPU,将下一个需要运行的进程载入(也叫上下文切换,非常消耗CPU时间)
      • 一次上下文切换,需要占用两万个CPU的周期(在编写代码的时候,需尽量避免造成上下文切换)
  • 并行与并发:

    • 并行:可以同时运行的进程数(两个咖啡机,两队同时使用)

      • 同时执行不同任务,实际上确实是同时执行
      • CPU逻辑核心数为8,那么并行度就是8,CPU可以同时执行8个进程
    • 并发:不能脱离时间单位,只讨论单位时间的并发量,(例如,一分钟能提供几杯咖啡,两队人交替使用咖啡机,假如一分钟出了四倍咖啡,那么在这一分钟中,并发量为4)

      • 应用可以交替执行不同任务,看起来是同时执行(操作系统中部的技术,切得很快)

      • 实际上不是同时执行的,只是切得很快,让用户感觉不到

      • 时间片轮转机制就是并发的一种手段

  • 高并发编程的意义

    • 多核背景,导致多线程高并发应用广泛

    • 多线程的好处:充分利用CPU资源,加速用户响应时间,代码模块化,异步化,简单化

      • 假如有8个CPU,单线程程序只能用一个,其他的就空闲了
      • 多线程速度快
      • 比如电商系统中多个不相关的子任务,拆给多个线程同时用,实现模块化,异步化,速度快,简单化
    • 高并发的问题:

      • 安全性:线程会共享进程资源,造成资源争夺

        • 引入锁:

          1. 造成死锁
          2. 锁的竞争会导致效率下降,甚至不如单线程
        • 操作系统内核限制线程数:对线程分配资源(线程独立的栈空间,java默认是1MB,还有文件描述符(句柄(指向资源的位置,像指针),引用))

          • Linux中一个进程最多开1000个线程,一个系统最多同时存在1024个文件描述符

          • Windows中一个进程最多开2000个线程

          • 线程开多了,导致服务器崩溃

          • 使用数据库(MySQL):连接数一般在150-200,但是应用往往使用不止这个

            • 引入数据库连接池
  • java天生就是多线程的

    • 证明:

      • 代码示意:

         public class OnlyMain {
             public static void main(String[] args) {
                 //Java 虚拟机线程系统的管理接口
                 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
                 // 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
                 ThreadInfo[] threadInfos =
                         threadMXBean.dumpAllThreads(false, false);
                 // 遍历线程信息,仅打印线程ID和线程名称信息
                 for (ThreadInfo threadInfo : threadInfos) {
                     System.out.println("[" + threadInfo.getThreadId() + "] "
                             + threadInfo.getThreadName());
                 }
             }
         }
        
      • 运行截图:打印结果与虚拟机版本,操作系统实时调度有关,可能不止6个

        image-20220219142753653

      • 部分线程说明:这个是对java程序进行监控采用

        1. Main:程序入口,这个里面会初始化虚拟机

        2. Finalizer:不要将对象资源回收放在这里(有可能不执行)

          • 优先级很低,属于守护线程(随主线程一起退出,可能导致当前对象都没有掉这个线程,来不及进行资源的回收)
        3. Attach Listener:获取内存的dump,将虚拟机中的线程信息打出来

        4. Monitor Ctrl-Break:检测中断信号的

  • 新线程的启动方式

    • java中新线程的启动大体是两种方式

      • JDK官方文档:在Thread源码注释信息中明确说明了只有两种

         There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:

      1. 通过Thread类:继承自Thread类,重写run方法

         //继承自Thread类,重写run方法
         private static class UseThread extends Thread{
            @Override
            public void run() {
               super.run();
               // do my work;
               System.out.println("I am extendec Thread");
            }
         }
         //调用:
             public static void main(String[] args) 
                     throws InterruptedException, ExecutionException {
                 UseThread useThread = new UseThread();
                 useThread.start();
                 useThread.start();
             }
        
      2. 通过Runnable接口,重写run方法

             /*实现Runnable接口*/
             private static class UseRunnable implements Runnable{
         ​
                 @Override
                 public void run() {
                     // do my work;
                     System.out.println("I am implements Runnable");
                 }
             }
         //调用:
         public static void main(String[] args) 
                     throws InterruptedException, ExecutionException {
             
                 UseRunnable useRunnable = new UseRunnable();
                 new Thread(useRunnable).start();
                 
             }
        
        • 细节:为什么要写这个new Thread(useRunnable).start();

          • 因为在Thread的构造方法中:Runnable为参数

             public Thread(Runnable target) {
                 init(null, target, "Thread-" + nextThreadNum(), 0);
             }
            
  • Thread与Runnable的区别

    • 概述:

      • 对象角度:单继承,多实现
      • Thread才是对线程的抽象(真正干活的),Runnable是对任务(业务逻辑)的抽象
  • 线程stop方法的不安全性:不建议使用,大部分情况下不合适

    • JDK不推荐使用:这些方法带有很高的强制性

    • 终止线程时,直接将当前线程干掉(根本不管当前线程有没有正常释放资源)

      • 假如,当前线程在写入文件达到一半的时候,调用stop

        • 导致文件缺失,后面的线程无法正常读取
        • 正常写入文件,结束后会在结尾处打上标记
    • 代码示意:

           @Deprecated//不推荐使用
           public final void stop() {
               SecurityManager security = System.getSecurityManager();
               if (security != null) {
                   checkAccess();
                   if (this != Thread.currentThread()) {
                       security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
                   }
               }
               // A zero status value corresponds to "NEW", it can't change to
               // not-NEW because we hold the lock.
               if (threadStatus != 0) {
                   resume(); // Wake up thread if it was suspended; no-op otherwise
               }
       ​
               // The VM can handle all thread states
               stop0(new ThreadDeath());
           }
      
    • suspend:将线程挂起(发生一次上下文切换,从可运行切换到挂起)

      • 细节:线程会持有此刻的资源(比如锁),进行休眠--->可能导致死锁

      • 代码

         @Deprecated
         public final void suspend() {
             checkAccess();
             suspend0();
         }
        
  • 让java中的线程安全停止工作:interrupt

    • 概述:让线程进行中断

      • Thread类提供了三个interrupt方法(interrupted还是static修饰的)

        image-20220219153556520

    • isInterrupted():boolean

      • 作用:判断当前线程是否被中断

      • 代码示意:

         //不会改变标志位,主线程发送中断信号后,标志位就不会改了
         public boolean isInterrupted() {
                 return isInterrupted(false);
             }
        
    • interrupted():boolean

      • 作用:判断当前线程是否被中断

      • 代码:

         public static boolean interrupted() {
             return currentThread().isInterrupted(true);
         }
         ​
         //isInterrupted
          private native boolean isInterrupted(boolean ClearInterrupted);
        
      • 具体使用:调用后会将标志位从true(主线程发送了中断信号) 改为false

        1. 代码:

           package cn.enjoyedu.ch1.base.safeend;
           ​
           /**
            *类说明:如何安全中断线程
            */
           public class EndThread {
           ​
              private static class UseThread extends Thread {
           ​
                 public UseThread(String name) {
                    super(name);
                 }
           ​
                 @Override
                 public void run() {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + " interrrupt flag =" + isInterrupted());
           //       while (!isInterrupted()) {
                       while(!Thread.interrupted()){
           //          while (true) {
                          System.out.println(threadName + " is running");
                          System.out.println(threadName + "inner interrrupt flag ="
                                + isInterrupted());
                       }
                    System.out.println(threadName+" interrrupt flag ="+isInterrupted());
                    }
                 }
           ​
                 public static void main(String[] args) throws InterruptedException {
                    Thread endThread = new UseThread("endThread");
                    endThread.start();
                    Thread.sleep(20);
                    endThread.interrupt();//中断线程,其实设置线程的标识位true
           ​
                 }
           }
          
        2. 运行截图:

          image-20220219155935614

    • interrupt():void

      • 作用:向当前线程发起中断(不代表线程立即停止工作),并不是终止线程(修改线程的标志位,默认是false),而且线程甚至可以不理会这种中断操作(在JDK中线程是协作式的(只是通知这个线程,至于怎么做,就是线程自己的事情了),不是抢占式(调用stop))

      • 源码:

         public void interrupt() {
             if (this != Thread.currentThread())
                 checkAccess();
         ​
             synchronized (blockerLock) {
                 Interruptible b = blocker;
                 if (b != null) {
                     interrupt0();           // Just to set the interrupt flag
                     b.interrupt(this);
                     return;
                 }
             }
             interrupt0();
         }
        
      1. 具体使用:仅对线程发送中断信号

        • 代码:

           package cn.enjoyedu.ch1.base.safeend;
           /**
            *类说明:如何安全中断线程
            */
           public class EndThread {
               private static class UseThread extends Thread {
                   public UseThread(String name) {
                       super(name);
                   }
                   @Override
                   public void run() {
                       String threadName = Thread.currentThread().getName();
                       System.out.println(threadName + " interrrupt flag =" + isInterrupted());
           //          while (!isInterrupted()) {
           //              while(!Thread.interrupted()){
                           while (true) {
                               System.out.println(threadName + " is running");
                               System.out.println(threadName + "inner interrrupt flag ="
                                       + isInterrupted());
                           }
           //          System.out.println(threadName+" interrrupt flag ="+isInterrupted());
                       }
                   }
           ​
                   public static void main(String[] args) throws InterruptedException {
                       Thread endThread = new UseThread("endThread");
                       endThread.start();
                       Thread.sleep(20);
                       endThread.interrupt();//中断线程,其实设置线程的标识位true
           ​
                   }
           }
          
        • 运行结果:线程并没有理会此中断信号(在run方法中没有对线程中断标示位检测)

          image-20220219154733538

      2. 具体使用:向线程发送中断信号,并在线程run方法中对中断信号做检测

        • 代码:

           package cn.enjoyedu.ch1.base.safeend;
           ​
           /**
            *类说明:如何安全中断线程
            */
           public class EndThread {
           ​
               private static class UseThread extends Thread {
           ​
                   public UseThread(String name) {
                       super(name);
                   }
           ​
                   @Override
                   public void run() {
                       String threadName = Thread.currentThread().getName();
                       System.out.println(threadName + " interrrupt flag =" + isInterrupted());
                       while (!isInterrupted()) {
           //              while(!Thread.interrupted()){
           //              while (true) {
                               System.out.println(threadName + " is running");
                               System.out.println(threadName + "inner interrrupt flag ="
                                       + isInterrupted());
                           }
                       System.out.println(threadName+" interrrupt flag ="+isInterrupted());
                       }
                   }
           ​
                   public static void main(String[] args) throws InterruptedException {
                       Thread endThread = new UseThread("endThread");
                       endThread.start();
                       Thread.sleep(20);
                       endThread.interrupt();//中断线程,其实设置线程的标识位true
           ​
                   }
           }
          
        • 运行截图:

          image-20220219155451187

  • 尽量使用中断结束线程:

    • 概述:

      • 调用interrupt方法:修改线程的中断标志位,但不会立即停止线程

      • 在线程run方法中,调用isInterrupted(这个用的比较大)与interrupted(充值标志位为空)方法可以检测标志位:

        • 线程可以不去看这个标志位,干自己的事情就行了
    • 自定义标志位(增加一个类属性):不建议

      • 在调用阻塞方法(wait,sleep,take)后,根本不会对自定义标志位进行判断

      • 在sleep源码中会抛出一个InterruptedException(即使线程挂起,也会对中断进行检测)

         public static native void sleep(long millis) throws InterruptedException;
        
    • 使用Runnable接口时怎么实现中断:找当前线程的标志位

      • 代码展示:

         package cn.enjoyedu.ch1.base.safeend;
         ​
         /**
          *类说明:实现接口Runnable的线程如何中断
          */
         public class EndRunnable {
             
             private static class UseRunnable implements Runnable{
                 
                 @Override
                 public void run() {
                     while(!Thread.currentThread().isInterrupted()) {
                         System.out.println(Thread.currentThread().getName()
                                 + " I am implements Runnable.");
                     }
                     System.out.println(Thread.currentThread().getName()
                             +" interrupt flag is "+Thread.currentThread().isInterrupted());
                 }
             }
         ​
             public static void main(String[] args) throws InterruptedException {
                 UseRunnable useRunnable = new UseRunnable();
                 Thread endThread = new Thread(useRunnable,"endThread");
                 endThread.start();
                 Thread.sleep(20);
                 endThread.interrupt();
             }
         ​
         }
        
    • 调用阻塞方法(sleep)后,如何侦测中断

      • sleep方法以抛出异常的方式检测中断信号

         public static native void sleep(long millis) throws InterruptedException;
        
      • 使用try-catch捕获相应的异常信息

         package cn.enjoyedu.ch1.base.safeend;
         ​
         /**
          *类说明:阻塞方法中抛出InterruptedException异常后,如果需要继续中断,需要手动再中断一次
          */
         public class HasInterrputException {
             
             private static class UseThread extends Thread{
                 
                 public UseThread(String name) {
                     super(name);
                 }
                 
                 @Override
                 public void run() {
                     while(!isInterrupted()) {
                         try {
                             Thread.sleep(100);
                         } catch (InterruptedException e) {
                             System.out.println(Thread.currentThread().getName()
                                     +" in InterruptedException interrupt flag is "
                                     +isInterrupted());
                             //资源释放
                             //interrupt();
                             e.printStackTrace();
                         }
                         System.out.println(Thread.currentThread().getName()
                                 + " I am extends Thread.");
                     }
                     System.out.println(Thread.currentThread().getName()
                             +" interrupt flag is "+isInterrupted());
                 }
             }
         ​
             public static void main(String[] args) throws InterruptedException {
                 Thread endThread = new UseThread("HasInterrputEx");
                 endThread.start();
                 Thread.sleep(500);
                 endThread.interrupt();
                 
         ​
             }
         ​
         }
        
      • 运行截图:抓到异常,但是没有终止当前的线程

        image-20220219163014644

      • 细节:抓到异常以后,它会修改线程的标志位(重新置false);导致当前线程不结束

        • 解决办法:在try-catch中再进行一次中断,修改标志位为true

        • 代码:

           try {
              Thread.sleep(100);
           } catch (InterruptedException e) {
              System.out.println(Thread.currentThread().getName()
                    +" in InterruptedException interrupt flag is "
                    +isInterrupted());
              //资源释放
              interrupt();//再次中断,修改标志位,结束当前线程
              e.printStackTrace();
           }
          
        • 运行截图:

          image-20220219163420810

        • 为什么要这样设计?

          • 当sleep时拿到了资源,如果不这样搞,就跟之前的stop方法一样,资源都没有被释放,就中断(终止了这个线程)

            死锁是不会理会中断信号的

  • 深入理解run与start方法的区别

    • 概述:

      • Thread类是对线程的抽象
      • 实例化Thread时并没有跟操作系统扯上关系,在调用start方法之后,才有关系
    • start方法:查看源码

      • 调用了start0方法:private native void start0();

        image-20220219164215766

    • 假如说:对一个线程进行了两次start会发生什么情况?

      • 抛出异常:Thread.java

         //调用start方法后,首先就会对当前线程的状态进行判断,掉了就不能掉了
         if (threadStatus != 0)
             throw new IllegalThreadStateException();
        
    • run方法:

      • 概述:就像是一个类的成员方法,可以脱离操作系统意义上的线程,可以随便调用

      • 代码:

         public class StartAndRun {
             public static class ThreadRun extends Thread{
         ​
                 @Override
                 public void run() {
                     int i = 90;
                     while(i>0){
                         try {
                             Thread.sleep(1000);
                         } catch (InterruptedException e) {
                             //e.printStackTrace();
                         }
                         System.out.println("I am "+Thread.currentThread().getName()
                                 +" and now the i="+i--);
                     }
                 }
             }
         ​
             public static void main(String[] args) {
                 ThreadRun threadRun = new ThreadRun();
                 threadRun.setName("threadRun");
                 threadRun.run();
             }
         }
        
      • 运行截图:执行run方法的是main线程,不是程序猿写的那个线程

        image-20220219164859668

      • 运行截图:将 threadRun.run();改为threadRun.start();(执行run方法的就是程序猿定义的那个线程了)

        image-20220219165009023

  • join方法:

    • 概述:

      • 应用场景:存在当前正在执行的线程A,此时调用线程B的join方法--->线程A被挂起,需要等到线程B执行完后,线程A才继续执行;谁调用join方法,谁就插队
      • 怎么保证两个线程顺序执行?调用join方法即可
    • 使用:

        static class Goddess implements Runnable {
               private Thread thread;
       ​
               public Goddess(Thread thread) {
                   this.thread = thread;
               }
       ​
               public Goddess() {
               }
       ​
               public void run() {
                   System.out.println("Goddess开始排队打饭.....");
                   try {
                       if(thread!=null) thread.join();
                   } catch (InterruptedException e) {
                   }
                   SleepTools.second(2);//休眠2秒
                   System.out.println(Thread.currentThread().getName()
                           + " Goddess打饭完成.");
               }
           }
       ​
           static class GoddessBoyfriend implements Runnable {
       ​
               public void run() {
                   SleepTools.second(2);//休眠2秒
                   System.out.println("GoddessBoyfriend开始排队打饭.....");
                   System.out.println(Thread.currentThread().getName()
                           + " GoddessBoyfriend打饭完成.");
               }
           }
      
      1. 不进行插队:

        • 代码:

               public static void main(String[] args) throws Exception {
           ​
                   Thread lison = Thread.currentThread();
                   Goddess goddess = new Goddess();
                   Thread g = new Thread(goddess);
                   g.start();
                   System.out.println("lison开始排队打饭.....");
           ​
                 System.out.println(Thread.currentThread().getName() + " lison打饭完成.");
               }
          
        • 运行截图:分开都有饭吃

          image-20220219170653587

      1. 让g插队:实现两个线程顺序执行

        • 代码:

              public static void main(String[] args) throws Exception {
           ​
                   Thread lison = Thread.currentThread();
                  
                   Goddess goddess = new Goddess();
                   Thread g = new Thread(goddess);
                   g.start();
                   System.out.println("lison开始排队打饭.....");
                   g.join();
                   SleepTools.second(2);//让主线程休眠2秒
                   System.out.println(Thread.currentThread().getName() + " lison打饭完成.");
               }
          
        • 运行截图:保证子线程一定在主线程之前完成执行

          image-20220219170952187

      2. 在g线程之前再查一个线程(g线程内部会检测类的成员变量,不为空,就将其插入到g线程前面执行)

        • 代码:

               public static void main(String[] args) throws Exception {
           ​
                   Thread lison = Thread.currentThread();
                   GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
                   Thread gbf = new Thread(goddessBoyfriend);
                   Goddess goddess = new Goddess(gbf);//在g线程前面在插一个线程
                   
                   Thread g = new Thread(goddess);
                   g.start();
                   gbf.start();
                   System.out.println("lison开始排队打饭.....");
                   g.join();
                   SleepTools.second(2);//让主线程休眠2秒
                   System.out.println(Thread.currentThread().getName() + " lison打饭完成.");
               }
          
        • 运行截图:

          image-20220219171452894

  • 线程的优先级和守护线程

    • 线程优先级:优先级不能确定线程的具体执行顺序(由操作系统决定)

      • 在启动线程的时候,可以为其设定优先级(1-10),默认为5

      • 优先级高的线程所分配的时间片可能较多(具体由操作系统决定)

        • 有些操作系统最高只有3
        • 需要休眠和I/O操作(设置优先级高点),计算的线程优先级设置低一点

          • 确保处理器不会被计算线程占据
    • 守护线程:支持性的线程(在程序后台中做调度,内存的回收等)

      • 在启动一个进程后,进程中会有很多线程

      • 通过new Thread启动的线程(用户线程),没有特殊处理时,均为非守护线程

      • 通过JDK内部启动与参数启动的就是守护线程

        • interrupt
        • 在用户线程(main)等非守护线程结束后,进程就结束了,守护线程随进程结束而结束
      • 自定义守护线程

        • 代码:

           Thread useThread = new Thread();
           useThread.setDaemon(true);//默认是false
          
        • 在框架中应用较多:管理自己分配的内存Netty

          • 应用程序退出后,操作系统会释放内存
        • 守护线程中的tr-catch-finally中的finally不一定执行

          • 一般来说finalize操作是放在finally里面的,但是如果是在守护线程中,那么就不一定会执行(主要看有没有被分配时间片)

            • 不要在这个里面做耗时操作
          • 在用户线程中finally就一定会执行

            • 里面就放内存释放,finalize
  • synchronized

    • 概述:锁住的是对象(不同的锁之间是可以并行执行),对象头上面就有标志位(同步代码拿到这个对象就修改其标志位,表示这个对象已经被某一把锁拿到了)

      • 内置锁(没有显示的开锁,绑锁操作),实际上是绑在对象上的(对象锁)
      • 在执行同步代码的时候,需要拿到对象的实例才行
      • 不同线程拿到不同锁里面的对象--->并发执行
      • 不同线程拿到同一把锁里面的对象--->不能并发执行
    • 使用:同步块,方法

      • 对方法加锁:对当前的对象加锁

         public synchronized void incCount(){
             ……
         }
        
      • 同步块

         private Object obj = new Object();//作为一个锁
         public void incCount(){
                 synchronized (obj){//可以将obj替换为this
                     count++;
                 }
             }
        
        • this关键字:当前成员方法所在的对象实例
    • 代码展示:使用Runnable,传入实例

      • 对两个对象分别加锁,各自启动一个线程,实现并行执行:运行时,基本上是同时输出的

         package cn.enjoyedu.ch1.syn;
         ​
         import cn.enjoyedu.tools.SleepTools;
         ​
         /**
          *类说明:锁的实例不一样,也是可以并行的
          */
         public class DiffInstance {
            
             private static class InstanceSyn implements Runnable{
                 private DiffInstance diffInstance;
         ​
                 public InstanceSyn(DiffInstance diffInstance) {
                     this.diffInstance = diffInstance;
                 }
         ​
                 @Override
                 public void run() {
                     System.out.println("TestInstance is running..."+ diffInstance);
                     diffInstance.instance();
                 }
             }
         ​
             private static class Instance2Syn implements Runnable{
                 private DiffInstance diffInstance;
         ​
                 public Instance2Syn(DiffInstance diffInstance) {
                     this.diffInstance = diffInstance;
                 }
                 @Override
                 public void run() {
                     System.out.println("TestInstance2 is running..."+ diffInstance);
                     diffInstance.instance2();
                 }
             }
         ​
             private synchronized void instance(){
                 SleepTools.second(3);
                 System.out.println("synInstance is going..."+this.toString());
                 SleepTools.second(3);
                 System.out.println("synInstance ended "+this.toString());
             }
         ​
             private synchronized void instance2(){
                 SleepTools.second(3);
                 System.out.println("synInstance2 is going..."+this.toString());
                 SleepTools.second(3);
                 System.out.println("synInstance2 ended "+this.toString());
             }
         ​
             public static void main(String[] args) {
                 DiffInstance instance1 = new DiffInstance();
                 Thread t3 = new Thread(new Instance2Syn(instance1));
                 DiffInstance instance2 = new DiffInstance();
                 Thread t4 = new Thread(new InstanceSyn(instance2));
                 t3.start();
                 t4.start();
                 SleepTools.second(1);
             }
         }
        
      • 对一个对象加锁,两条线程执行同一个对象的实例

         //修改main方法
         public static void main(String[] args) {
             DiffInstance instance1 = new DiffInstance();
             //传入相同对象实例
             Thread t3 = new Thread(new Instance2Syn(instance1));
             Thread t4 = new Thread(new InstanceSyn(instance1));
             t3.start();
             t4.start();
             SleepTools.second(1);
         }
        
    • 类锁:在静态方法上面加锁;跟对象锁互相对立

      • 实质上还是对对象加锁;因为java虚拟机在执行类加载的时候,每一个类都有唯一的Class对象;只是说,现在synchronized锁住的是类唯一的Class对象
    • 判断能否并行:可以,锁的是不同的对象

       //加载静态方法上,锁的是类的Class对象
       private static synchronized void synClass(){
           System.out.println(Thread.currentThread().getName()
                   +"synClass going...");
           SleepTools.second(1);
           System.out.println(Thread.currentThread().getName()
                   +"synClass end");
       }
       //加载静态对象上,锁的是静态变量而已
       private static Object obj = new Object();
       private static void synStatic(){
           synchronized (obj){
               System.out.println(Thread.currentThread().getName()
                       +"synStatic going...");
               SleepTools.second(1);
               System.out.println(Thread.currentThread().getName()
                       +"synStatic end");
           }
       }