java多线程基础

一、基本概念

  1. CPU核心数和线程数的关系:

    1. 线程数可以模拟出不同的CPU核心数,CPU核心数指的是硬件上存在着几个核心,而线程数可以模拟出多个核心数的功能,线程数越多,越有利于同时运行多个程序,因为线程数等同于在某个瞬间CPU能同时并行处理的任务数。

    2. 对于一个CPU,线程数总是大于或等于核心数的,一个核心最少对应一个线程,但通过超线程技术,一个信和可以对应多个线程,也就是说它可以同时运行两个线程。

    3. 通俗的说,CPU核心数就是我们经常说的我们的CPU是几核的CPU,线程则是运行在某个CPU上,但是一个CPU可分时间段来运行多个线程。

  2. CPU时间片轮转机制:

    时间片轮转法(Round-Robin, RR)主要用于分时系统中的进程调度,为了实现轮转调度,系统把所有就绪进程按先入先出的原则排成一个队列,新来的进程加到就绪队列末尾,每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间,时间片是一个小的时间单位,通常为10~100ms数量级,当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止改进程的运行,把它放入就绪队列的末尾,然后把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此反复。

  3. 程序、线程和进程的联系和区别:

    1. 程序是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,而进程则不同,它是程序在某个数据集上的执行,进程是一个动态的实体,它有自己的生命周期,它因创建而产生,因调度而运行,因等待资源或时间而被处于等待状态,因完成任务而被撤销,反应了一个程序在一定的数据集上运行的全部动态过程。
    2. 进程是程序运行进行资源分配的最小单位,进程内部可能有多个线程,多个线程之间共享进程资源,进程与进程之间相互独立。
    3. 线程是CPU调度的最小单位,线程本身不能独立运行,需要依附进程才能运行。
  4. 并行和并发:

    1. 所谓并发处理都是有排队等候,唤醒和执行这三个步骤,所以并发是宏观的概念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片来轮转),所以在宏观上多个几乎同时到达的请求同时在被处理,如果同一时刻到达的请求也会根据优先级的不同,先后进入队列排队等候执行。
    2. 并发指的是一个处理器同时处理多个任务,并行是指多个处理或者是多核处理器同时处理多个不同的任务,并行通俗来讲就是4个CPU,每个CPU处理一个任务,并行能力就是4,并发是单个CPU在指定时间段内通过轮转切换处理多个任务的能力。

二、java多线程基本特性:

  1. Java语言天生是多线程,仅仅一个main方法,虚拟就就启动了6个线程:

    /**
     * 仅仅运行一个main方法,则后台虚拟机启动6个线程:
     * [6] [Monitor Ctrl-Break]
     * [5] [Attach Listener]  Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且把                           结果返回给发送者
     * [4] [Signal Dispatcher] Attach Listener线程的职责是接收外部jvm命令, 当命令接收成功后,会交                            给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处                            理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始                            化工作
     * [3] [Finalizer]  这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象                     的finalize()方法;关于Finalizer线程的几点:
                        1. 只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象                        的finalize()方法都会被执行;
                       2. 该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有                         没有执行完finalize()方法,JVM也会退出;
                       3. JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实                          现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer                        对象的引用置为null,由垃圾收集器来回收;
                       4. JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自                         己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对                         GC线程来说是一种灾难;
     * [2] [Reference Handler] VM在创建main线程后就创建Reference Handler线程,其优先级最高,                                为10, 它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收                            问题
     * [1] [main]
     */
    public class OnlyMain {
        public static void main(String[] args) {
            // 虚拟机线程管理接口
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println(String.format("[%s] [%s]", threadInfo.getThreadId(), threadInfo.getThreadName()));
            }
        }
    }
    
  2. 线程的创建方式:

    /**
     * 创建线程的方式:
     *    继承Thread类,实现Runnable接口,实现Callable接口
     *    区别:java只能单继承,但是可以多实现,使用Callable接口,可以得到每个线程的返回值
     */
    public class NewThread {
        // 继承Thread类
        private static class UseThread extends Thread {
            @Override
            public void run() {
                System.out.println("This is extends Thread class");
            }
        }
        // 实现Runnable接口
        private static class UseRunnable implements Runnable {
            @Override
            public void run() {
                System.out.println("This is implements Runnable interface");
            }
        }
        // 实现Callable接口
        private static class UseCallable implements Callable<String> {
            @Override
            public String call() throws Exception {
                return "This is Callable Result";
            }
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            Thread thread = new Thread(new UseThread(), "UseThread");
            thread.start();
            Thread runnable = new Thread(new UseRunnable(), "UseRunnable");
            runnable.start();
            FutureTask<String> futureTask = new FutureTask<String>(new UseCallable());
            new Thread(futureTask).start();
            System.out.println(futureTask.get());
        }
    }
    
  3. 线程的终止:

    线程的终止有三种方式:

    1. 设置推出标志,使线程正常退出,也就是当run()方法完成后线程终止。

      /**
       * 使用标志位来终止线程
       */
      public class FlagExitThread implements Runnable {
      
          // 使用volatile保证flag标志位在多线程运行下可见
          private volatile static boolean flag = false;
      
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while(!flag) {
                  System.out.println(threadName + "is running!!");
              }
              System.out.println(threadName + " is Exit running, flag = " + flag);
          }
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread(new FlagExitThread(), "flagExitThread");
              thread.start();
              TimeUnit.SECONDS.sleep(1);
              flag = true;
          }
      }
      
    2. 使用Thread.stop()方法来终止线程:

      程序可以用stop方法来终止线程,但是stop方法是很危险的,就像突然关闭计算机电源一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程就会抛出ThreadDeathError的错误,并且会释放子线程所持有的锁,一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有 的所有锁突然释放(不可控制) ,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用错误,因此不推荐使用stop方法来终止线程。

    3. 使用interrupt()方法来中断线程:

      /**
       * 使用interrupt方法来终止线程:
       * 1. interrupt():中断一个线程,相当于打个招呼,将中断标志置为true
       * 2. isInterrupted(): 判断线程是否处于中断状态
       * 3. 静态方法interrupted方法判断线程是否处于中断状态,中断标志置位false
       * 使用interrupt来中断线程,其实就是线程的协作式,并不是强制关闭线程
       */
      public class InterruptThread implements Runnable {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while(!Thread.currentThread().isInterrupted()) {
                  System.out.println(threadName + "is running!!");
              }
              System.out.println(threadName + " is Exit running, flag = " + Thread.currentThread().isInterrupted());
          }
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread(new InterruptThread(), "Interrupt Thread");
              thread.start();
              TimeUnit.SECONDS.sleep(1);
              thread.interrupt();
          }
      }
      
    4. 注意事项:线程抛出InterruptedException,会将isInterrupted进行复位,中断标志位修改为false,需要我们在catch里面再次中断,不然无法中断线程,线程会一直运行下去:

      public class InterruptExceptionThread implements Runnable {
          @Override
          public void run() {
              String threadName = Thread.currentThread().getName();
              while(!Thread.currentThread().isInterrupted()) {
                  // 模拟异常场景,如果在线程中断时进行休眠,会抛出InterruptedException
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      // 如果抛出异常,会将线程isInterrupted进行复位
                      System.out.println(threadName + " interrupt flag is " + Thread.currentThread().isInterrupted());
                      // 线程如果不再次中断,线程会一直运行下去
                      Thread.currentThread().interrupt();
                      e.printStackTrace();
                  }
                  System.out.println(threadName + " is running!!");
              }
              System.out.println(threadName + " is Exit running, flag = " + Thread.currentThread().isInterrupted());
          }
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread(new InterruptExceptionThread(), "Interrupt Exception");
              thread.start();
              TimeUnit.SECONDS.sleep(1);
              thread.interrupt();
          }
      }
      
  4. 线程的状态:

  1. Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其他线程执行机会的最佳方式。

  2. Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变成就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行,实际上无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中,Thread.yield()不会导致阻塞,该方法与sleep()类似,只是不能由用户定暂停时间。

  3. t.join()/t.join(long millis),当前线程里调用其他线程的join()方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁,线程t执行完毕或者millis时间到,当前线程进入就绪状态。

  4. obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列,依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。

  5. obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的,notifyAll()唤醒在此对象监视器上等待的所有线程。

  6. LockSupport中的方法,park(): 无限期暂停当前线程,parkNanos( long nanos): 暂停当前线程,不过有超时时间的限制,parkUtil(long deadline): 暂停当前线程,直到某个时间,unpark(Thread thread): 恢复指定的线程。park和wait的区别,park不需要获取某个对象的锁,中断的时候park不会抛出InterruptedException异常,所有需要在park之后自行判断中断状态,然后做额外处理。

    说明:park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用,park和unpark的使用顺序倒置也不会出现死锁的情况。

在这里插入图片描述

  1. 守护线程:

    1. 守护线程会随着主线程结束而结束

    2. setDaemon必须要在线程启动之前(即在start方法之前)

    3. 使用后台线程不能保证finally块一定执行

      public class DemanThread {
          private static class DemanThreadDemo implements Runnable {
              @Override
              public void run() {
                  try {
                      while(!Thread.currentThread().isInterrupted()) {
                          System.out.println(Thread.currentThread().getName() + " is running !!");
                      }
                      System.out.println(Thread.currentThread().getName() + " interrupt flag is " + Thread.currentThread().isInterrupted());
                  } finally {
                      // 如果线程为后台线程,此finally里面的代码不会被执行
                      System.out.println("I am finally method");
                  }
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread(new DemanThreadDemo(), "DemanThread");
              thread.setDaemon(true);
              thread.start();
              TimeUnit.SECONDS.sleep(1);
          }
      }
      
  2. ThreadLocal:线程变量

    ThreadLocal在每个线程都会创建一个线程内对应的线程副本,本线程数据可以在本线程内任何地方被使用,线程之间互相不影响,所有是线程安全的。

    public class UseThreadLocal {
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return 1;
            }
        };
    
        /**
         * 启动多个线程,看会不会相互影响彼此的threadLocal结果
         */
        public void startThreadArray() {
            Thread[] runs = new Thread[10];
            for (int i = 0; i < runs.length; i++) {
                runs[i] = new Thread(new TestThread(i), "ThreadLocal");
            }
            for (int i = 0; i < runs.length; i++) {
                runs[i].start();
            }
        }
    
        /**
         * 测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会相互影响
         */
        public static class TestThread implements Runnable {
            private int id;
            public TestThread(int id) {
                this.id = id;
            }
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":start");
                Integer s = threadLocal.get();
                s = s + id;
                threadLocal.set(s);
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
                // threadLocal.remove();   回收threadLocal变量,用的比较多的情况是线程池中
            }
        }
    
        public static void main(String[] args) {
            UseThreadLocal useThreadLocal = new UseThreadLocal();
            useThreadLocal.startThreadArray();
        }
    }
    
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值