多线程小结

参考于:首页-KuangStudy

https://blog.csdn.net/pange1991/article/details/53860651

什么是线程

线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程可以利用进程所有拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。

创建线程的方式

  1. 继承Thread类(lamda表达式:new Thread(()->System.out.println(“多线程学习。。。。”)).start();)

    1. 自定义线程类继承Thread类
    2. 重写run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
  2. 实现Runnable接口(推荐使用,因为Java单继承的局限性)

    1. 自定义实现Runnable接口
    2. 实现run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
  3. 实现Callable接口

    1. 实现Callable接口,需要返回值类型
    2. 重写call()方法,需要抛出异常
    3. 创建目标对象
      1. 方式一:通过线程池
        • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
        • 提交执行:Future future = ser.submit(callable);
        • 获取结果:boolean r1 = future .get()
        • 关闭服务:ser.shutdownNow();
      2. 方式二:FutureTask(适配类)
        • FutureTask futureTask = new FutureTask(myThread); // 适配类
        • new Thread(futureTask,”A”).start(); // 调用执行
        • Integer result = (Integer) futureTask.get(); // 获取返回值
  4. 创建线程池

    1. 使用Executors的静态方法(不推荐使用)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 有且只有一个固定的线程
      ExecutorService threadPool = Executors.newSingleThreadExecutor();
      //创建一个线程池,一池有N个固定的线程,有固定线程数的线程
      ExecutorService threadPool = Executors.newFixedThreadPool(5);//执行长期任务性能好
      //线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。可扩容,遇强则强
      ExecutorService threadPool = Executors.newCachedThreadPool();//执行很多短期异步任务

      /**
      *阿里巴巴Java开发手册
      *4. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
      *的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
      *说明:Executors 返回的线程池对象的弊端如下:
      * 1)FixedThreadPool 和 SingleThreadPool:
      *允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
      * 2)CachedThreadPool 和 ScheduledThreadPool:
      *允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
      */
    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
      public ThreadPoolExecutor(int corePoolSize,
      int maximumPoolSize,
      long keepAliveTime,
      TimeUnit unit,
      BlockingQueue<Runnable> workQueue,
      ThreadFactory threadFactory,
      RejectedExecutionHandler handler) {
      if (corePoolSize < 0 ||
      maximumPoolSize <= 0 ||
      maximumPoolSize < corePoolSize ||
      keepAliveTime < 0)
      throw new IllegalArgumentException();
      if (workQueue == null || threadFactory == null || handler == null)
      throw new NullPointerException();
      this.acc = System.getSecurityManager() == null ?
      null :
      AccessController.getContext();
      this.corePoolSize = corePoolSize;
      this.maximumPoolSize = maximumPoolSize;
      this.workQueue = workQueue;
      this.keepAliveTime = unit.toNanos(keepAliveTime);
      this.threadFactory = threadFactory;
      this.handler = handler;
      }
    3. 线程池源码解析

      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
      public ThreadPoolExecutor(
      int corePoolSize,//核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。
      //默认情况下,在创建了线程池后,线程池中的线程数为0
      //当有任务来之后,就会创建一个线程去执行任务,
      //当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
      int maximumPoolSize,//最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于1
      long keepAliveTime,//空闲的线程保留的时间。
      TimeUnit unit,//空闲线程的保留时间单位
      /*
      TimeUnit.DAYS; //天
      TimeUnit.HOURS; //小时
      TimeUnit.MINUTES; //分钟
      TimeUnit.SECONDS; //秒
      TimeUnit.MILLISECONDS; //毫秒
      TimeUnit.MICROSECONDS; //微妙
      TimeUnit.NANOSECONDS; //纳秒
      */
      BlockingQueue<Runnable> workQueue,//阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选
      ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
      RejectedExecutionHandler handler//拒绝策略。阻塞队列已满,且任务量大于最大线程的异常处理策略。参数有
      /*
      ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
      ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务 (重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
      */
      )
    4. 创建线程池

      1
      2
      //创建线程池
      ExecutorService threadPool = new ThreadPoolExecutor( 2,5,2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());
    5. ThreadPoolExecutor 底层工作原理

      1

线程状态

1
2
3
4
5
6
7
8
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

Java线程的6种状态及切换(透彻讲解) 原文链接:https://blog.csdn.net/pange1991/article/details/53860651

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

  3. 阻塞(BLOCKED):表示线程阻塞于锁。

  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

  6. 终止(TERMINATED):表示该线程已经执行完毕。

    线程的状态图

死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。

Synchronized 与 Lock 的对比

  1. 首先synchronized是java内置关键字,在jvm层面;Lock是个java类,是api层面的锁;

  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

  3. Lock只有代码块锁,synchronized有代码块锁和方法锁

  4. 使用Lock锁,JVM将花费较少的时间来调度线程,性能好。并且具有更好的扩展性(提供更多的子类)

  5. **synchronized(隐式锁)会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock(显式锁)**需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

  6. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

  7. synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平(两者皆可)

    JAVA锁机制-可重入锁,可中断锁,公平锁,读写锁,自旋锁- 博客园 (cnblogs.com)

  8. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

    提示:Synchronized中不使用if而使用while可以防止虚假唤醒

wait / sleep 的区别

  1. 来自不同的类

    这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类

    sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

  2. 有没有释放锁(释放资源)

    最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

    sleep是线程被调用时,占着cpu去睡觉,其他线程不能占用cpu,os认为该线程正在工作,不会让出系统资源,wait是进入等待池等待,让出系统资源,其他线程可以占用cpu。

  3. 使用范围不同

    wait、notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

  4. 是否需要捕获异常

    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

8锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**

* 多线程的8锁

* 1、标准访问,请问先打印邮件还是短信?

* 2、邮件方法暂停4秒钟,请问先打印邮件还是短信?

* 3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?

* 4、两部手机、请问先打印邮件还是短信?

* 5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?

* 6、两个静态同步方法,2部手机,请问先打印邮件还是短信?

* 7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?

* 8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?

*/

小结

synchronized实现同步的基础:java中的每一个对象都可以作为锁具体的表现为以下三种形式:

对于同步代码块,锁是synchronized括号里面的配置对象

对于普通同步方法,锁的是当前实例对象

对于静态同步方法,锁的是当前的Class对象。