首页 > 编程语言 >Java多线程

Java多线程

时间:2024-05-01 10:22:06浏览次数:31  
标签:执行 Java 对象 创建 接口 任务 线程 多线程

  1. 程序,进程,线程
    • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念;
    • 进程是执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位;
    • 通常在一个进程中可以包含若干个线程,线程是CPU调度和执行的单位;
    • 若是单核cpu,则多线程是模拟出来的,在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,由于切换的块,就有同时执行的错觉;而 真正的多线程是指有多个cpu,即多核;
  2. 一些概念
    • 线程就是独立的执行路径;
    • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
    • main()为主线程,为系统的入口,用于执行整个程序;
    • 在一个进程中,若开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;
    • 对同一份资源进行操作时,会存在资源抢夺的问题,需要加入并发的控制;
    • 线程会带来额外的开销,如cpu调度时间,并发控制开销;
    • 每个线程在自己的工作内存交互,内存控制不当回造成数据不一致;
  3. 创建线程的方法
    继承Thread类:重写run()方法,调用start()方法开启线程,线程开启不一定立即执行,由cpu调度执行;由这种方法创建线程,子类继承Thread类具备多线程能力,但由于OOP单继承局限性不建议使用;
    实现Runnable()接口:创建一个类实现Runnable接口的类,重写run()方法,创建实现类的对象,将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象,通过Thread类的对象调用start()方法,推荐使用此方法,
    方便同一个对象被多个线程使用;
    实现Callable接口,需要返回值类型,重写call方法,需要抛出异常,创建目标对象,创建执行服务,提交执行,获取结果,关闭服务。
  4. 静态代理:静态代理是定义父类或者接口,然后被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同父类。代理对象与目标对象事项相同的接口,然后通过调用相同的方法来调用目标对象的方法;
    优点:可不修改目标对象的功能,通过代理对象对目标功能扩展;
    缺点:由于实现一样的接口,会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护。
  5. 函数式接口:任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口,对于函数式接口,就可以通过lambda表达式来创建该接口的对象;
  6. 线程的状态:
    • 创建:程序使用new关键字创建了一个线程之后,该线程就处于一个新建状态(初始状态);
    • 就绪:当线程对象调用了Thread.start()方法之后,该线程处于就绪状态;
    • 运行:就绪之后,若抢到了cpu的资源分配,就进入了运行状态;
    • 阻塞:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态,阻塞状态可分为等待阻塞,同步阻塞和其他阻塞;
    • 死亡:线程正常结束或者抛出未补货的异常都可以结束线程;
  7. 用sleep模拟倒计时
public class TestCountDown {
  public static void main(String[] args) {
      down();
  }
  public static void down(){
      int num=10;
      while (true){
          try {
              Thread.sleep(1000);
              System.out.print(num--);
              if(num<=0){
                  break;
              }
          } catch (InterruptedException e) {
              throw new RuntimeException(e);
          }
          System.out.println();
      }
  }
}
  1. yield():礼让线程,让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态,让cpu重新调度,礼让不一定成功。
  2. join():使调用join()方法的线程进入等待池并等待线程执行完毕后才会被唤醒,并不影响同一时刻处在运行状态的其他线程。
  3. 观测线程的状态:thread.getState();
  4. 线程的优先级:优先级用数字表示,范围从1~10,用setPriority()设置优先级,优先级的设定建议在start()调度前,优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度;
  5. 用户线程和守护线程:守护线程(Daemon Thread)也被称之为后台线程或服务线程,守护线程是为用户线程服务的,当程序中的用户线程全部执行结束之后,守护线程也会跟随结束。
    可以通过 Thread.setDaemon(true) 方法将线程设置为守护线程;默认情况下我们创建的线程或线程池都是用户线程,gc就是守护线程。
  6. 线程同步:由于统一进程的多个线程共享同一块存储空间,会有访问冲突的问题,为了保证数据被访问时的正确性,在访问的同时加入锁机制,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。
  7. 同步方法默认用this或者当前类class对象作为锁;同步代码块选择会发生同步问题的部分代码进行锁住,锁会变化的对象。
  8. 产生死锁的四个必要条件:
    • 互斥条件:一个资源每次只能被一个进程使用;
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
    • 循环等待条件:若干进程形成一种头尾相接的循环等待资源关系;
  9. synchronized与lock对比
    • lock是显示锁(手动开启与关闭),synchronized是隐式锁,出了作用域自动释放;
    • lock只有代码块锁,synchronized有代码块锁和方法锁;
    • 使用lock锁,JVM将话诶较少的时间来调度线程,性能更好,并有更好的扩展性;
    • 优先使用顺序:lock,同步代码块,同步方法;
  10. 线程通信问题:
    • wait():表示线程一直等待,知道其他线通知,与sleep不同,会释放锁;
    • wait(long timeout):指定等待的毫秒数;
    • notify():唤醒一个处于等待状态的线程;
    • notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程有限调度;
  11. 生产者消费者问题:是多线程同步问题的经典案例。也就是两个想爱你成在实际运行中会有互相通知的问题,解决这个问题可以利用管程法(缓冲区法)或者信号灯法;
  12. 为什么要有线程池?
    经常创建和销毁线程会使用特别大的资源,尤其是并发情况下,对性能影响很大,因此要提前创建好多个线程,放入线程池中,使用获取,用完放回,高效利用。
    java通过Executors提供四种线程池:分别为:
    工厂方法 corePoolSize maximumPoolSize keepAliveTime workQueue 应用场景
    newCachedThreadPool 0 Integer.MAX_VALUE 60s SynchronousQueue 执行数量多,耗时少的线程任务
    newFixedThreadPool nThreads nThreads 0 LinkedBlockingQueue 控制线程最大并发数
    newSingleThreadExecutor 1 1 0 LinkedBlockingQueue 单线程(不适合并发但可能引起IO阻塞或影响UI线程相应的操作,如数据库操作)
    newScheduledThreadPool corePoolSize Integer.MAX_VALUE 0 DelayedWorkQueue 执行定时/周期性任务
    //举例:定长线程池(FixedThreadPool)
    //创建方法的源码:
    public static ExecutorService newFixedThreadPool(int nThreads) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
    }
    //特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
    //应用场景:控制线程最大并发数。
    //如何使用:
    // 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run() {
         System.out.println("执行任务啦");
      }
    };
    // 3. 向线程池提交任务
    fixedThreadPool.execute(task);
    
  13. 总结:Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理可以更加明确线程池的运行规则,规避资源耗尽的风险。
    Executors 的 4 个功能线程有如下弊端:
    FixedThreadPool 和 SingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM。
    CachedThreadPool 和 ScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。
    //ThreadPoolExecutor构造方法
    public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
    }
    /*
    创建线程池,在构造一个新的线程池时,必须满足下面的条件:
    corePoolSize(线程池基本大小)必须大于或等于0;
    maximumPoolSize(线程池最大大小)必须大于或等于1;
    maximumPoolSize必须大于或等于corePoolSize;
    keepAliveTime(线程存活保持时间)必须大于或等于0;
    workQueue(任务队列)不能为空;
    threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类
    handler(线程饱和策略)不能为空,默认策略为ThreadPoolExecutor.AbortPolicy。
    */
    //创建实例
    //任务队列
    BlockingQueue queue = new LinkedBlockingQueue(10);
    
    ThreadPoolExecutor executor = new ThreadPoolExecutor(4,10,2,TimeUnit.SECONDS,queue);
    
    executor.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("test");
        }
    });
    合理配置线程池:需要针对具体情况而具体处理,不同的任务类别应采用不同规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任务。
    对于CPU密集型任务(计算密集型):线程池中线程个数应尽量少,不应大于CPU核心数;
    对于IO密集型任务(大量网络,文件操作):由于IO操作速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池可以配置尽量多些的线程,以提高CPU利用率;
    对于混合型任务:可以拆分为CPU密集型任务和IO密集型任务,当这两类任务执行时间相差无几时,通过拆分再执行的吞吐率高于串行执行的吞吐率,但若这两类任务执行时间有数据级的差距,那么没有拆分的意义。
    

标签:执行,Java,对象,创建,接口,任务,线程,多线程
From: https://www.cnblogs.com/hytip/p/18168649

相关文章

  • 多线程TCP的一些问题
    使用循环堵塞等待客户端连接,连接到一个就开一条线程,当用以下代码,即每次ad重新初始化后其地址作为实参进行线程的创建,结果就是当有新客户端连接,开了新线程时,旧线程看起来会被停止,实际上是因为ad用了地址而不是值作为实参,所以当新连接进来时,ad的值被更改,但地址不变,旧线程所使用的ad......
  • Java IO流之为什么要手动关闭IO流
    目录1IO流关闭1.1问题引入1.2为什么IO流需要手动关闭1.3正确关闭流姿势介绍1.3.1在try中关流而没在finally中关流1.3.2在关闭多个流时将其放在一个try中1.3.3在循环中创建流在循环外关闭1.3.4关闭多个流时没用遵循后定义先释放原则1.3.5jdk7及以上版本推荐try-......
  • 03.Java 基础语法
    1.注释、标识符、关键字三种注释单行注释://多行注释:/*多行注释*/文档注释:/***@DescriptionHelloWorld*@Authorxxx*/标识符关键字Java所有的组成部分都需要名字。类名、变量名以及方法名都被称为标识符2.数据类型强类型语言:要求变量的使用严格符合规定,......
  • java EasyExcel 导出不同dto到多sheet,同时有动态字段,分页写入方案,解决存在oom的问题
    思路 1将一次查询数据改成分页查询,比如一次2000条,2将每次查询的数据按业务分组计算每类业务动态列追加的最大次数treeMap追加列2在excel列表头则是追加2列,名称自定义,我这边是补数字,示例追加列1,追加列2我的业务是按数据库存放的图片来确定最大追加列,需要将图片......
  • Java实现自定义指标数据远程写入Prometheus
    主要的流程如下:1>prometheus添加启动参数2>调用http请求来远程写,数据格式是protobuf(一种自定义的编码格式),编码格式是snappy(一种压缩格式)3>远程写通过snappy先压缩,然后将通过protobuf编码的字节数组发送请求;prometheus官网文档远程写提供remote.proto(包含编码和解码),remote.pr......
  • JavaScript函数
    JavaScript函数函数就是一些功能或语句的封装。在需要的时候,通过调用的形式,执行这些语句。函数也是一个对象函数定义我们使用function关键字定义函数,中文含义是“函数”、“功能”。可以使用如下方式进行定义。函数声明使用函数声明来创建一个函数。语法:function函数名([形......
  • java5
    for循环:publicclassFor{publicstaticvoidmain(String[]args){intsum=0;for(inti=1;i<=100;i++){if(i%2==0){sum=sum+i;}}System.out.println(sum);}while循环:......
  • 多线程
    1.相关概念程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。如:运行中的QQ,运行中的网易音乐播放器。线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。一个......
  • java面试题汇总
    基础篇1、java中==和equals的区别?==是比较运算符,Equals是方法==在判断基本数据类型的时候,就是判断数值是否相等,比如int10和int20的时候就是判断10和20这两个数是否相等==在判断引用数据类型的时候,也就是对象是否相等的时候,判断的是内存地址是否相等,也就是这两......
  • Java中“==”与“equals”在字符串比较中的应用与分析
    packagecom.aiit.helloworld;publicclassHelloWorld{ publicstaticvoidmain(Stringargs[]){ Strings1="a"+"b"; Strings2=newString(s1); if(s1==s2)//false System.out.println("doit~~~"); if(s1.equals(s......