Java学习(十)

本文深入探讨Java多线程编程,包括线程的创建、生命周期、控制、同步、通信及线程池应用,旨在帮助读者掌握Java并发编程的核心概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java学习(十)

多线程

  1. 线程的创建和启动

    三种方式:

    1. 继承Thread类创建线程类(自定义线程类)

      重写run()方法,调用线程类对象的start()方法来启动

      Thread.currentThread()返回当前正在执行的线程对象

      getName()实例方法返回调用该方法的线程名,默认是Thread-n,可以用setName(String name)为线程设置名字

      但是如果不是这种方式实现的线程,就必须用Thread.currentThread().getName()来得到名字了

    2. 实现Runnable接口创建线程类

      Runnable对象仅仅作为Thread对象的target,其内部的run()方法仅作为线程执行体,实际的线程对象仍然是Thread实例,但是这个target对象可以被多个线程共享

    3. Callable和Future接口

      Callable接口的call()方法可以作为线程的执行体,具有返回值用于让任意方法作为线程的执行体,但由于该接口不是Runnable的子接口,故不能直接作为target

      Future接口提供的FutureTask实现类同时实现了Future接口和Runnable接口

      提供了取消Callable任务、获取call()的返回值、暂停任务等方法

      创建并启动有返回值的线程:

      1. 创建Callable接口的实现类(函数式接口),实现call()方法(有返回值)
      2. 使用FutureTask类包装Callable对象
      3. FutureTask对象作为Thread对象的target
      4. 通过FutureTask对象的get()取得返回值
  2. 线程生命周期

    1. 新建和就绪态

      调用start()方法后,线程处于就绪态,此时JVM为其创建方法调用栈和程序计数器,如果想要让其立即运行,可以让当前运行的线程使用Thread.sleep(1),进入阻塞态,则JVM会让新线程进入运行态

    2. 运行和阻塞态

      yield()方法可以让一个线程从运行态进入就绪态

    3. 线程死亡

      1. 正常结束:run()call()
      2. 抛出异常或错误
      3. 调用该线程的stop()方法,易死锁

      死亡状态的线程无法再start()

  3. 控制线程

    Thread类提供的join()实例方法,使得调用该实例方法的主线程程序体必须等待调用该方法的线程执行完毕后才能继续运行。

  4. 后台线程

    特性:所有前台线程死亡,后台线程会自动死亡;前台线程创建的线程默认是前台线程,同理,后台默认是后台,主线程是前台线程。

    可以在线程start()之前,使用相应对象的setDaemon(true)来设置其为后台线程。

  5. 线程睡眠

    sleep()方法,进入阻塞态

  6. 线程让步

    yield()方法,线程进入就绪态

  7. 改变线程优先级

    Thread类提供了实例方法setPriority(int newPriority)来设置优先级,范围是1~10,并且有如下三个静态常量

    MAX_PRIORITY:10

    MIN_PRIORITY:1

    NORM_PRIORITY:5

    但不同OS对优先级的支持不同,所以一般使用这三个。

    还提供了getPriority()方法来获取优先级

  8. 线程同步

    1. 同步监视器

      Java引入同步监视器来解决线程同步的问题,通用方法是使用同步代码块,格式如下:

      synchronized(obj)/*obj就是同步监视器,一般使用要共享的资源作为同步监视器*/
      {
          ......
      }
      

      任何时候都只能有一个线程获得同步监视器的锁定

    2. 同步方法

      synchronized修饰实例方法即可(不能是static),监视器是this,即整个对象

    3. 释放同步监视器的锁定

      释放:

      1. 代码执行结束
      2. 代码遇到错误或异常
      3. 调用了同步监视器对象的wait()方法

      不释放:

      1. 调用了sleep()yield()方法
      2. 调用了suspend()挂起线程
    4. 同步锁

      每次只能有一个线程对Lock对象加锁,需要在代码的相应位置手动加锁/释放锁;

      常用的是可重入锁ReentrantLock

    Extra

    1. synchronized只能修饰实例方法和代码块,不能修饰构造器和成员变量。
    2. 如果运行环境可变,有单/多线程两种环境,那么应该提供两种方案——线程安全和线程不安全,保证性能和可靠性
  9. 线程通信

    传统通信,Object类的三个方法,必须由同步监视器对象调用

    1. wait():当前线程等待,直到其他线程调用notify()notifyAll()来唤醒;或者用带参数的wait自己醒来

      notify():唤醒在该监视器上等待的单个线程,多个等待则选择任意的一个,只有当前线程放弃锁定时才可执行被唤醒的线程。

      notifyAll():唤醒所有线程,只有当前线程放弃锁定时才可执行被唤醒的线程。

    2. 如果不存在隐式的同步监视器对象,即用了Lock对象,就使用Condition类来协调通信

      Condition对象被绑定在Lock对象上,使用newCondition()可以得到特定的Lock对象对应的Condition实例

      await():类似wait()使当前线程等待

      signal():唤醒单个线程,类似notify()

      signalAll():唤醒在此Lock对象上等待的所有线程,类似notifyAll()

    3. 阻塞队列控制线程通信

      BlockingQueue接口为Queue接口的子接口:生产者线程想往队列放入元素时,如果队列已满则该线程被阻塞。

      提供了两个支持阻塞的方法:

      1. put(E e)放入该队列,若已满则阻塞该线程
      2. take():从队列头部取出元素,若为空则阻塞该线程

      此外还有ArrayBlockQueue等支持线程阻塞的队列

  10. 线程组

    Java采用ThreadGroup表示线程组。

    特点:

    1. 如果没有显式指定线程组,那么则属于默认线程组;子线程默认属于父线程所在的线程组。
    2. 线程加入指定线程组后,直到线程死亡都不能改变其所属线程组

    Thread类提供了带有ThreadGroup参数的构造器

    可以通过调用一个Thread对象的getThreadGroup()方法来返回其所属的线程组。

    线程组有两个构造器:

    ThreadGroup(String name)

    ThreadGroup(ThreadGroup parent, String name):指定名字的同时,指定父线程组

  11. 线程池

    线程池适合程序需要创建大量生存期很短的线程。

    特点:

    当系统启动时,线程池会创建大量空闲的线程,把一个Runnable对象或Callable对象传给线程池时,线程池就会启动一个线程来执行它们的run()或者call()方法,执行完毕后线程不死亡,而是返回线程池成为空闲状态,等待执行下一个Runnable对象的run()或者call()方法

    创建线程池:

    1. Executors工厂类(提供静态方法)

      ExecutorService newCachedThreadPool():具有缓存功能的线程池,根据需要创建并缓存

      ExecutorService newFixedThreadPool(int nThreads):创建固定数量线程的线程池

      ExecutorService newSingleThreadExecutor():创建单线程的线程池

      ScheduledExecutorService newScheduledThreadPool(int corePoolSize):指定线程数,并在指定延迟后执行线程任务

      ScheduledExecutorService newSingleThreadScheduledExecutor():只有一个线程的线程池,在指定延迟后执行线程任务

      ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,还会使用多队列来减少竞争。

      ExecutorService newWorkStealingPool():简化版本,其值和CPU数量相等。

      其中的ExecutorService对象代表线程池,可以执行RunnableCallable对象代表的线程

      上述中间两个方法的ScheduledExecutorService是前者子类,在指定延迟后执行任务。

      最后两个方法生成的是后台线程池

      ExecutorService对象提供了如下三个方法:

      1. Future<?> submit(Runnable task):有空闲线程时执行任务,Future对象会接受返回值,如果是run()方法则返回null
      2. <T> Future<T> submit(Runnable task, T result):有空闲线程时执行任务,result显式指定返回值,该值是由Future对象返回的
      3. <T> Future<T> submit(Callable<T> task):有空闲线程时执行任务,Future接受call()的返回值
      4. shutdown():关闭线程池
    2. ForkJoinPool

      ExecutorService的实现类,他利用CPU的多核优势,将一个任务拆分成多个小任务进行并行计算,再把多个小任务的结果合并起来。

      两种构造器:

      1. ForkJoinPool(int parallelism):指定并行程度

      2. ForkJoinPool():以Runtime.getRuntime().availableProcessors()的返回值作为parallelism

      执行任务

      submit(ForkJoinTask task)invoke(ForkJoinTask task)

      ForkJoinTask代表可并行任务,为抽象类,有两个抽象子类RecursiveAction(无返回值)和RecursiveTask(有返回值,使用时继承的是RecursiveTask<T>,T即为返回值类型)

      分解:需要手动定义分解的任务,且需要调用fork()函数。

      合并:返回值需要调用子任务的join()函数,其结果需要自定义

  12. 线程相关类

    1. ThreadLocal类

      为每个使用ThreadLocal<T>对象的线程提供一个该变量值的副本,每个线程可以独立改变自己的副本。

      可以理解为:该变量对于不同的线程来讲是独立的,即每个线程都有自己的一个ThreadLocal

      示例:

      class Account
      {
          private ThreadLocal<String> name = new ThreadLocal<>();
          public Account(String str)
          {
              this.name.set(str);
              System.out.println("===" + this.name.get());
          }
      
          public String getName()
          {
              return name.get();
          }
      
          public void setName(String str)
          {
              this.name.set(str);
          }
      }
      
      class Mytest extends Thread
      {
          private Account account;
          public Mytest(Account account, String name)
          {
              super(name);
              this.account = account;
          }
          public void run()
          {
              for (int i = 0;i < 10;i++)
              {
                  if (i == 6)
                  {
                      account.setName(getName());
                  }
                  System.out.println(account.getName() + ' ' + i);
              }
          }
      }
      
      public class test {
          public static void main(String[] args) throws InterruptedException {
              Account at = new Account("init");
              new Mytest(at, "t1").start();/*对于t1和t2来说,at中,name的值是各自私有的*/
              new Mytest(at, "t2").start();
          }
      }
      

      提供三个public实例方法

      T get():返回副本值

      void remove():删除值

      void set(T value):设置值

      通过上述方式,可以保证共享对象的安全,如果其他线程只是写不读取(不通信)共享对象,那么完全可以用ThreadLocal替代锁或者同步,来提高性能。

    2. 包装线程不安全的集合为线程安全的集合

      使用Collections提供的静态方法即可

    3. 线程安全的集合类

      两种:

      1. Concurrent开头的集合类

        支持多线程并发访问,使用迭代器遍历时,可能无法反映出创建迭代器之后元素的变化

      2. CopyOnWrite开头的集合类

        底层封装了CopyOnWriteArrayList,会完全复制原始数组,只是读取的话性能很赚,如果频繁写入就很浪费

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值