Java基础知识-线程

Java基础知识-线程

1、在 Java 中要想实现多线程代码有几种手段?

  1. 一种是继承 Thread 类

  2. 另一种就是实现 Runnable 接口

  3. 最后一种就是实现 Callable 接口

  4. 第四种也是实现 callable 接口,只不过有返回值而已

2、Thread 类中的 start() 和 run() 方法有什么区别?

start()方法被用来启动新创建的线程,而且 start()内部调用了 run()方法,这和直接调用 run()方法的效果不一样。当你调用 run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。

3、Java 中 notify 和 notifyAll 有什么区别?

notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它 才有用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一 个线程能继续运行。

4、Java 多线程中调用 wait() 和 sleep()方法有什么不同?

Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放 CPU 资源或者让当前线程停止执行一段时间,但不会释放锁。

在java中,sleep和wait方法都用于线程的暂停,但它们在原理、使用场景、对锁的处理机制等方面有所不同。以下是详细介绍:

● 原理不同。sleep是Thread类中的静态方法,用于让线程暂停执行一段时间,但不释放持有的锁;wait是Object类的方法,用于线程间的通信,当线程执行wait时会释放它持有的锁,并进入等待池。

● 使用区域不同。sleep可以在任何地方使用,并且必须捕获可能出现的异常;wait必须放在同步控制方法和同步代码块中使用。

● 唤醒方式不同。sleep必须传递一个超时时间参数,超过指定时间后线程会自动唤醒;wait可以有两种形式,一种是无需传递参数,线程会进入无限期的等待状态,直到被其他线程的notify或notifyAll唤醒;另一种是传递参数,线程会在指定时间后醒来。

● 释放锁资源不同。sleep不会释放锁;wait会释放锁,使得其他线程可以访问被锁定的资源。

5、什么是线程安全

多个线程同时运行一段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。同一个实例对象在被多个线程使用的情况下也不会出现计算失误,也是线程安全的,反之则是线程不安全的。

7、线程的状态?

实线程一般具有五种状态,即创建、就绪、运行、阻塞、终止。

  1. 在这里插入图片描述新建( new ):新创建了一个线程对象。

  2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象的start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。

  3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ),执行程序代码。

  4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:

● a.等待阻塞:运行( running )的线程执行 o.wait ()方法,JVM 会把该线程放 入等待队列( waitting queue )中。

● b.同步阻塞:运行( running )的线程在获取对象的同步锁时, 若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。

● c.其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t.join ()方法,或者发出了 I / O 请求时,JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。

  1. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

8、线程间通信的几种实现方式?

  1. 使用 volatile 关键字。基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式。

  2. 使用Object类的wait() 和 notify() 方法。Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。

注意: wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁

9 线程属性

编号(ID):用于标识线程的唯一编号,只读属性。

名称 (Name):用于定义线程名称,可读可写。

线程类别(Daemon):通过线程的 setDaemon(boolean on) 方法进行设置,为 true 表示设置为守护线程,否则为用户线程。用户线程会阻止 Java 虚拟机正常停止,守护线程则不会。通常可以把一些不重要的线程设置为守护线程,比如监控其他线程状态的监控线程,当其他工作线程停止后,虚拟机就可以正常退出。

优先级 (Priority):Java 线程支持 1 到 10 十个优先级,默认值为 5 。Java 线程的优先级本质上只是给线程调度器一个提示信息,它并不能保证线程一定按照优先级的高低顺序运行,所以它是不可靠的,需要谨慎使用。在 Java 平台中,子线程的优先级默认与其父线程相同。

1 0 Java 线程的生命周期分为以下五类状态:

RUNABLE:该状态包括两个子状态:READY 和 RUNING 。处于 READY 状态的线程被称为活跃线程,被线程调度器选中后才开始运行,转化为 RUNING 状态。

BLOCKED:一个线程发起一个阻塞式 IO 操作后(如文件读写或者阻塞式 Socket 读写),或者申请一个由其他线程持有的独占资源(比如锁)时,相应的线程就会处于该状态。

WAITING:线程处于无时间限制的等待状态。

TIMED_WAITING:有时间限制的等待状态,如果在指定时间内并没有执行的特定的操作,则该线程自动转换为 RUNABLE。

TERMINATED:Thread.run()正常返回或者由于抛出异常而提前终止,则对应的线程都会处于该终止状态。

1 1 线程的三大特性

线程有原子性、可见性和有序性三大特性。

原子性

对于涉及共享变量的操作,若该操作从其执行线程以外的任意线程来看都是不可分割的,那么我们就说该操作具有原子性。它包含以下两层含义:

访问(读、写)某个共享变量的操作从其执行线程以外的其他任何线程来看,该操作要么已经执行结束要么尚未发生,即其他线程不会看到该操作的中间部分的结果。

可见性

如果一个线程对某个共享变量进行更新之后,后续访问该变量的其他线程可以读取到这个更新结果,那么我们就称该更新对其他线程可见,反之则是不可见,这种特性就是可见性。出现可见性问题,往往意味着线程读取到了旧数据,这会导致更新丢失,从而导致运行结果与预期结果存在差异。可见性问题与计算机的存储结构和 Java 的内存模型都有着密切的关系。

有序性

源代码顺序:程序员编写的代码的执行顺序;

程序顺序:编译后的代码的执行顺序;

执行顺序:给定代码在处理器上的实际执行顺;

感知顺序:处理器感知到的其他处理器上代码的执行顺序。

在 Java 语言中,volatile 和 synchronized 都能够保证有序性:

volatile:通过内存屏障来禁止指令重排序,通过加载屏障和存储屏障来冲刷写缓冲器和清空无效化队列,从而可以避免内存重排序的现象;

synchronized :使用 synchronized 修饰的变量在同一时刻只允许一个线程对其进行 lock 操作,这种限制决定了持有同一个锁的两个同步块只能串行执行,也就避免了乱序问题。

1 2 ThreadLocal的理解

ThreadLocal 主要功能有两个:

● 第一个是可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题

● 第二个是实现了线程内的资源共享

面试官:好的,那你知道ThreadLocal的底层原理实现吗?

在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量,用来存储资源对象

当我们调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为 value,放入当前线程的 ThreadLocalMap 集合中

当调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值

当调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值

面试官:好的,那关于ThreadLocal会导致内存溢出这个事情,了解吗?

是应为ThreadLocalMap 中的 key 被设计为弱引用,它是被动的被GC调用释放key,不过关键的是只有key可以得到内存释放,而value不会,因为value是一个强引用。

在使用ThreadLocal 时都把它作为静态变量(即强引用),因此无法被动依靠 GC 回收,建议主动的remove 释放 key,这样就能避免内存溢出。

相关推荐
清风徐来QCQ5 分钟前
八股文(1)
java·开发语言
zdl6866 分钟前
springboot集成onlyoffice(部署+开发)
java·spring boot·后端
lsx2024069 分钟前
网站主机技术
开发语言
摇滚侠11 分钟前
你是一名 java 程序员,总结定义数组的方式
java·开发语言·python
xyq202421 分钟前
Vue3 条件语句详解
开发语言
架构师沉默38 分钟前
AI 让程序员更轻松了吗?
java·后端·架构
浩浩kids1 小时前
R•Homework
开发语言·r语言
Oueii1 小时前
掌握Python魔法方法(Magic Methods)
jvm·数据库·python
qq_416018721 小时前
设计模式在C++中的实现
开发语言·c++·算法