线程笔记

多线程

什么是线程

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

创建线程的方式

  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. 创建目标对象
      • 方式一:通过线程池
        • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
        • 提交执行:Future future = ser.submit(callable);
        • 获取结果:boolean r1 = future .get()
        • 关闭服务:ser.shutdownNow();
      • 方式二:FutureTask(适配类)
        • FutureTask futureTask = new FutureTask(myThread); // 适配类
        • new Thread(futureTask,”A”).start(); // 调用执行
        • Integer result = (Integer) futureTask.get(); // 获取返回值
  4. 创建线程池

    1. //不推荐这样使用// 有且只有一个固定的线程
      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. 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:由调用线程处理该任务*/
                               )
      
    3. //创建线程池
      ExecutorService threadPool = new ThreadPoolExecutor( 2,5,2L, TimeUnit.SECONDS, 
                                                          new LinkedBlockingDeque<>(3), 
                                                          Executors.defaultThreadFactory(),
                                                          new ThreadPoolExecutor.DiscardPolicy()
                                                         );
      
    4. ThreadPoolExecutor 底层工作原理

      1

线程状态

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可以在任何地方使用

8锁问题

8锁

/**

* 多线程的8锁

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

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

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

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

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

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

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

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

*/

小结

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

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

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

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

posted @ 2023-05-07 10:59  丶忘川  阅读(28)  评论(0)    收藏  举报