Java 多线程 常见问题

发布于 2024-10-09 21:32:26 字数 13903 浏览 8 评论 0

Java 实现线程有哪几种方式?

1、继承 Thread 类实现多线程
2、实现 Runnable 接口方式实现多线程
3、使用 ExecutorService、Callable、Future 实现有返回结果的多线程

多线程中 start() 和 run() 区别

只有调用了 start() 方法,才会表现出多线程的特性,不同线程的 run() 方法里面的代码交替执行。

如果只是调用 run() 方法,那么代码还是同步执行的,必须等待一个线程的 run() 方法里面的代码全部执行完毕之后 另外一个线程才可以执行其 run() 方法里面的代码。

Runnable 接口和 Callable 接口的区别

Runnable 接口中的 run() 方法的返回值是 void,它做的事情只是纯粹地去执行 run() 方法中的代码而已;

Callable 接口中的 call() 方法是有返回值的,是一个泛型,和 Future、FutureTask 配合可以用来获取异步执行的结果。

CyclicBarrier 和 CountDownLatch 的区别

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务,CyclicBarrier 可重用,CountDownLatch 不可重用,计数值为 0 该 CountDownLatch 就不可再用了

volatile 关键字的作用

使用 volatile 关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到 volatile 变量,一定是最新的数据

什么是线程安全

如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。

线程安全也是有几个级别的

不可变

像 String、Integer、Long 这些,都是 final 类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用

绝对线程安全

不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java 中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java 中也有,比方说 CopyOnWriteArrayList、CopyOnWriteArraySet

相对线程安全

相对线程安全也就是我们通常意义上所说的线程安全,像 Vector 这种,add、remove 方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个 Vector、有个线程同时在 add 这个 Vector

99% 的情况下都会出现 ConcurrentModificationException,也就是 fail-fast 机制。

  1. Java 中如何获取到线程 dump 文件

死循环、死锁、阻塞、页面打开慢等问题,打线程 dump 是最好的解决问题的途径。
所谓线程 dump 也就是线程堆栈,获取到线程堆栈有两步:

  • 获取到线程的 pid,可以通过使用 jps 命令,在 Linux 环境下还可以使用 ps -ef | grep java
  • 打印线程堆栈,可以通过使用 jstack pid 命令,在 Linux 环境下还可以使用 kill -3 pid

另外提一点,Thread 类提供了一个 getStackTrace() 方法也可以用于获取线程堆栈。

这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,

一个线程如果出现了运行时异常会怎么样

如果这个异常没有被捕获的话,这个线程就停止执行了。

另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

如何在两个线程之间共享数据

通过在线程之间共享对象就可以了,然后通过 wait/notify/notifyAll、await/signal/signalAll 进行唤起和等待,
比方说阻塞队列 BlockingQueue 就是为线程之间共享数据而设计的

并发工具类:Exchanger 允许在并发任务之间交换数据。具体来说,Exchanger 类允许在两个线程之间定义同步点。
当两个线程都到达同步点时,他们交换数据结构,因此第一个线程的数据结构进入到第二个线程中,第二个线程的数据结构进入到第一个线程中。

sleep 方法和 wait 方法有什么区别

sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器

生产者消费者模型的作用是什么

  • 通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
  • 解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

ThreadLocal 有什么用

简单说 ThreadLocal 就是一种以空间换时间的做法,为每一个线程创建一个单独的变量副本,每个线程都可以独立改变自己所拥有的变量副本,而不会影响其它线程变量副本。

把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了

为什么 wait() 方法和 notify()/notifyAll() 方法要在同步块中被调用

这是 JDK 强制的,wait() 方法和 notify()/notifyAll() 方法在调用前都必须先获得对象的锁

wait() 方法和 notify()/notifyAll() 方法在放弃对象监视器时有什么区别

wait() 方法立即释放对象监视器,notify()/notifyAll() 方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

为什么要使用线程池

避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。

怎么检测一个线程是否持有对象监视器

Thread 类提供了一个 holdsLock(Object obj) 方法,当且仅当对象 obj 的监视器被某条线程持有的时候才会返回 true,注意这是一个 static 方法,这意味着"某条线程"指的是当前线程。

synchronized 和 ReentrantLock 的区别

synchronized 是和 if、else、for、while 一样的关键字,
ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,
ReentrantLock 比 synchronized 的扩展性体现在几点上:

  1. ReentrantLock 可以对获取锁的等待时间进行设置,这样就避免了死锁
  2. ReentrantLock 可以获取各种锁的信息
  3. ReentrantLock 可以灵活地实现多路通知

ConcurrentHashMap 的并发度是什么

ConcurrentHashMap 的并发度就是 segment 的大小,默认为 16,
这意味着最多同时可以有 16 条线程操作 ConcurrentHashMap,这也是 ConcurrentHashMap 对 Hashtable 的最大优势

ReadWriteLock 是什么

ReadWriteLock 是一个读写锁接口,ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

FutureTask 是什么

FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于 FutureTask 也是 Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。

Java 编程写一个会导致死锁的程序

死锁就是线程 A 和线程 B 相互等待对方持有的锁导致程序无限死循环下去

  • 两个线程里面分别持有两个 Object 对象:lock1 和 lock2。这两个 lock 作为同步代码块的锁;
  • 线程 1 的 run() 方法中同步代码块先获取 lock1 的对象锁, Thread.sleep(xxx) ,时间不需要太多,50 毫秒差不多了,然后接着获取 lock2 的对象锁。这么做主要是为了防止线程 1 启动一下子就连续获得了 lock1 和 lock2 两个对象的对象锁
  • 线程 2 的 run() 方法中同步代码块先获取 lock2 的对象锁,接着获取 lock1 的对象锁,当然这时 lock1 的对象锁已经被线程 1 锁持有,线程 2 肯定是要等待线程 1 释放 lock1 的对象锁的

这样,线程 1 睡觉 睡完,线程 2 已经获取了 lock2 的对象锁了,线程 1 此时尝试获取 lock2 的对象锁,便被阻塞,此时一个死锁就形成了。代码就不写了,占的篇幅有点多,Java 多线程 7:死锁这篇文章里面有,就是上面步骤的代码实现。

怎么唤醒一个阻塞的线程

如果线程是因为调用了 wait()、sleep() 或者 join() 方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;

如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

不可变对象对多线程有什么帮助

不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。

什么是多线程的上下文切换

多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取 CPU 执行
权的线程的过程。

如果你提交任务时,线程池队列已满,这时会发生什么

  1. 如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷大的队列,可以无限存放任务
  2. 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到 ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增加线程数量,如果增加了线程数量

还是处理不过来,ArrayBlockingQueue 继续满,那么则会使用拒绝策略 RejectedExecutionHandl
er 处理满了的任务,默认是 AbortPolicy

Java 中用到的线程调度算法是什么

抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0) 的作用是什么

由于 Java 采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到 CPU 控制权的情况,为了让某些优先级比较低的线程也能获取到 CPU 控制权,可以使用 Thread.sleep(0) 手动触发一次操作系统分配时间片的操作,这也是平衡 CPU 控制权的一种操作。

什么是自旋锁

自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。

使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。

因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了 CPU 时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。

在 JDK1.6 中,Java 虚拟机提供-XX:+UseSpinning 参数来开启自旋锁,使用-XX:PreBlockSpin 参数来设置自旋锁等待的次数。

在 JDK1.7 开始,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整。

很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。

如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

什么是 Java 内存模型

Java 虚拟机规范试图定义一种 Java 内存模型(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,让 Java 程序在各种平台上都能达到一致的内存访问效果。

总结一下 Java 内存模型的几部分内容:

Java 内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次 Java 线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去

Java 采用内存共享的模式来实现线程之间的通信。编译器和处理器可以对程序进行重排序优化处理,但是需要遵守一些规则,不能随意重排序。

  • 原子性:一个操作或者多个操作要么全部执行要么全部不执行;
  • 可见性:当多个线程同时访问一个共享变量时,如果其中某个线程更改了该共享变量,其他线程应该可以立刻看到这个改变;
  • 有序性:程序的执行要按照代码的先后顺序执行;

happens-before 原则是 JMM 中非常重要的一个原则,它是判断数据是否存在竞争、线程是否安全的主要依据。依靠这个原则,我们可以解决在并发环境下两个操作之间是否存在冲突的所有问题。JMM 规定,两个操作存在 happens-before 关系并不一定要 A 操作先于 B 操作执行,只要 A 操作的结果对 B 操作可见即可。

什么是 CAS

CAS,全称为 Compare and Swap,即比较-替换。假设有三个操作数:内存值 V、旧的预期值 A、要修改的值 B,当且仅当预期值 A 和内存值 V 相同时,才会将内存值修改为 B 并返回 true,否则什么都不做并返回 false。

当然 CAS 一定要 volatile 变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值 A 对某条线程来说,永远是一个不会变的值 A,只要某次 CAS 操作失败,永远都不可能成功。

什么是乐观锁和悲观锁

乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像 synchronized,不管三七二十一,直接上了锁就操作资源了。

什么是 AQS

简单说一下 AQS,AQS 全称为 AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。

如果说 java.util.concurrent 的基础是 CAS 的话,那么 AQS 就是整个 Java 并发包的核心了,ReentrantLock、CountDownLatch、Semaphore 等等都用到了它。

AQS 实际上以双向队列的形式连接所有的 Entry,比方说 ReentrantLock,所有等待的线程都被放在一个 Entry 中并连成双向队列,前面一个线程使用 ReentrantLock 好了,则双向队列实际上的第一个 Entry 开始运行。

AQS 定义了对双向队列所有的操作,而只开放了 tryLock 和 tryRelease 方法给开发者使用,开发者可以根据自己的实现重写 tryLock 和 tryRelease 方法,以实现自己的并发功能。

单例模式的线程安全性

首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:

  1. 饿汉式单例模式的写法:线程安全
  2. 懒汉式单例模式的写法:非线程安全
  3. 双检锁单例模式的写法:线程安全

Lazy initialization holder class 模式这个模式综合使用了 Java 的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。

Semaphore 有什么作用

Semaphore 就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore 有一个构造函数,可以传入一个 int 型整数 n,表示某段代码最多只有 n 个线程可以访问,如果超出了 n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果 Semaphore 构造函数中传入的 int 型整数 n=1,相当于变成了一个 synchronized 了。

Hashtable 的 size() 方法中明明只有一条语句 return count,为什么还要做同步?

同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程 A 在执行 Hashtable 的 put 方法添加数据,线程 B 则可以正常调用 size() 方法读取 Hashtable 中当前元素的个数,那读取到的值可能不是最新的,可能线程 A 添加了完了数据,但是没有对 size++,线程 B 就已经读取 size 了,那么对于线程 B 来说读取到的 size 一定是不准确的。而给 size() 方法加了同步之后,意味着线程 B 调用 size() 方法只有在线程 A 调用 put 方法完毕之后才
可以调用,这样就保证了线程安全性

CPU 执行代码,执行的不是 Java 代码,这点很关键,一定得记住。Java 代码最终是被翻译成机器码执行的,机器码才是真正可以和硬件电路交互的代码。即使你看到 Java 代码只有一行,甚至你看到 Java 代码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。一句 return count 假设被翻译成了三句汇编语句执行,一句汇编语句和其机器码做对应,完全可能执行完第一句,线程就切换了。

线程类的构造方法、静态块是被哪个线程调用的

线程类的构造方法、静态块是被 new 这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。

假设 Thread2 中 new 了 Thread1,main 函数中 new 了 Thread2,那么:

  • Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run() 方法是 Thread2 自己调用的
  • Thread1 的构造方法、静态块是 Thread2 调用的,Thread1 的 run() 方法是 Thread1 自己调用的

同步方法和同步块,哪个是更好的选择

同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。

虽说同步的范围越少越好,但是在 Java 虚拟机中还是存在着一种叫做锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说 StringBuffer,它是一个线程安全的类,自然最常用的 append() 方法是一个同步方法,我们写代码的时候会反复 append 字符串,这意味着要进行反复的加锁->解锁,这对性能不利,因为这意味着 Java 虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此 Java 虚拟机会将多次 append 方法调用的代码进行一个锁粗化的操作,将多次的 append 的操作扩展到 append 方法的头尾,变成一个大的同步块,这样就减少了加锁-->解锁的次数,有效地提升了代码执行的效率。

高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

  • 高并发、任务执行时间短的业务,线程池线程数可以设置为 CPU 核数+1,减少线程上下文的切换
  • 并发不高、任务执行时间长的业务要区分开看:
    • 假如是业务时间长集中在 IO 操作上,也就是 IO 密集型的任务,因为 IO 操作并不占用 CPU,所以不要让所有的 CPU 闲下来,可以加大线程池中的线程数目,让 CPU 处理更多的业务
    • 假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
  • 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考其他有关线程池的文章。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

怎么终止一个线程?如何优雅地终止线程

通过 stop 方法可以很快速、方便地终止一个线程 但是不建议使用 jdk 已经被标为废弃的方法只需要添加一个变量,判断这个变量在某个值的时候就退出循环,这时候每个循环为一个整合不被强行终止就不会影响单个业务的执行结果。

一个线程的生命周期有哪几种状态?它们之间如何流转的?

  • NEW:毫无疑问表示的是刚创建的线程,还没有开始启动。
  • RUNNABLE: 表示线程已经触发 start() 方式调用,线程正式启动,线程处于运行中状态。
  • BLOCKED:表示线程阻塞,等待获取锁,如碰到 synchronized、lock 等关键字等占用临界区的情况,一旦获取到锁就进行 RUNNABLE 状态继续运行。
  • WAITING:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒 通过相关事件唤醒线程,线程就进入了 RUNNABLE 状态继续运行。
  • TIMED_WAITING:表示线程进入了一个有时限的等待,如 sleep(3000),等待 3 秒后线程重新进行 RUNNABLE 状态继续运行。
  • TERMINATED:表示线程执行完毕后,进行终止状态

什么是死锁?如何避免死锁?

  • 死锁就是两个线程相互等待对方释放对象锁。
  • 按顺序加锁,每个获取锁的时候加上个时限,按线程间获取锁的关系检测线程间是否发生死锁,如果发生死锁就执行一定的策略,如终断线程或回滚操作等。

常用的几种线程池并讲讲其中的工作原理。

  • newFixedThreadPool: 固定线程池,核心线程数和最大线程数固定相等,而空闲存活时间为 0 毫秒 当执行任务时,如果线程都很忙,就会丢到工作队列等有空闲线程时再执行,队列满就执行默认的拒绝策略。
  • newCachedThreadPool:带缓冲线程池 从构造看核心线程数为 0,最大线程数为 Integer 最大值大小,超过 0 个的空闲线程在 60 秒后销毁,如果线程池有可用线程则执行任务,没有的话就创建一个来执行,线程池中的线程数不确定,一般建议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。
  • newSingleThreadExecutor:单线程线程池,核心线程数和最大线程数均为 1 意味着每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保证了线程的顺序执行。
    newScheduledThreadPool:调度线程池,即按一定的周期执行任务,即定时任务,对 ThreadPoolExecutor 进行了包装而已。

submit 和 execute 分别有什么区别呢?

  • execute 没有返回值,如果不需要知道线程的结果就使用 execute 方法,性能会好很多。
  • submit 返回一个 Future 对象,如果想知道线程结果就使用 submit 提交,而且它能在主线程中通过 Future 的 get 方法捕获线程中的异常。

CyclicBarrier 和 CountDownLatch 的区别?

  • 都可以用来表示代码运行到某个点上,二者的区别在于
  • CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值-1 而已,该线程继续运行
  • CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务
  • CyclicBarrier 可重用,CountDownLatch 不可重用,计数值为 0 该 CountDownLatch 就不可再用了

什么是活锁、饥饿、无锁、死锁?

  • 死锁:多个线程相互占用对方的资源的锁,而又相互等对方释放锁 若无外力干预 一直阻塞形成死锁
  • 活锁:活锁恰恰与死锁相反,活锁是拿到资源却又相互释放不执行
  • 饥饿:多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿
  • 无锁:即没有对资源进行锁定,即所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。无锁典型的特点就是一个修改操作在一个循环内进行,线程会不断的尝试修改共享资源,如果没有冲突就修改成功并退出否则就会继续下一次循环尝试。所以,如果有多个线程修改同一个值必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。JDK 的 CAS 原理及应用即是无锁的实现。

什么是原子性、可见性、有序性?

  • 原子性:一个线程的操作是不能被其他线程打断,同一时间只有一个线程对一个变量进行操作。
  • 可见性:某个线程修改了某一个共享变量的值,而其他线程是否可以看见该共享变量修改后的值
  • 有序性:程序是按代码顺序执行的,对于单线程来说确实是如此,但在多线程情况下就不是如此了。为了优化程序执行和提高 CPU 的处理性能,JVM 和操作系统都会对指令进行重排 只要不影响当前线程的执行结果。所以,指令重排只会保证当前线程执行结果一致,但指令重排后势必会影响多线程的执行结果。

什么是守护线程?有什么用?

  • 守护线程就是守护用户线程,当用户线程全部执行完结束之后,守护线程才会跟着结束。也就是守护线程必须伴随着用户线程,如果一个应用内只存在一个守护线程,没有用户线程,守护线程自然会退出

yield() 方法有什么用?

  • Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定能占用 CPU,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。

Fork/Join 框架是干什么的?

  • Fork/Join 框架是 Java7 提供的并行执行任务框架,思想是将大任务分解成小任务,然后小任务又可以继续分解,然后每个小任务分别计算出结果再合并起来,最后将汇总的结果作为大任务结果

同步方法和同步代码块的区别是什么?

  • 同步方法默认用 this 或者当前类 class 对象作为锁;
  • 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生 同步问题的部分代码而不是整个方法;

在监视器(Monitor) 内部,是如何做线程同步的?程序应该做哪种级别的同步?

  • 监视器和锁在 Java 虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许 执行同步代码。

如何确保 N 个线程可以访问 N 个资源同时又不导致死锁

  • 使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出 现死锁了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

甜点

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

lee_heart

文章 0 评论 0

往事如风

文章 0 评论 0

春风十里

文章 0 评论 0

纸短情长

文章 0 评论 0

qq_pdEUFz

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文