java面试突破 - 并发编程面试汇总

Java高薪面试:如何突破BAT面试门槛 开篇词 Java高薪面试:如何突破BAT面试门槛》是一本JAVA程序员必读的书籍,本书讲解了如何突破BAT公司招聘门槛,提高JAVA程序员的面试技巧,增加面试成功率,获取高薪工作的方法和技巧。Java并发编程是Java面试中非常重要的考察点,本文分享了Java并发编程的相关知识和常见面试题,帮助读者加深对Java并发编程的理解。Java网络编程是Java程序员必须掌握的技能之一,本文介绍了Java网络编程的基础知识和应用场景,帮助读者掌握Java网络编程技巧。#第三篇:注解在Java面试中的应用及注意事项# 阅读详情

目录

1. 多线程有什么用?(为什么用它)

  • 发挥多核CPU的优势!

单核CPU:所谓的多线程是假的!同一时间只会处理一段逻辑,只不过线程切换的快!像多个线程同时运行!

多核CPU:真正的多线程!多段逻辑同时工作!充分利用CPU!

  • 防止阻塞

单CPU时,线程间的上下文切换,反而会降低所谓多线程的效率!但是还是使用多线程,就是为了防止阻塞!(防止某一线程超时!)

  • 便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。(大任务分解)

2. 多线程和单线程的区别和联系?

  1. 在单核CPU中,将CPU分成很小的时间片!在每个时刻只能有一个线程执行,是一种微观上轮流占用CPU的机制!

  1. 多线程会存在上下文切换!导致程序变慢;即采用一个拥有两个线程的进程执行所需的时间比一个线程的进程执行两次所需要的时间要多!
  • 结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。

3. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?

  • 线程

与进程相似,一个进程在执行过程中可以产生多个线程!
不同点:同类的多个线程共享同一块内存空间和一组系统资源;所以系统在产生线程和线程的切换时,负担比进程小。所以线程被称为轻量级进程!

  • 程序

是含有指令和数据的文件!被存储在磁盘或者其他数据存储设备当中,可以说是静态的代码!

  • 进程

与程序的关系:
是程序的一次执行过程,是系统运行程序的基本单位!动态的!
系统运行一个程序 就是 一个进程从 创建、运行、消亡的过程!(一个进程就是一个执行中的程序)
每个进程占有某些系统资源(CPU时间、内存空间、文件、输入输出设备的使用权等);当程序运行时,会被操作系统载入内存空间!

与线程的关系:
线程是进程划分为更小的运行单位。
最大的不同:进程基本上是独立的,而线程则不一定,同一个进程的线程间可能会相互影响!
另一个不同:
进程属于操作系统范畴,同一时间段,可以执行一个以上的程序;
线程在同一程序内几乎同时执行一个以上的程序段!

4. 线程的创建方式

七种方法:后面带了相应方法的例子

  1. 继承Thread类,作为线程对象存在
  2. 实现runnable接口,作为线程任务存在
  3. 匿名内部类创建线程对象
  4. 创建带返回值的线程
  5. 定时器Timer
  6. 线程池创建线程
  7. 利用java8新特性 stream 实现并发
  • 方法一:继承Thread类,作为线程对象存在(继承Thread对象)
public class CreatThreadDemo1 extends Thread{
	/**
	* 构造方法: 继承父类方法的Thread(String name);方法
	* @param name
	*/
	public CreatThreadDemo1(String name){
		super(name);
	}
	
	@Override
	public void run() {
		//interrupted方法,是来判断该线程是否被中断。
		while (!interrupted()){
			System.out.println(getName()+"线程执行了...");
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
		CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
		
		d1.start();
		d2.start();
		//(终止线程不允许用stop方法,该方法不会施放占用的资源。)
		d1.interrupt(); //中断第一个线程
	}
}
  • 让线程等待的方法
  • Thread.sleep(200); //线程休息2ms
  • Object.wait(); //让线程进入等待,直到调用Object的 notify 或者 notifyAll 时,线程停止休眠
  • 方法二:实现runnable接口,作为线程任务存在
public class CreatThreadDemo2 implements Runnable {
	@Override
	public void run() {
		while (true){
			System.out.println("线程执行了...");
		}
	}
	public static void main(String[] args) {
		//将线程任务传给线程对象
		Thread thread = new Thread(new CreatThreadDemo2());
		//启动线程
		thread.start();
	}
}

Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里。

  • 方法三:匿名内部类创建线程对象
public class CreatThreadDemo3 extends Thread{
	public static void main(String[] args) {
		//创建无参线程对象
		new Thread(){
			@Override
			public void run() {
				System.out.println("线程执行了...");
			}
		}.start();
		//创建带线程任务的线程对象
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程执行了...");
			}
		}).start();
		//创建带线程任务并且重写run方法的线程对象
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("runnable run 线程执行了...");
			}
		}){
			@Override
			public void run() {
				System.out.println("override run 线程执行了...");
			}
		}.start();
	}
}

创建带线程任务并且重写run方法的线程对象中:为什么只运行了Thread的run方法?
源码:在这里插入图片描述
原因:Thread实现了Runnable接口,而Runnable接口里有一个run方法。
结果:匿名内部类创建线程对象的第三种,会调用Thread类的run方法。而不是Runnable接口的run方法。(输出:override run 线程执行了…)

  • 方法四:创建带返回值的线程
public class CreatThreadDemo4 implements Callable {
	public static void main(String[] args) throws ExecutionException,InterruptedException {
		CreatThreadDemo4 demo4 = new CreatThreadDemo4();
		
		FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最终实现的是runnable接口
	
		Thread thread = new Thread(task);
		thread.start();
		System.out.println("我可以在这里做点别的业务逻辑...因为FutureTask是提前完成任
	务");
		//拿出线程执行的(call方法)返回值
		Integer result = task.get();
		System.out.println("线程中运算的结果为:"+result);
	}
	
	//重写Callable接口的call方法
	@Override
	public Object call() throws Exception {
		int result = 1;
		System.out.println("业务逻辑计算中...");
		Thread.sleep(3000);
		return result;
	}
}

Callable接口介绍:

public interface Callable<V> {
	/**
	 * Computes a result, or throws an exception if unable to do so.
	 * 计算结果,或者抛异常(无法计算时)
	 * @return computed result
	 * @throws Exception if unable to compute a result
	 */
	V call() throws Exception;
}

返回指定泛型的call方法。然后调用FutureTask对象的get方法得到call方法的返回值。

  • 方法五:定时器Timer
public class CreatThreadDemo5 {
	public static void main(String[] args) {
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println("定时器线程执行了...");
			}
		},0,1000); //延迟0,周期1s
	}
}
  • 方法六:线程池创建线程
public class CreatThreadDemo6 {
	public static void main(String[] args) {
		//创建一个具有10个线程的线程池
		ExecutorService threadPool = Executors.newFixedThreadPool(10);
		long threadpoolUseTime = System.currentTimeMillis();
		for (int i = 0;i<10;i++){
			threadPool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName()+"线程执行了...");
				}
			});
		}
		long threadpoolUseTime1 = System.currentTimeMillis();
		System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
		//销毁线程池
		threadPool.shutdown();
		threadpoolUseTime = System.currentTimeMillis();
	}
}
  • 方法七:利用java8新特性 stream 实现并发

5. 线程有哪些基本状态?

操作系统中 有 三态模型 ->五态模型->七态模型;通常说的是五态模型。

  • 三态模型
    在这里插入图片描述
  • 五态模型

在这里插入图片描述

  • 七态模型
    在这里插入图片描述

6. 如何停止一个正在运行的线程?

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
3、使用interrupt方法中断线程。

7. start()方法和run()方法的区别?

  • 区别:

1、start方法 用来启动相应的线程;
2、run方法 只是thread的一个普通方法,在主线程里执行;
3、需要并行处理的代码放在run方法中,start方法 启动线程后 自动调用 run方法;
4、run方法必须是public的访问权限,返回类型为void。

理解:

run() 是由JVM直接调用的,如果没有启动线程(调用start()方法),就直接调用run(),那么这个run方法其实是运行在当前线程中(如:main中),而不是之前定义的线程中!

  • 总结

只有调用了start()方法,才体现多线程的特性,不同线程里的run()中的代码交替执行!
如果只调用run(),那么代码还是同步代码,必须一个run结束才运行下一个run()。

8. Runnable接口和Callable接口的区别

  • 返回值不同

1、Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
2、Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

利用这一点,可以解决这些问题:

某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?

Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。

9. 什么是线程安全?

多线程访问同一段代码,不会产生不确定的结果!

  • 数据不共享(private成员),一定线程安全!
  • 同步控制:synchronized……

如何确保线程安全?

对非安全的代码进行加锁控制
使用线程安全的类
多线程并发情况下,线程共享的变量改为方法级的局部变量

10. 在多线程中,什么是上下文切换(context-switching)?

单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。(一段代码在分配到的时间片内没有完成,从运行->就绪;直到下一次调用,此时时间片分给另一段程序,发生上下文切换)

上下文切换时:要保存当前线程运行的位置到内存;并从内存中加载需要恢复的线程的信息!(耗时)

11. 什么是竞态条件?你怎样发现和解决竞争?

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。

解决:

在临界区中使用适当的同步就可以避免竞态条件。
一种是用synchronized,一种是用Lock显式锁实现。

12. 用户线程和守护线程有什么区别?

守护(Daemon)线程:运行在后台,为JVM中所有非守护(前台)线程的运行提供便利服务**(佣人)**:只要当前 JVM 实例中 尚存任何一个非守护线程没有结束,守护线程就全部工作;只有 最后一个非守护线程结束时,守护线程随着JVM一同结束工作!

用户(User)线程:运行在前台,执行具体的任务。如:main、连接网络的子线程等!

例如:main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

  • 区别:用户线程结束,JVM退出(不管有没有守护线程在运行)。守护线程不影响JVM的退出!(因为没有被守护者,Daemon也就没有运行的必要了)

  • 注意事项:

  1. setDaemon(true)必须在start()方法前执行,否则会抛出 IllegalThreadStateException 异常
  2. 在守护线程中产生的新线程也是守护线程
  3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑
  4. 守护 (Daemon) 线程中不能依靠 finally 块的内容来确保执行关闭或清理资源的逻辑。因为我们上面也说过了一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作,所以守护 (Daemon) 线程中的 finally 语句块可能无法被执行。

13. 如何创建守护线程?以及在什么场合来使用它?

任何线程都可以被设置为守护线程和用户线程;

Thread.setDaemon(boolean);
//true则把该线程设置为守护线程,
//反之则为用户线程。

Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。

守护线程相当于后台管理者 比如 : 进行内存回收,垃圾清理等工作

14. 线程安全的级别

  1. 不可变
    不可变得对象一定是线程安全的,并且永远不需要额外的同步!

Java类库中大多数基本数值类如Integer、String和BigInteger都是不可变的。

  1. 无条件的线程安全
    由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要额外的同步

如 Random 、ConcurrentHashMap、Concurrent集合、atomic

  1. 有条件的线程安全
    对于单独的操作可以是线程安全的,但是某些操作序列可能需要额外同步。

有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器

  1. 非线程安全(线程兼容)
    不是线程安全的,但是可以通过正确使用同步而在并发环境下安全使用!

如ArrayList 、 HashMap

  1. 线程对立
    不管是否采用同步措施,都不能在多线程环境中并发的使用!

如System.setOut()、System.runFinalizersOnExit()

15. 什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

  • 线程调度器

是一个操作系统服务,负责为Runnable 状态的线程分配CPU时间!一旦创建一个线程并启动它,他的执行便依赖于线程调度器的实现!

  • 时间分片

指将可用的CPU时间分配给可用的Runnable线程的过程!(可以基于线程优先级、线程等待时间)

线程调度并不受JVM控制,所以由应用程序来控制他是更好的选择!

16. volatile关键字的作用

一旦一个共享变量(类的成员变量、静态成员变量) 被 volatile 修饰后,有两层语义:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  • 禁止进行指令重排序。

volatile 与 synchronized 的区别!

锁、(when)什么地方使用、修改可见性+原子性、是否优化

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
    synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;
    synchronized则可以使用在变量、方法、和类级别的。
  • volatile仅能实现变量的修改可见性,并不能保证原子性;
    synchronized则可以保证变量的修改可
    见性和原子性
    。
  • volatile不会造成线程的阻塞;
    synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;
    synchronized标记的变量可以被编译器优化。

从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic 包下的类,比如 AtomicInteger 。

17. volatile 变量和 atomic 变量有什么不同?

二者相似,但是功能不同!

  • Volatile变量:可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。
    例如:用volatile修饰count变量那么 count++ 操作就不是原子性的。
  • atomic:AtomicInteger类提供的atomic方法可以让这种操作具有原子性
    如:getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。

18. volatile 是什么?可以保证有序性吗?

参考16题的两层语义!volatile 不是原子性操作

  • 什么叫保证部分有序性?

当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

x = 2; 		 //语句1
y = 0;		 //语句2
flag = true; //语句3
x = 4; 		 //语句4
y = -1; 	 //语句5

1、2 在 3 之前;4、5 在 3 之后;
但是 1、2 与 4、5 的顺序由于不是volatile的,所以不确定!

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

  • 使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁

19. 什么是Java内存模型?

Java Memory Model,JMM
定义了一种多线程访问Java内存的规范!

  1. JMM将内存分为了主内存和工作内存!类的状态,也就是类之间共享的的变量,是存储在主内存中的,每次Java线程 用到这些变量时,会读一次主内存中的变量,并让这些内存在自己的工作内存中拷贝一份,运行时操作的都是自己工作内存中的,线程结束后,将值更新到主内存中!
  2. 定义了几个原子操作,用于操作主内存和工作内存中的变量!
  3. 定义了volatile变量的使用规则!(屏障)
  4. happens-before,即先行发生原则,定义了操作A 必然先行发生于 操作B 的一些规则!
    比如:在同一个线程中,控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于 后面对于同一个锁进行 lock 的动作 等等
    只要符合这些规则,则不需要额外的同步措施,如果某段代码不符合所有的 happens-before 规则,则这段代码一定是线程非安全的!

20. sleep方法和wait方法有什么区别?

  1. sleep()属于 Thread类;wait()属于Object类
  2. sleep 导致程序暂停(指定时间),把CPU让给其他线程,但是监控状态依旧保持,当达到指定的时间到了又会恢复运行状态;此过程不会释放锁!
    wait方法,线程会放弃对象锁,进入等待此对象的等待锁定池,只有调用notify() 后才进入对象锁定池等待重新获得锁!

21. 线程的sleep()方法和yield()方法有什么区别?

  1. sleep让CPU时不考虑优先级,直接让出!
    yield 只会给相同优先级或更高优先级的线程以运行的机会!
  2. 线程执行sleep()后进入阻塞(blocked)状态,而执行yield()后进入就绪(ready)状态!
  3. sleep()声明抛出InterruptedException,而yield()没有声明异常!
  4. sleep 比 yield 具有更好的可移植性!(与CPU调度有关)

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

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

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

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

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

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

24. 在线程中你怎么处理不可控制异常?

Java中有两种异常

  1. 非运行时异常(Checked Exception):这种异常必须在方法声明的throws语句指定,或者在方法体内捕获。例如:IOException和ClassNotFoundException。
  2. 运行时异常(Unchecked Exception):这种异常不必在方法声明中指定,也不需要在方法体中捕获。例如,NumberFormatException。

因为run()不支持 throws语句,所以当 线程对象的 run 方法 抛出 非运行异常时,我们必须捕获并且处理他。
当线程抛出一个未捕获的异常时,JVM将有以下处理方法:

首先,查找线程对象的 未捕获异常处理器。
如果找不到,继续找线程对象所在的线程组(ThreadGroup)的未捕获异常处理器。
如果还没找到,JVM 将继续查找
默认的未捕获异常处理器
。
如果没有一个处理器存在。JVM将执行默认行为:将堆栈异常记录打印到控制台,并退出程序!

  • 未捕获异常处理器的使用:类实现UncaughtExceptionHandler接口并且实现这个接口的uncaughtException()方法
package concurrency;
import java.lang.Thread.UncaughtExceptionHandler;
public class Main2 {
	public static void main(String[] args) {
	Task task = new Task();
	Thread thread = new Thread(task);
	thread.setUncaughtExceptionHandler(new ExceptionHandler());
	thread.start();
	}
}
class Task implements Runnable{
	@Override
	public void run() {
		int numero = Integer.parseInt("TTT");
	}
}
class ExceptionHandler implements UncaughtExceptionHandler{
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.printf("An exception has been captured\n");
		System.out.printf("Thread: %s\n", t.getId());
		System.out.printf("Exception: %s: %s\n",
		e.getClass().getName(),e.getMessage());
		System.out.printf("Stack Trace: \n");
		e.printStackTrace(System.out);
		System.out.printf("Thread status: %s\n",t.getState());
	}
}

  • 默认未捕获异常处理器的定义:

Thread类还有另一个方法可以处理未捕获到的异常,即静态方法setDefaultUncaughtExceptionHandler()。这个方法在应用程序中为所有的线程对象创建了一个异常处理器。

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

原则:同步的范围越小越好!

同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。

但是存在锁粗化现象!

例如:
说StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁–>解锁的次数,有效地提升了代码执行的效率。(同一个操作一直加锁解锁,明显没必要,浪费性能!)

26. 什么是CAS?

CAS,全称为Compare and Swap,即比较-替换。

假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。

27. CAS 有什么缺陷,如何解决?

在这里插入图片描述

  • ABA问题

现象:并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。

解决:可以通过AtomicStampedReference「解决ABA问题」,它,一个带有标记的原子引用类,通过控制变量值的版本来保证CAS的正确性。(给值加一个修改版本号)

  • 循环开销时间长

自旋CAS,如果一直循环执行,一直不成功,会给CPU带来非常大的开销!

解决:CAS思想体现,有一个自旋次数,就是为了避开这个耗时问题……

  • 只能保证一个变量的原子操作

问题:CAS只能对一个变量执行操作的原子性,如果对多个变量,CAS目前无法直接保证操作的原子性!

解决:(两种)
使用互斥锁来保证原子性;
将多个变量封装成对象,通过AtomicReference来保证原子性!

28. 什么是AQS?

AQS : AbstractQueuedSynchronizer(抽象队列同步器)
CAS 是 java.util.concurrent 的基础;AQS 是java并发包的 核心。ReentrantLock、CountDownLatch、Semaphore 等等都用到了AQS;

  • 本质

以双向队列的形式连接所有的Entry,比方说 ReentrantLock,所有等待的线程都被放在一个Entry中并联成双向队列。

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

29. 线程池的作用

(可联系到如何用、好处、启动策略)
合理使用线程池带来的三个好处。

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等待线程的创建就立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控!

30. ThreadLocal 是什么?

线程本地变量,指 ThreadLocal 中填充的变量属于 当前线程,该变量对其他线程是隔离的。ThreadLocal 为变量在每个线程中都创建副本,每个线程可以访问自己内部的副本变量。(常问)

//创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
  • 使用场景
  1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  2. 线程间数据隔离。
  3. 进行事务操作,用于存储线程事务信息。
  4. 数据库连接,Session会话管理。

有什么用?

以空间换时间,每个Thread里面维护一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,排除线程安全问题。

ThreadLocal 原理

内存结构图:

在这里插入图片描述
由结构图可以看出:

  • Thread 对象中持有一个 ThreadLocal.ThreadLocalMap的 成员变量。
  • ThreadLocalMap 内部维护了 Entry数组,每个Entry 代表一个完整的对象, key 是ThreadLocal 本身,value 是 ThreadLocal 的泛型值。

部分源码:

public class Thread implements Runnable {
	//ThreadLocal.ThreadLocalMap是Thread的属性
	ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal中的关键方法set()和get():

public void set(T value) {
	Thread t = Thread.currentThread(); //获取当前线程t
	ThreadLocalMap map = getMap(t); //根据当前线程获取到ThreadLocalMap
	if (map != null)
		map.set(this, value); //K,V设置到ThreadLocalMap中
	else
		createMap(t, value); //创建一个新的ThreadLocalMap
}
public T get() {
	Thread t = Thread.currentThread();//获取当前线程t
	ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap
	if (map != null) {
		//由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

ThreadLocalMap的Entry数组:

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
		/** The value associated with this ThreadLocal. */
		Object value;
		Entry(ThreadLocal<?> k, Object v) {
			super(k);
			value = v;
		}
	}
}
  • 实现原理解答:
  1. Thread类持有一个类型为ThreadLocal.ThreadLocalMap 的 实例变量 ThreadLocals,即每个线程都有属于自己的ThreadLocalMap。
  2. ThreadLocalMap 内部维护着 Entry 数组,每个 Entry 代表一个完整的对象,key 是 ThreadLocal 本身,value 是 ThreadLocal的泛型值。
  3. 每个线程在往ThreadLoacl 里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal 为引用,在自己的map里找对应的可以,从而实现线程隔离!

ThreadLocal 内存泄露问题

  • ThreadLocal 引用示意图:

在这里插入图片描述
ThreadLocalMap 中使用的 key 是 ThreadLocal 的弱引用,容易在JVM内存空间不足时,被回收!
如果被回收,则,Entry 中的 key 不存在,而 value 还在,造成内存泄露的问题!

解决办法:

使用完ThreadLocal 后,及时的调用 remove()方法,释放内存空间!

  • 应用场景

数据库连接池
会话管理中使用!

31. wait()、notify()、notifyAll()

notify()和notifyAll()有什么区别?

  • 共同点:都是Object对象,用于通知正在等待该对象的线程的方法。
  • 不同点:notify 唤醒一个等待该对象的线程;
    notifyAll 唤醒所有等待该对象的线程。(使其重新进入锁的争夺队列)
  • 造成问题:notify 可能会造成死锁。
    正确场景: WaitSet中等待的是相同的条件,唤醒任一个都能正确处理下来的事项,如果唤醒的线程没法处理,务必确保继续notify下一个,并自身重新进入WaitSet。

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

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

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

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

32. 什么是阻塞(Blocking)和非阻塞(Non-Blocking)?

通常用来形容多线程间的相互影响。

比如一个线程占用了临界区资源,那么其他所有需要这个而资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。

非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行。所有的线程都会尝试不断前向执行。

33. 什么是自旋

很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。

既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

34. 自旋锁的优缺点?

自旋锁不会引起调用者休眠,如果自旋锁已经被别的线程保持,调用者就一直循环在那里看是否该自旋锁的保持者释放了锁。由于自旋锁不会引起调用者休眠,所以自旋锁的效率远高于互斥锁。

虽然自旋锁效率比互斥锁高,但它会存在下面两个问题:

1、自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。
2、试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

  • 总结

要慎重使用自旋锁,适合 锁使用者 保持锁时间比较短 并且 锁竞争不激烈的情况。正是由于自旋锁使用者 一般保持锁时间非常短,所以采取自旋而不是睡眠(阻塞)。

35. 常用的线程池模式以及不同线程池的使用场景?

  • 四种Java自带线程池:
  1. newFixedThreadPool 创建一个指定工作线程数量的线程池。
    每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始最大值,则将提交的任务存入到池队列中。
  1. newCachedThreadPool 创建一个可缓存的线程池。
    特点:
  • 工作线程的创建数量几乎没有限制(小于Integer.Max_VALUE),这样可灵活的往线程池添加线程!
  • 如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认1分钟),则该工作线程自动终止。终止后,如果有新任务,则重新创建一个工作线程。
  1. newSingleThreadExecutor 创建一个单线程化的 Executor,即只创建唯一的工作线程来执行任务,如果这个线程异常结束,会有另一个取代他,保证顺序执行!
  • 特点:
    可保证顺序执行,并在任意给定的时间不会有多个线程是活动的。
  1. newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

36. 在Java中Executor、ExecutorService、Executors的区别?

Executor 和 ExecutorService 这两个接口主要的区别是(四个):

  • ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
  • Executor 接口定义了 execute()方法用来接收一个Runnable接口的对象,
    而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable接口的对象。
  • Executor 中的 execute() 方法 不返回任何结果,
    而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果。
  • 除了允许客户端提交一个任务,ExecutorService
    还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池。

Executors 类提供工厂方法用来创建不同类型的线程池。

newSingleThreadExecutor() 创建一个只有一个线程的线程池,
newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池,
newCachedThreadPool()可以根据需要创建新的线程,但如果已有线程是空闲的会重用已有线程。

37. 如何控制某个方法允许并发访问线程的大小?(信号量)

Semaphore 有两个重要方法:

semaphore.acquire() : 请求一个信号量,将信号量减1;(如果变为负,再次请求就会阻塞)

semaphore.release():释放一个信号量,此时信号量个数+1

38. 如何创建线程池?

虽然可以通过Executor 区创建,但是由于他的弊端,一般强制不使用它!

Executors 返回线程池对象的弊端如下:

  • FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
  • CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

方式一:通过Executor框架的工具类Executors来实现

在这里插入图片描述

方式二:通过构造方法(ThreadPoolExecutor )实现
在这里插入图片描述

39. 不同情况怎么使用线程池?

高并发、任务执行时间短

线程池的线程数设置为CPU+1,减少线程上下文切换

并发不高、任务执行时间长

时间集中在IO(IO密集型):因为IO不占用CPU,所以不要让所有CPU闲下来,可以加大线程池中的线程数目,让Cpu处理更多业务

时间集中在计算上(计算密集型):和上一种一样,线程数设置少一点,减少线程上下文切换;

高并发、业务执行时间长

这种更看重整体架构的设计,看能不能做缓存,能不能增加服务器,能不能使用中间件做任务拆分和解耦、最后根据上一种考虑线程池!

40. Java中interrupted 和isInterruptedd方法的区别?

  • 主要区别:前者会将中断状态清除而后者不会。
    Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。

当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。

非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。

  • 注意:一个线程的中断状态可能被其他线程调用中断来改变!

41. 说一说自己对于 synchronized 关键字的了解

解决:多个线程之间访问资源的同步性!
作用:可以保证被他修饰的方法或者代码块在任意时刻只能有一个线程执行!

早期效率低的原因:在Java早期版本中,synchronized 属于重量级锁,因为监视器锁(monitor)是依赖底层的操作系统的 MUtex Lock来实现的,Java的线程是映射到操作系统的原生线程上的。如果挂起、唤醒一个线程,都需要操作系统帮忙,此时线程间的上下文切换(用户态->内核态)耗时;

JDK1.6 优化:自旋锁、适应性自旋锁、锁消除、锁粗化、锁升级(无锁->偏向锁->轻量级锁->重量级锁)……(从JVM层面优化)

42. 怎么使用 synchronized 关键字(三种使用方式)?

  1. 修饰实例方法:对当前对象加锁,进入同步代码块前要获得当前对象的锁!
  2. 修饰静态方法:相当于给类加锁,(区分类和对象!)

例:如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象。

原因:因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

  1. 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
  • 总结

synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
synchronized 关键字加到实例方法上是给对象实例上锁。
尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!

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

线程dump:线程堆栈;死循环、死锁、阻塞、页面打开慢等额问题

  • 获取线程堆栈有两步:
  1. 获取到线程的pid(jps命令/ps -ef | grep java命令)
  2. 打印线程堆栈(jstack pid命令/kill -3 pid)

此外,Thread类提供了getStackTrace()方法来获取线程堆栈!是一个实例方法,与具体实例线程绑定!

44. 如何在两个线程间共享数据?

通过线程之间共享对象就可,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待

阻塞队列(BlockingQueue) 就是为了线程之间共享数据设计的!

第一种:将共享数据封装到一个对象,把这个共享数据所在的对象传递给不同的Runnable(线程)。
第二种:将这些Runnable对象作为一个类的内部类,共享的数据作为外部类的成员变量,对共享数据的操作分配给外部类来完成,以此实现对操作共享数据的互斥和通信,作为内部类的Runnable来操作外部类的方法,实现对数据的操作!

实例:

class ShareData {
	private int x = 0;
	public synchronized void addx(){
		x++;
		System.out.println("x++ : "+x);
	}
	public synchronized void subx(){
		x--;
		System.out.println("x-- : "+x);
	}
}
public class ThreadsVisitData {
	public static ShareData share = new ShareData();
	public static void main(String[] args) {
		//final ShareData share = new ShareData();
		new Thread(new Runnable() {
			public void run() {
				for(int i = 0;i<100;i++){
					share.addx();
				}
			}
		}).start();
		new Thread(new Runnable() {
			public void run() {
				for(int i = 0;i<100;i++){
					share.subx();
				}
			}
		}).start();
	}
}

45. Java中的死锁

多个线程抢夺有限个资源时,会出现死锁现象!

  • 四个必要条件

1.互斥条件:一段时间内,某一资源只能被一个线程占用,其他线程请求需要等待!
2. 请求和保持:一个线程去请求其他资源时,占用原有资源,不释放
3. 不可剥夺:线程已经占用的资源,除非自己使用完主动释放,不能被抢占!
4. 环路等待:a等B,B等C,C又等A

  • 如何避免与检测

预防死锁

  • 破坏互斥条件:资源同时访问而非互斥使用!(行不通,很多时候互斥是固有特性,不能改,打印机?)
  • 破坏请求与保持:只有能全部满足才去拿资源!(资源会闲置,利用率低下)
  • 破坏不可剥夺:获得一些,但另外的无法获得,则释放已占用的(只适用于内存、处理器资源)(拿到了又放弃,耗时!)
  • 破坏环路等待:给资源编号,按顺序依次进行!

银行家算法解决(避免)死锁问题

核心思想:分配资源之前,判断系统是否是安全的;若是,才分配。每分配一次资源就测试一次是否安全,不是资源全部就位后才测试。

具体不细说!

  • 还可以设置加锁顺序,要以相同的顺序获取锁!

46. 什么是可重入锁(ReentrantLock)?

Java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,允许把锁的实现作为Java类。具体的实现在特定的类中。(抽象类的作用)

ReentrantLock 类 实现了 Lock,与synchronized 相同的并发性和内存语义,添加了(锁投票、定时锁、可中断锁等候)等特性;

  • 实际作用:
    ReentrantLock 有一个与锁相关的获取计数器,如果拥有某个锁的线程再次得到锁,则计数器+1,释放时也需要释放两次才能真正释放!

和synchronized 类似,只有退出最外层synchronized 块才释放锁

47. synchronized 关键字的底层原理?

synchronized 关键字底层原理属于 JVM 层面。

通过查看字节码,我们可以知道:

  • 修饰同步代码块
    synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

  • 修饰方法

synchronized 修饰的方法 并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

48. synchronized和ReentrantLock的区别?

  • 相同点:都是可重入锁!

本质区别:synchronized 是关键字,ReentrantLock 是类!

类提供了更加灵活的特性

  • 扩展性体现在:
  1. ReentrantLock 可以对获取锁的等待时间进行设置,可避免死锁!(等待可中断!)
  2. ReentrantLock 可以获取各种锁的信息。
  3. ReentrantLock 可以灵活的实现多路通知!
  • 获取、释放锁不同: synchronized 自动加锁、释放;而 ReentrantLock 需要lock、unlock(手动)。
  • ReentrantLock 可以设置为非公平锁、公平锁;而synchronized 默认为非公平锁!
  • 锁唤醒情况:ReentrantLock 可以根据条件 分组唤醒多个线程!

ReentrantLock实现流程!

  • 非公平锁
  • 上锁
    锁可用:直接CAS持有
    不可用:判断是否可重入:
    ~ 可重入:锁状态+请求数
    ~ 不可重入:CAS循环进入等待队列的队尾!
  • 处理中断:
    由LockSupport类支持,通过JNI 代用本地操作系统 来完成挂起的任务。
    被唤起后,先检查是否中断!

49. ConcurrentHashMap的并发度是什么?

并发度就是 segment 数组(1.8以后是key.value数组)的大小(默认为16)
意味着:最多可同时16条线程操作!

50. ReadWriteLock是什么?

是一个读写锁接口!读锁共享、写锁独占!

51. FutureTask是什么?

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

52. CyclicBarrier(回环栅栏)和CountDownLatch(线程计数器)的区别?

都位于java.util.concurrent包下!

在这里插入图片描述

有任何问题,请联系我,一定及时修改

Java高薪面试:如何突破BAT面试门槛之-第四篇:Java并发编程面试题详解 在 run 方法中,我们在 while 循环中检查变量是否为 true,而在 stop 方法中,我们将变量设置为 false,让线程正常退出。如果一个变量被声明为 volatile,在写入该变量时,会立即将其刷新到主内存中,而其他线程在读取该变量时也会直接读取主内存中的值,从而保证多个线程看到的变量值是一致的。在 main 方法中,我们先启动 thread1 线程,并使用 join 方法等待该线程执行完毕后再启动 thread2 线程,从而保证两个线程之间的执行顺序。在多线程中,线程安全问题是难以避免的。 阅读详情

相关推荐

STM32F103C8T6+A4988驱动42步进电机全流程(附接线图+代码解析)

本文详细介绍了使用STM32F103C8T6单片机与A4988驱动模块控制42步进电机的完整流程。内容涵盖硬件接线图解析、电流设置、GPIO驱动代码编写以及细分控制等核心实践,旨在帮助嵌入式开发者和DIY爱好者快速实现精准运动控制,适用于3D打印机、CNC等需要精密定位的项目。

Python的专栏 794

java面试突破 – Java基础题目解答

java基础面试题目答案!

joy_cd 736

避坑实录:用certificate-manager重置vCenter 6.7证书时遇到的三个致命陷阱

本文详细解析了使用certificate-manager重置vCenter 6.7证书时可能遇到的三大致命陷阱,包括进度卡在85%的服务启动僵局、脚本突然退出的静默杀手以及密码错误锁死的隐藏机制。通过实战验证的解决方案和高级恢复技巧,帮助系统管理员有效避免vCenter环境瘫痪,确保证书更新过程顺利进行。

g5h6i7j的博客 919

Java面试突击手册,一周刷完这300道面试题,你也可以当架构师!

今天在这分享目前国内公司Java面试常问的问题包括JVM、常用的算法和数据结构,redis缓存,分布式,Spring,微服务等。对源码解读和核心原理理解也是成了加分项,特别是对 Java 的一些核心基础知识点掌握的不够或者没有体系方向的朋友面试也成了一个苦恼的事情。由于篇幅原因,在这就不做全部展示了,这些题我已经整理成pdf文档免费分享给那些有需要的朋友,同时整理也花费了蛮多时间,面对今年的大环境而言,跳槽成功的难度比往年高了很多,很明显的感受就是:对于今年的java开发朋友跳槽面试,

stone_tmp的博客 706

实战STM32G0驱动DRV8870/AT8870:PWM+DMA精准调速与H桥衰减模式详解

本文详细介绍了如何使用STM32G0单片机结合DRV8870/AT8870电机驱动芯片实现直流有刷电机的精准控制。重点讲解了PWM与DMA结合实现高效调速的硬件配置与软件驱动方法,并深入剖析了H桥快衰减与慢衰减模式的工作原理及应用场景,为嵌入式电机控制项目提供了完整的实战解决方案。

c7d8e9的博客 595

cartographer 添加二维码、apritag码作为路标点,测试位置关系

这里记录一下cartographer里面的路标点是如何转换的,首先你要知道路标点在相机坐标系的位置(很多二维码读码器的关系恰好相反,是相机在二维码的坐标系),然后要知道相机坐标系和小车坐标系(一般为base_link)之间的关系。中间用tf_broadcaster_发出来便于查看是否设置正确 void Node::HandleLandmarkMessage( const int trajectory_id, const std::string& sensor_id, const ca

南北粉面馆 2432

Java面试附答案:掌握关键技能,突破面试难题!

问题:什么是大O表示法?它在Java中的应用是什么?回答:大O表示法是一种用来衡量算法复杂度的方法,它描述了算法的时间复杂度和空间复杂度的增长速度。它使用符号O(n)来表示算法的渐进时间复杂度,其中n表示输入规模的大小。这种表示法忽略了常数因子和低阶项,只关注随着输入规模n的增长,算法执行所需的时间或者空间的增长趋势。在Java中,大O表示法常常用于分析和比较不同算法的效率。通过使用大O表示法,我们可以预估算法在输入规模增加时所需的时间或空间。

m0_47946173的博客 255

YOLOv11改进策略【损失函数篇】| Shape-IoU:考虑边界框形状和尺度的更精确度量

本文记录的是改进YOLOv11的损失函数,将其替换成Shape-IoU。现有边界框回归方法通常考虑与之间的几何关系,通过边界框的相对位置和形状计算损失,但忽略了边界框本身的形状和尺度等固有属性对边界框回归的影响。为了弥补现有研究的不足,Shape-IoU提出了一种关注边界框本身形状和尺度的边界框回归方法。

Limiiiing的博客 4596

《JAVA面试突破专题》

《JAVA面试突破专题》 面向对象 1、面向对象和面向过程的区别 面向过程关注于一个功能实现的步骤,按步骤编程实现功能。 面向对象关注于一个功能实现的行为,将一些行为封装为一个对象来统一调用。 面向过程是一种事件为中心的编程思想。就是分析出解决问题所需的步骤,然后用函数把这些步骤实现,并按顺序调用。面向对象是以对象为中心的编程思想。 2、四个基本特性 抽象:J...

JaccePon 238

深入URP之Shader篇1: URP Shader概述

我们知道,URP和HDRP都是基于SRP的,尽管现在可以使用Shader Graph编写Shader,但是手写Shader仍然是有一定的优势,特别是在性能方面,手写Shader可以更极致的优化,因此我们在项目中前期都是直接使用Shader Graph编写Shader,项目后期会改成手写Shader来提高性能。而在SRP中手写Shader,我们仍然使用的是Shader Lab,这个结构并没有太大的变化,但是SRP Shader中使用Shader Lab语言还是有几点不同。

聚焦游戏和图形引擎开发技术 4638

c语言实现求一个矩阵特征值和特征向量

前言 求矩阵的特征值,主要是用的QR分解,在我的有一次博客里,我已经详细地给出了计算的过程,大家有兴趣可以去看下,经过几天的钻研,终于完成了整个的eig算法。下面我将把我的整个代码附上,有不懂的可以问我,欢迎一起讨论学习! 这是对上一次的修改版,上一次写的程序是在C++编译环境下编译的,所以放在c里面编译有些会出错。 最后,如果有不对的地方希望大...

qq_36417014的博客 2万+

金三银四高质量Java面试题集锦:高级Java工程师面试八股汇总

人人都想进大厂,当然我也不例外。早在春招的时候我就有向某某某大厂投岗了不少简历,可惜了,疫情期间都是远程面试,加上那时自身也有问题,导致屡投屡败。突然也意识到自己肚子里没啥货,问个啥都是卡卡卡卡,后期下定决心好好弥补我的知识与智商,天天扎在书堆里,再加上实操,自我感觉还是不错的,有进步。谈到 Java 面试,相信大家第一时间脑子里想到的词肯定是金三银四,金九银十。好像大家的潜意识里做 Java 开发的都得在这个时候才能出去面试,跳槽成功率才高!

m0_46995061的博客 396

【WRF数据准备】制备月尺度静态地理数据:LAI、Fpar、LUCC

【WRF数据准备】制备月尺度静态地理数据:LAI、Fpar、LUCC

WW、forever的博客 803

2020金三银四BAT面试题汇总:JVM+Spring+Dubbo+Redis+并发编程

2020年,跳槽面试就靠它们了,现在每天刷题,看源码文档,最近看的这些题目还是有些难度,有一部分我都要好好想一想才能回答出来,或者回答不出来,总的来说知识宽度够了深度还跟不上。所以要重点突破下底层的东西。这篇文章我总结了很久,希望对正在准备面试的同仁们有点帮助。没错我就是传说中的面霸!! 下面我把面试题总结一下: 1.Redis 面试题 使用redis有哪些好处? redis相比memcach...

a646705816的博客 3137

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

《Java工程师面试核心突破》专栏简介

🔥 大厂Offer收割机 | 源码级技术纵深 | 90%高频考点覆盖

穿越清华 3329

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Java并发编程与堆算法面试突破指南

并发编程是Java开发中的核心难点,其本质是解决共享资源访问的三大特性:可见性、原子性和有序性。通过volatile、synchronized和CAS等机制,可以确保多线程环境下的数据一致性。JUC工具类如ConcurrentHashMap和AQS,为高并发场景提供了高效解决方案。堆作为一种完全二叉树结构,在解决TopK问题、任务调度等场景中表现出色。结合JUC原理与堆算法实践,不仅能深入理解并发控制机制,还能提升算法解题能力。本文通过JUC知识体系拆解和堆算法精讲,帮助开发者构建系统的面试准备策略,涵盖从

weixin_33827731的博客 502

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

2022年突破年薪百万难关!Java真题汇总

我相信大多 Java 开发的程序员或多或少经历过BAT一些大厂的面试,也清楚一线互联网大厂 Java 面试是有一定难度的,小编经历过多次面试,有满意的也有备受打击的。因此呢小编想把自己这么多次面试经历以及近期的面试真题来个汇总分析,阐述下如何去准备,去回答面试官的提问,可以和面试官有个愉快的交谈。

老男孩的架构路 201

程序员们金九银十将至,如何快速突破Java面试僵局?

建立错题本记录非常规考点(如。

马士兵教育 750

全网首发!阿里巴巴68道Java高级面试题(含答案

主要用到的架构,做了些什么?比较复杂的业务逻辑讲一下?遇到很难解决的问题和突破收获以后的打算和未来的职业发展,谈谈自己的看法阿里高级岗面试题========美团面试题=====1. Java 中的自增(i++)是线程安全的嘛?如何实现线程安全的自增?2. 其他常用的线程安全的类;3. ConcurrentHashMap是如何实现线程安全的?4. Synchronized是否是公平锁;5. ThreadLocal使用及原理?6. Jdk8中的stream有用过嘛?

m0_57502067的博客 1081

Java秋招面试技能储备指南——系统性突破技术壁垒,掌握面试主动权

亲爱的开发者,当你翻开这份指南时,你正站在Java技术栈的广袤疆域前,而秋招的战场已悄然拉开序幕。在激烈的竞争中,。这不是一份简单的知识点罗列,而是我们结合数十家一线大厂(阿里、腾讯、字节、美团等)近年Java岗位面试真题,提炼出的和。—— 直击200+高频面试点,拒绝无效复习—— 洞悉技术原理背后的设计哲学与工程价值—— 从背诵答案到掌握问题拆解逻辑。

程序员高级码农的博客 1908

竟然有人说学Java并发编程只是为了应付面试?怪不得你只能拿10K!

前言 招聘黄金季,大厂每月都要吸纳几十名 Java 开发,对普通人来说,那可是几十张通往财务自由的船票。经验要求一般 3 年起,但很多 3 年经验的 Java,却在一面就挂得很惨。Java 3 年的“坎”,究竟是什么呢?耐心看完,一定对你有帮助。 技术岗,不是靠工作年限吃饭的岗位。3年经验是个幌子,我整理过一份详细的大厂岗位需求表,很多 20K 以上的 Java 岗,基本都要求具备高并发分布式的相关经验。老练的面试官知道,对于一个 Java 程序员而言,如果对并发编程有全面而深入的了解,那说明技术功底足够.

whyrookie的博客 185

Java开发面试必备:终极宝典助你突破

(例如:12306抢票系统设计、如何设计一个秒杀系统、高并发系统、微博系统以及百万级数据导出Excel怎么性能优化,避免OOM)(含:OAuth2有哪几种授权模式、说说Seata的执行流程、说说你对DDD的理解、微服务设计的9种设计模式等)(例如:JDK8提升代码优雅技巧、BIO、NIO、AIO有什么区别、什么事守护线程?与普通线程的区别等)分享这些给大家,也是希望帮助大家,除了在技术上的精进和能力上的增长,也能在面试中更加从容地应对。(例如:什么是堆外内存、对象的结构是什么样的、说说对象分配规则)

公众号-老炮说Java 79

java面试简历作假,帮你突破瓶颈

光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性。

m0_60607675的博客 963

致应届生:2025年Java实习生面试高频考点解析与突破

针对应届生竞争加剧的情况,本文从面试官视角梳理核心考点。重点包括:1) Java基础(面向对象特性、==与equals区别、String相关类);2) 集合框架(HashMap原理、线程安全实现);3) 并发编程(线程创建、锁机制、volatile、线程池);4) JVM(内存结构、GC算法)。建议应届生深入理解原理而非死记硬背,并尝试结合项目经验阐述。掌握这些核心知识能帮助在基础考察中脱颖而出。

weixin_73740443的博客 1061

Java面试指南:突破面试难关,成为优秀程序员的必备利器!

序列化就是一种用来处理对象流的机制,就是将对象的内容进行流化,可以对流化后的对象进行读写操作,也可以将流化后的对象传输于网络之间。可通过实现接口来实现序列化。AOP是面向切面编程,用于在不改变原有逻辑的基础上增加一些额外的功能,如事务管理、日志、缓存、权限控制等。Spring AOP是基于代理的。如果目标对象实现了接口,则默认采用JDK动态代理。如果目标对象没有实现接口,则采用CgLib进行动态代理。如果目标对象实现了接口,且强制CgLib代理,则采用CgLib动态代理。

2301_78976656的博客 1126

Java面试题集权威解析:面向Android开发的专项突破指南

你是否在为Android开发工程师面试中的Java技术问题而烦恼?想要系统掌握Java核心知识,在面试中脱颖而出吗?本文将为你提供一份完整的Java面试题集权威解析,帮助你专项突破Android开发中的Java技术难点。作为一名Android开发者,扎实的Java基础是成功的关键,这份指南将带你深入理解Java在Android开发中的核心应用。 ## 📚 Java面试题集概览与核心知识点 J

gitblog_00049的博客 701
上一篇: java面试突破 - 集合面试汇总
下一篇: VsCode 配置 Vue 项目
dotnet之路
博客等级 码龄7年 24粉丝 18原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值