初探Java线程

本文详细介绍了Java中Thread的四种创建方法,线程的五种状态,进程与线程的区别,以及sleep(),wait(),notify(),notifyAll()的区别。此外,还讲解了线程池的使用、submit()和execute()的不同,以及锁的原理和ThreadLocal的实现及其应用场景。
摘要由CSDN通过智能技术生成

一. Thread的四种创建方式

1. 继承Thread类

  • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
  • 创建Thread子类的实例,即创建了线程对象。
  • 调用线程对象的start()方法来启动该线程。

2. 实现Runnable接口中run方法

  • 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • 调用线程对象的start()方法来启动该线程。

3. FutureTask包装Callable

  • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

4. 从线程池中获取线程

二. Thread的五种状态

五种状态,创建、就绪、运行、阻塞和死亡

  • 创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  • 就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  • 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  • 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  • 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪

三. 进程与线程的区别

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

四. Thread.sleep() 和Object.wait() 的区别

sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,不会释放锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程。

五. notify()和notifyAll()的区别

  • 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
  • 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
  • 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

六. 线程池

1. 线程池的三大方法、七大参数、四种策略

七大参数:

  1. corePoolSize, 核心线程数大小,当线程数<corePoolSize ,会创建线程执行runnable
  2. maximumPoolSize, 最大线程数, 当线程数 >= corePoolSize的时候,会把runnable放入workQueue中
  3. keepAliveTime, 保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
  4. unit,时间单位
  5. workQueue, 保存任务的阻塞队列
  6. threadFactory, 创建线程的工厂
  7. handler, 拒绝策略

线程池中线程创建流程是:

corePoolSize --》 workQueue --》 maximumPoolSize --》 rejectedExecution

  1. 当线程数小于corePoolSize时,创建线程执行任务。
  2. 当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中
  3. 线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize
  4. 当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。

ThreadPoolExecutor默认有四个拒绝策略:

  1. ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException
  2. ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行
  3. ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务
  4. ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务

2. 线程池submit()和execute()的区别

(1)submit()有返回值,而execute()没有返回值。

(2)submit()方便Exception处理,意思就是如果你的task里会抛出exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

七. 锁

1. 锁的几种状态

锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

重量级锁,上锁、协调需要操作系统协调和调度

轻量级锁,自旋锁(CAS算法-compare and swap 比较和交换),cas操作怎么避免aba问题?可以通过加版本号的方式解决,版本号用原子类实现,如atomicInteger等可以实现版本号

偏向锁,单线程情况下,在对象头部打标签,标记为偏向锁,超过两个以上的线程,转为轻量级锁

2.  几种常见的锁

自旋锁,当一个线程准备获取锁时,此时锁被其他线程获取,当前线程循环等待获取锁,不停判断是否获取到锁,成功获取到锁时,则退出循环。减少了多线程环境下上下文切换的开销

乐观锁、悲观锁,自旋锁是乐观锁的一种,synchronized 锁是一种悲观锁,独占锁,互斥锁

可重入锁,那是因为这种锁是可以反复使用,这里的反复使用仅局限于一个线程。

公平锁,在大多数情况下,锁的申请都是非公平的。也就是说,线程1首先请求了锁A,接着线程2也请求了锁A。那么当锁A可用时,是线程1可以获得锁还是线程2可以获得锁呢?这是不一定的。系统只是会从这个锁的等待队列中随机挑选一个。因此不能保证其公平性,而公平的锁,则不是这样,它会按照时间的先后顺序,保证先到者先得,后到者后得。公平锁的一大特点是:它不会产生饥饿现象。只要你排队,最终还是可以等到资源的。如果我们使用synchronized关键字进行锁控制,那么产生的锁就是非公平的。而重入锁允许我们对其公平性进行设置。所以我的理解是公平锁是可充入锁的一种特例。

八. ThreadLocal的实现原理以及使用场景

ThreadLocal底层是通过ThreadLocalMap实现的。ThreadLocalMap是是ThreadLocal的一个内部类,但是使用的时候是在Thread中使用的。每一个线程都有一个ThreadLocalMap,这个Map的key是ThreadLocal对象,value就是我们塞进去的值。

使用场景:

(1)ThreadLocal可以用来做参数传递,从Controller一直传递到service层

(2)不同线程间数据隔离

(3)用于事务操作,用于存储线程事务信息。

Spring事务开始的时候,会给当前线程绑定一个JDBC Connection,在整个事务执行过程中都用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。

  • 23
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值