程序员的100大Java多线程面试问题及答案(一)

简介: 程序员的100大Java多线程面试问题及答案(一)

1.什么是进程?

进程是系统中正在运行的一个程序,程序一旦运行就是进程。

进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。

2.什么是线程?

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

3.线程的实现方式?

1.继承Thread类

2.实现Runnable接口

3.使用Callable和Future

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

1.start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。

2.run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

5.线程NEW状态

new创建一个Thread对象时,并没处于执行状态,因为没有调用start方法启动改线程,那么此时的状态就是新建状态。

6.线程RUNNABLE状态

线程对象通过start方法进入runnable状态,启动的线程不一定会立即得到执行,线程的运行与否要看cpu的调度,我们把这个中间状态叫可执行状态(RUNNABLE)。

7.线程的RUNNING状态

一旦cpu通过轮询货其他方式从任务可以执行队列中选中了线程,此时它才能真正的执行自己的逻辑代码。

8.线程的BLOCKED状态

线程正在等待获取锁。

  • 进入BLOCKED状态,比如调用了sleep,或者wait方法
  • 进行某个阻塞的io操作,比如因网络数据的读写进入BLOCKED状态
  • 获取某个锁资源,从而加入到该锁的阻塞队列中而进入BLOCKED状态

9.线程的TERMINATED状态

TERMINATED是一个线程的最终状态,在该状态下线程不会再切换到其他任何状态了,代表整个生命周期都结束了。

下面几种情况会进入TERMINATED状态:

  • 线程运行正常结束,结束生命周期
  • 线程运行出错意外结束
  • JVM Crash 导致所有的线程都结束

10.线程状态转化图

11.i——————与System.out.println()的异常

示例代码:

public class XkThread extends Thread {
    private int i = 5;
    @Override
    public void run() {
       System.out.println("i=" + (i——————) + " threadName=" + Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        XkThread xk = new XkThread();
        Thread t1 = new Thread(xk);
        Thread t2 = new Thread(xk);
        Thread t3 = new Thread(xk);
        Thread t4 = new Thread(xk);
        Thread t5 = new Thread(xk);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

结果:

i=5 threadName=Thread-1
i=2 threadName=Thread-5
i=5 threadName=Thread-2
i=4 threadName=Thread-3
i=3 threadName=Thread-4

虽然println()方法在内部是同步的,但i——————的操作却是在进入println()之前发生的,所以有发生非线程安全的概率。

println()源码:

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

12.如何知道代码段被哪个线程调用?

System.out.println(Thread.currentThread().getName());

13.线程活动状态?

public class XKThread extends Thread {
    @Override
    public void run() {
        System.out.println("run run run is "  + this.isAlive() );
    }
    public static void main(String[] args) {
        XKThread xk = new XKThread();
        System.out.println("begin ——— " + xk.isAlive());
        xk.start();
        System.out.println("end ————— " + xk.isAlive());
    }
}

14.sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前的“正在执行的线程”休眠(暂停执行)。

15.如何优雅的设置睡眠时间?

jdk1.5 后,引入了一个枚举TimeUnit,对sleep方法提供了很好的封装。

比如要表达2小时22分55秒899毫秒。

Thread.sleep(8575899L);
TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(22);
TimeUnit.SECONDS.sleep(55);
TimeUnit.MILLISECONDS.sleep(899);

可以看到表达的含义更清晰,更优雅。

16.停止线程

run方法执行完成,自然终止。

stop()方法,suspend()以及resume()都是过期作废方法,使用它们结果不可预期。

大多数停止一个线程的操作使用Thread.interrupt()等于说给线程打一个停止的标记, 此方法不回去终止一个正在运行的线程,需要加入一个判断才能可以完成线程的停止。

17.interrupted 和 isInterrupted

interrupted : 判断当前线程是否已经中断,会清除状态。

isInterrupted :判断线程是否已经中断,不会清除状态。

18.yield

放弃当前cpu资源,将它让给其他的任务占用cpu执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片。

测试代码:(cpu独占时间片)

public class XKThread extends Thread {
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用时 = " + (endTime - beginTime) + " 毫秒! ");
    }
    public static void main(String[] args) {
        XKThread xkThread = new XKThread();
        xkThread.start();
    }
}

结果:

用时 = 20 毫秒! 

加入yield,再来测试。(cpu让给其他资源导致速度变慢)

public class XKThread extends Thread {
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++) {
            Thread.yield();
            count = count + (i + 1);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用时 = " + (endTime - beginTime) + " 毫秒! ");
    }
    public static void main(String[] args) {
        XKThread xkThread = new XKThread();
        xkThread.start();
    }
}

结果:

用时 = 38424 毫秒! 

19.线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到cpu资源比较多,也就是cpu有限执行优先级较高的线程对象中的任务,但是不能保证一定优先级高,就先执行。

Java的优先级分为1~10个等级,数字越大优先级越高,默认优先级大小为5。超出范围则抛出:java.lang.IllegalArgumentException。

20.优先级继承特性

线程的优先级具有继承性,比如a线程启动b线程,b线程与a优先级是一样的。

21.谁跑的更快?

设置优先级高低两个线程,累加数字,看谁跑的快,上代码。

public class Run extends Thread{
    public static void main(String[] args) {
        try {
            ThreadLow low = new ThreadLow();
            low.setPriority(2);
            low.start();
            ThreadHigh high = new ThreadHigh();
            high.setPriority(8);
            high.start();
            Thread.sleep(2000);
            low.stop();
            high.stop();
            System.out.println("low  = " + low.getCount());
            System.out.println("high = " + high.getCount());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadHigh extends Thread {
    private int count = 0;
    public int getCount() {
        return count;
    }
    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}
class ThreadLow extends Thread {
    private int count = 0;
    public int getCount() {
        return count;
    }
    @Override
    public void run() {
        while (true) {
            count++;
        }
    }
}

结果:

low  = 1193854568
high = 1204372373

22.线程种类

Java线程有两种,一种是用户线程,一种是守护线程。

23.守护线程的特点

守护线程是一个比较特殊的线程,主要被用做程序中后台调度以及支持性工作。当Java虚拟机中不存在非守护线程时,守护线程才会随着JVM一同结束工作。

24.Java中典型的守护线程

GC(垃圾回收器)

25.如何设置守护线程

Thread.setDaemon(true)

PS:Daemon属性需要再启动线程之前设置,不能再启动后设置。

25.Java虚拟机退出时Daemon线程中的finally块一定会执行?

Java虚拟机退出时Daemon线程中的finally块并不一定会执行。

代码示例:

public class XKDaemon {
    public static void main(String[] args) {
        Thread thread = new Thread(new DaemonRunner(),"xkDaemonRunner");
        thread.setDaemon(true);
        thread.start();
    }
    static class DaemonRunner implements Runnable {
        @Override
        public void run() {
            try {
                SleepUtils.sleep(10);
            } finally {
                System.out.println("Java daemonThread finally run …");
            }
        }
    }
}

结果:


         

没有任何的输出,说明没有执行finally。

26.设置线程上下文类加载器

获取线程上下文类加载器

public ClassLoader getContextClassLoader() 

设置线程类加载器(可以打破Java类加载器的父类委托机制)

public void setContextClassLoader(ClassLoader cl)

27.join

join是指把指定的线程加入到当前线程,比如join某个线程a,会让当前线程b进入等待,直到a的生命周期结束,此期间b线程是处于blocked状态。

28.什么是synchronized?

synchronized关键字可以时间一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象是对多个线程可见的,那么对该对想的所有读写都将通过同步的方式来进行。

29.synchronized包括哪两个jvm重要的指令?

monitor enter 和 monitor exit

30.synchronized关键字用法?

可以用于对代码块或方法的修饰

31.synchronized锁的是什么?

普通同步方法 —————> 锁的是当前实例对象。

静态同步方法—————> 锁的是当前类的Class对象。

同步方法快 —————> 锁的是synchonized括号里配置的对象。

32.Java对象头

synchronized用的锁是存在Java对象头里的。对象如果是数组类型,虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,用2字宽存储对象头。

Tips:32位虚拟机中一个字宽等于4字节。

33.Java对象头长度

34.Java对象头的存储结构

32位JVM的Mark Word 默认存储结构

35.Mark Word的状态变化

Mark Word 存储的数据会随着锁标志为的变化而变化。

64位虚拟机下,Mark Word是64bit大小的

36.锁的升降级规则

Java SE 1.6 为了提高锁的性能。引入了“偏向锁”和轻量级锁“。

Java SE 1.6 中锁有4种状态。级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。

锁只能升级不能降级。

37.偏向锁

大多数情况,锁不仅不存在多线程竞争,而且总由同一线程多次获得。当一个线程访问同步块并获取锁时,会在对象头和栈帧中记录存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行 cas操作来加锁和解锁,只需测试一下对象头 Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁,如果失败,则需要测试下Mark Word中偏向锁的标示是否已经设置成1(表示当前时偏向锁),如果没有设置,则使用cas竞争锁,如果设置了,则尝试使用cas将对象头的偏向锁只想当前线程。

38.关闭偏向锁延迟

java6和7中默认启用,但是会在程序启动几秒后才激活,如果需要关闭延迟,

-XX:BiasedLockingStartupDelay=0。

39.如何关闭偏向锁

JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

Tips:如果你可以确定程序的所有锁通常情况处于竞态,则可以选择关闭。

40.轻量级锁

线程在执行同步块,jvm会现在当前线程的栈帧中创建用于储存锁记录的空间。并将对象头中的Mark Word复制到锁记录中。然后线程尝试使用cas将对象头中的Mark Word替换为之乡锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

41.轻量锁的解锁

轻量锁解锁时,会使原子操作cas将 displaced Mark Word 替换回对象头,如果成功则表示没有竞争发生,如果失败,表示存在竞争,此时锁就会膨胀为重量级锁。

42.锁的优缺点对比

43.什么是原子操作

不可被中断的一个或一系列操作

44.Java如何实现原子操作

Java中通过锁和循环cas的方式来实现原子操作,JVM的CAS操作利用了处理器提供的CMPXCHG指令来实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。

45.CAS实现原子操作的3大问题

ABA问题,循环时间长消耗资源大,只能保证一个共享变量的原子操作

46.什么是ABA问题

问题:

因为cas需要在操作值的时候,检查值有没有变化,如果没有变化则更新,如果一个值原来是A,变成了B,又变成了A,那么使用cas进行检测时会发现发的值没有发生变化,其实是变过的。

解决:

添加版本号,每次更新的时候追加版本号,A-B-A —> 1A-2B-3A。

从jdk1.5开始,Atomic包提供了一个类AtomicStampedReference来解决ABA的问题。

47.CAS循环时间长占用资源大问题

如果jvm能支持处理器提供的pause指令,那么效率会有一定的提升。

一、它可以延迟流水线执行指令(de-pipeline),使cpu不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,有些处理器延迟时间是0。

二、它可以避免在退出循环的时候因内存顺序冲突而引起的cpu流水线被清空,从而提高cpu执行效率。

48.CAS只能保证一个共享变量原子操作

一、对多个共享变量操作时,可以用锁。

二、可以把多个共享变量合并成一个共享变量来操作。比如,x=1,k=a,合并xk=1a,然后用cas操作xk。

Tips:java 1.5开始,jdk提供了AtomicReference类来保证饮用对象之间的原子性,就可以把多个变量放在一个对象来进行cas操作。

49.volatile关键字

volatile 是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性“。

Java语言规范第3版对volatile定义如下,Java允许线程访问共享变量,为了保证共享变量能准确和一致的更新,线程应该确保排它锁单独获得这个变量。如果一个字段被声明为volatile,Java线程内存模型所有线程看到这个变量的值是一致的。

50.等待/通知机制

一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作。

方法wait()的作用是使当前执行代码的线程进行等待,wait()是Object类通用的方法,该方法用来将当前线程置入“预执行队列”中,并在 wait()所在的代码处停止执行,直到接到通知或中断为止。

在调用wait之前线程需要获得该对象的对象级别的锁。代码体现上,即只能是同步方法或同步代码块内。调用wait()后当前线程释放锁。

程序员的100大Java多线程面试问题及答案(二):https://developer.aliyun.com/article/1416655

相关文章
|
2天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
15 9
|
1天前
|
SQL 存储 Java
面向 Java 程序员的 SQLite 替代品
SQLite 是轻量级数据库,适用于小微型应用,但其对外部数据源支持较弱、无存储过程等问题影响了开发效率。esProc SPL 是一个纯 Java 开发的免费开源工具,支持标准 JDBC 接口,提供丰富的数据源访问、强大的流程控制和高效的数据处理能力,尤其适合 Java 和安卓开发。SPL 代码简洁易懂,支持热切换,可大幅提高开发效率。
|
5天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
2天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
4天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
5天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
14 1
|
6月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
55 0
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
63 1
|
4月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
90 0