多线程

一. 线程与进程

  1. 进程

   是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。

  2. 线程

  (1)是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。

  (2)线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

二. 线程调度

  1. 分时调度

   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  2. 抢占式调度

  (1)优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

  (2)CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

三. 同步与异步

  1. 同步:排队执行 , 效率低但是安全。

  2. 异步:同时执行 , 效率高但是数据不安全。

四. 并发与并行

  1. 并发:指两个或多个事件在同一个时间段内发生。

  2. 并行:指两个或多个事件在同一时刻发生(同时发生)。

五. 多线程实现方法

  1. 继承Thread

   类继承Thread,重写run方法。run方法就是线程要执行的任务方法。 这个方法里的代码就是一条新的执行路径,执行路径的处理方式不是调用run方法,而是通过Thread对象的start()来启动。

   例子:MyThread t = new MyThread();

      t.start();

  2. 实现Runnable接口

   类实现Runnable,重写run方法。创建任务对象(实现Runnable的类),创建线程并分配任务,然后执行。

   例子:MyRunnable r = new MyRunnable();  

      Thread t = new Thread(r);

      t.start();

   实现Runnable与继承Thread相比有如下优势:

    (1)通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况。

    (2)可以避免单继承带来的局限性

    (3)任务与线程本身是分离的,提高程序的健壮性

    (4)后续学习的线程池,接受Runnable类型的任务,不接受Thread类型的线程。

  3. 实现Callable接口

    Callable使用步骤

     (1)编写类实现Callable接口,实现call方法

       class XXX implements Callable<T> { 

        @Override
            public <T> call() throws Exception {
              return T;
           }
       }

     (2)创建FutureTask对象,并传入第一步编写的Callable类对象。

       FutureTask<Integer> future = new FutureTask<>(callable);

     (3)通过Thread,启动线程

       new Thread(future).start();

  4. Runnable 与 Callable的相同点

   (1)都是接口

   (2)都可以编写多线程程序

   (3)都采用Thread.start()启动线程

  5. Runnable 与 Callable的不同点

   (1)Runnable没有返回值;Callable可以返回执行结果

   (2)Callable接口的call()允许抛出异常;Runnable的run()不能抛出

六. 常用方法

  1. Thread.currentThread()

    获取当前线程对象

  2. getName();

    获取线程名称

  3. Thread.sleep();

    线程休眠

  4. 线程阻塞

    所有比较消耗时间的操作,例如文件的读取,接受用户输入等,也叫耗时操作。

  5. interrupt()线程中断:

   一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。

  6. setDaemon(true)守护线程:

    线程:分为守护线程和用户线程

    用户线程:当一个进程不包含任何存活的用户线程时,进程结束。

    守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。

  7. 线程安全问题:   

    public static void main(String[] args) {

    //线程不安全

     Runnable run = new Ticket();

    new Thread(run).start();

    new Thread(run).start();

    new Thread(run).start();}

    

    线程不安全解决方案:

    (1)同步代码块            

      格式:synchronized(锁对象){}      

          Runnable run = new Ticket();          

        new Thread(run).start();           

        new Thread(run).start();            

        new Thread(run).start();}

 

        class Ticket() implements Runnable{   

         private int count = 10;

         private Object o = new Object();

         public void run(){ 

          while(true){  

           synchronized( o ){

             执行语句

           } 

          }

         }  

        }

 

      锁应该是同一把,就像例子里面,锁的创建是在类的属性创建的而不是在run方法里面创建。

    (2)同步方法

      把上面的执行语句编写成一个方法,然后给方法添加synchronized修饰符来解决。

        class Ticket() implements Runnable{   

         private int count = 10;

         private Object o = new Object();

          public void run(){ 

          while(true){  

           boolean flag = slae();

           if(!flag){

            break;

           }

          }

         }  

        }

      public synchronized boolean slae(){}

      如果不是静态的同步方法,锁就是this,如果是静态的就是 类.class

    (3)显式锁Lock

      同步代码块和同步方法都是隐式锁

       class Ticket() implements Runnable{   

        private int count = 10;

        private Object o = new Object();

        Lock l = new ReentrantLock();

        public void run(){ 

         while(true){  

          //加锁

          l.lock();

          执行语句

          //解锁

          l.unlock();

         }

        }  

       }

  8. 公平锁和不公平锁

   公平锁:先抢到先执行

   不公平锁:一直在抢

   隐式锁都是不公平锁,显式锁可以是公平锁,定义:Lock l = new ReentrantLock(true);

  9. 线程死锁

   A线程被锁住,需要等待B线程结束才能解锁,而B线程也被锁住,需要等待A线程结束才能解锁,两个线程互相等待

   

  10. 多线程通信问题

   wait()导致当前线程等待被唤醒,可以添加参数long timeoutMillis,如果没被其他线程唤醒,则经过一段时间被唤醒。

   notify()唤醒单个被某个对象监视的线程。

   notifyAll()唤醒所有被某个对象监视的线程。

   导致等待和唤醒必须是同一个对象

七. 线程的六种状态

  (1)New

    创建但尚未启动的状态

  (2)Runnable

    运行状态

  (3)Blocked

    线程安全,排队状态

  (4)Waiting

    无限期等待另一个线程唤醒

  (5)Time Waiting

    等待另一个唤醒或者等待时间结束后自动唤醒

  (6)Terminated

    结束状态。

八. 线程池

  1. 线程池概述

   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

  2. 线程池的好处

   (1)降低资源消耗。

   (2)提高响应速度。

   (3)提高线程的可管理性。

  3. Java中的四种线程池

   (1)缓存线程池

     判断线程池是否存在空闲线程;

     存在则使用;

     不存在,则创建线程并放入线程池,然后使用。

      ExecutorService service = Executors.newCachedThreadPool();

      service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

      service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

      service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

   (2)定长线程池

     判断线程池是否存在空闲线程;

     存在则使用;

     不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用;

     不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。

     ExecutorService service = Executors.newFixedThreadPool(2);

     service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

     service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

   (3)单线程线程池

     判断线程池的那个线程是否空闲;

     空闲则使用;

     不空闲,则等待池中的单个线程空闲后使用。

     ExecutorService service = Executors.newSingleThreadExecutor();

     service.execute(new Runnable() {
         @Override
          public void run() {
             System.out.println("线程的名称:"+Thread.currentThread().getName());
          }
       });

   (4)周期性任务定长线程池

     判断线程池是否存在空闲线程;

     存在则使用;

     不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用;

     不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程。

    周期性任务执行时:

     定时执行,当某个时机触发时,自动执行某任务。

     参数1. runnable类型的任务

     参数2. 时长数字

     参数3. 时长数字的单位

     ScheduledExecutorService service = Executors.newScheduledThreadPool(2);

     service.schedule(new Runnable() {
        @Override
      public void run() {
       System.out.println("俩人相视一笑~ 嘿嘿嘿");
        }
     },5,TimeUnit.SECONDS);

     参数1. runnable类型的任务

     参数2. 时长数字

     参数3. 周期时长(每次执行的间隔时间)

     参数4. 时长数字的单位

     service.schedule(new Runnable() {
        @Override
      public void run() {
       System.out.println("俩人相视一笑~ 嘿嘿嘿");
        }
     },5,2,TimeUnit.SECONDS);

九. Lambda表达式

  调用某个方法需要传递一个对象,而对象继承了某个接口,且这个接口只有一个抽象方法时,就可以使用Lambda表达式

  例子:

    interface MyMath{

     int sum(int x,int y);

    }

    使用匿名内部类

    PrintSum(new MyMath(){

     public int sum(int x,int y){

      return x+y;

     }

    },100,200);

    public void printSum(MyMath m,int x,int y){

     int num = m.sum(x,y);

     System.out.println(num);

    }

    使用Lambda表达式

    printSum((int x,int y) -> {

      return x+y;

    },100,200);

    public void printSum(MyMath m,int x,int y){

     int num = m.sum(x,y);

     System.out.println(num);

    }

 

posted @   Luo_YB  阅读(58)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示