给你一个网站你怎么做的,如何快速做网站,图片上传分享平台,宁波网站推广高手文章目录写在前面多线程回顾Thread和Runnable面试官#xff1a;为什么我们在项目中推荐使用使用Runnable方法而不用继承Thread#xff1f;面试官#xff1a;Callable为什么可以带返回值#xff0c;你知道底层原理#xff1f;面试题#xff1a;线程了解#xff1f;给我讲…
文章目录写在前面多线程回顾Thread和Runnable面试官为什么我们在项目中推荐使用使用Runnable方法而不用继承Thread面试官Callable为什么可以带返回值你知道底层原理面试题线程了解给我讲讲线程的几种状态面试题你知道线程等待和阻塞的区别面试官给我讲讲线程的生命周期面试官如果我在代码种连续调用两次thread.start()会发生什么你知道synchronized声居然我们学习到锁给我讲讲锁的本质是什么声synchronized使用的几种方式面试官给我讲讲synchronized 实例锁Synchronized和类锁Static Synchronized有什么区别面试官给我讲讲锁是如何实现的monitor entermonitor exit面试题synchronized抛出异常是如何保证能正常释放锁面试题进入synchronized获取对象锁后调用Thread.sleep()方法会释放锁资源wait和notify笔试题如何用wait和notify实现生产者消费者模式面试题为什么wait()必须和synchronized一起使用面试题为什么Java要把wait()和notify()放在如此Object类里面而不是像sleep放在Thread中呢面试题wait()的时候对象锁会释放锁面试题wait()和sleep()区别interruptedException和interrupt()方法声什么情况下抛出InterruptedException面试官给我说说轻量级阻塞和重量级阻塞声你了解线程中断后线程复位和被动复位声如何优雅的关闭线程并发核心概念并发与并行同步1.控制同步面试题如何控制多个线程执行顺序给你三个线程如何顺序打印数字2.数据访问同步不可变对象面试官String为什么设计成不可变对象原子操作和原子变量并发问题面试官多线程场景会出现哪些并发问题你项目中是如何解决的数据竞争死锁面试题什么叫死锁死锁必须满足哪些条件如何定位死锁问题有哪些解决死锁策略活锁资源不足JMM(java memory model)内存模型面试官你知道JMM内存模型、java内存模型、jvm内存模型区别是什么声什么是JMM我听着还是好复杂呀那什么是内存可见性我什么是原子操作我们应该注意什么呢我什么是指令重排序面试官给我讲讲jvm内存模型以及jdk1.7和1.8版本有何区别happen-beforeas if serial(串行)语义面试官:什么是happen-before?voliate关键字final关键字写在前面
这是第一次尝试用模拟对话体方式来叙述知识点这样换种方式来做笔记使我印象更深刻也希望使读者更容易理解()
多线程回顾
Thread和Runnable
我这个使用还不简单我分分钟就可以创建执行线程给你看
1.继承Thread
package net.dreamzuora;public class ThreadDemo extends Thread{Overridepublic void run() {System.out.println(Thread.currentThread().getName() threadImpl run);}}
2.实现Runnable
package net.dreamzuora;public class RunnableDemo implements Runnable {Overridepublic void run() {System.out.println(Thread.currentThread().getName() runnableImpl run);}
}
3.带返回值的Callable
package net.dreamzuora;import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;public class CallableDemo implements Callable {Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(3);String p Thread.currentThread().getName() callableImpl run;return p;}
}
测试方法: Testpublic void threadAndRunnable() throws ExecutionException, InterruptedException {new Thread(new RunnableDemo()).start();new Thread(new ThreadDemo()).start();//包装返回对象FutureTaskString futureTask new FutureTaskString(new CallableDemo());new Thread(futureTask).start();//同步阻塞String p futureTask.get();System.out.println(p);}面试官为什么我们在项目中推荐使用使用Runnable方法而不用继承Thread 1.Runnable 与 Thread 类的解耦 2.提高性能不用继承Thread每来个任务就必须创建一个线程可以交给线程池提交任务而线程池有线程复用机制可以一个线程执行多个任务 3.从java语言特性分析java单继承多实现因此使用Runnable方式拓展性更强就比如利用FutureTask包装Callable实现带有返回值的任务。 面试官你居然谈到线程复用那你可以给我讲讲线程复用原理
面试官Callable为什么可以带返回值你知道底层原理 首先Callable接口只是定义了一个带有返回值的方法利用FutureTask包装Callable返回对象根据类继承关系会发现FutureTask还是继承了Runnable接口只是对其进行功能拓展。 jdk1.8的Future特性简介及使用场景
声那你知道线程有哪些特征
main方法启动的主线程为第一个线程线程优先级每个线程都有其优先级默认为5介于[1,10]之间线程共享公共资源多线程会有并发问题同步解决资源竞争守护和非守护线程用户线程
我你说的那些东西我工作当中根本用不着有啥用
声你忘记了你因为基础薄弱被大厂虐的很惨难道你不想进阿里了不想升职加薪了
我真香我要好好学习那么什么是守护线程main()主线程是守护线程 声 main不是守护线程首先我们来理解守护线程的概念守护线程是程序运行后它默默的在背后提供服务支持最典型的像GC的内存回收保障程序正常运行守护线程终止是被动的只要非守护线程都退出之后守护线程就会被终止非守护线程结束后意味着程序终止了。 我我明白了非守护线程(用户线程)就是我们一般手动创建的线程而守护线程一般都是java底层默认提供的线程例如垃圾回收器、缓存管理器等用来执行辅助任务的那么我们怎么创建守护线程呢
声你理解很到位创建守护线程很简单Thread类给我们提供类 thread.setDaemon(true) 方法但是你一般不会用它如果我们使用守护线程进行IO、业务逻辑操作而它随着用户线程结束而结束因此你无法保证服务正常执行。
我我现在明白线程等基本特征那线程又有哪些状态呢
声切记线程有6种状态这个面试时候经常会遇到,总共以下几种 New new Thread() - 新建状态Runnable new Thread().start() - 执行start()方法以后Runnable状态
其实Runnable分为两种状态取决于获取CPU分配时间片之前和之后两种状态1.就绪状态等待操作系统分配CPU时间片段2.运行状态获取CPU分配时间片之后线程运行Blocked 高并发多线程场景下执行synchronied代码块线程处于被阻塞状态 Waiting 1.不带时间参数的Object.wait()
2.不带时间参数的Thread.join()不带时间参数
3.LockSupport.park()方法声你知道LockSupport.park()方法用在什么场景 我不知道… 声刚才强调过Blocked 仅仅针对 synchronized monitor 锁可是在 Java 中还有很多其他的锁比如 ReentrantLock如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态因为本质上它执行了 LockSupport.park() 方法所以会进入 Waiting 状态。同样Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态。 park函数作用 Java的LockSupport.park()实现分析 面试题线程了解给我讲讲线程的几种状态 面试题你知道线程等待和阻塞的区别 Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁而 Waiting 则是在等待某个条件比如 join 的线程执行完毕或者是 notify()/notifyAll() 。 Timed_waiting 1.设置了时间参数的 Thread.sleep(long millis) 方法
2.设置了时间参数的 Object.wait(long timeout) 方法
3.设置了时间参数的 Thread.join(long millis) 方法
4.设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。Terminated 再来看看最后一种状态Terminated 终止状态要想进入这个状态有两种可能。 1.run() 方法执行完毕线程正常退出。 2.出现一个没有捕获的异常终止了 run() 方法最终导致意外终止。 Thread类存储线程状态源码 Thread.State 类 public enum State {/*** Thread state for a thread which has not yet started.*/NEW,/*** Thread state for a runnable thread. A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.*/RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {link Object#wait() Object.wait}.*/BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* ul* li{link Object#wait() Object.wait} with no timeout/li* li{link #join() Thread.join} with no timeout/li* li{link LockSupport#park() LockSupport.park}/li* /ul** pA thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called ttObject.wait()/tt* on an object is waiting for another thread to call* ttObject.notify()/tt or ttObject.notifyAll()/tt on* that object. A thread that has called ttThread.join()/tt* is waiting for a specified thread to terminate.*/WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:* ul* li{link #sleep Thread.sleep}/li* li{link Object#wait(long) Object.wait} with timeout/li* li{link #join(long) Thread.join} with timeout/li* li{link LockSupport#parkNanos LockSupport.parkNanos}/li* li{link LockSupport#parkUntil LockSupport.parkUntil}/li* /ul*/TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.*/TERMINATED;}最后我们再看线程转换的两个注意点。 1.线程的状态是需要按照箭头方向来走的比如线程从 New 状态是不可以直接进入 Blocked 状态的它需要先经历 Runnable 状态。 2.线程生命周期不可逆一旦进入 Runnable 状态就不能回到 New 状态一旦被终止就不可能再有任何状态的变化。所以一个线程只能有一次 New 和 Terminated 状态只有处于中间状态才可以相互转换。 面试官给我讲讲线程的生命周期
我可以参考我之前写过的博客
面试官如果我在代码种连续调用两次thread.start()会发生什么你知道 synchronized
声居然我们学习到锁给我讲讲锁的本质是什么 我在多线程环境下访问共享资源这个资源可能是变量、对象、文件等当我们给资源加锁以后能够保证一段时间只有一个线程去访问这样就避免数据竞争问题。 声synchronized使用的几种方式
我这个简单不就下面三种吗
给对象加锁类加锁、实例加锁同步代码块同步方法
面试官给我讲讲synchronized 实例锁Synchronized和类锁Static Synchronized有什么区别
如果synchronized作用在static方法中就是类锁类锁是对该类下所有的对象实例都生效而对象锁只是针对实例加锁
声那你知道synchronized对对象加锁的原理 首先我们可以把synchronized锁理解成一个“对象”居然是“对象”里面肯定有成员变量其中“对象”有三个成员变量 1.state锁是否被占用标识符。 2.threadID占用锁的线程id。 3.threadIDList保存正在等待、被阻塞获取锁的线程id当前占用锁的线程被释放以后就从threadIdList中唤醒一个线程拿到锁循环往复。 synchronized(this)或者synchronized(obj)可以理解为就是将锁“对象” synchronized和加锁的对象this/obj合二为一怎么理解这句话呢要访问的共享资源是obj那么这把锁就加载obj对象上将锁“对象”作为obj对象的成员变量这样意味着这个obj对象即是共享资源同事具备锁的“对象” 面试官给我讲讲锁是如何实现的 在对象头里有一块数据叫Mark Word其中包括两个重要的字段锁标志位、占用锁的threadId。不同版本的JVM对象头的数据结构不一样。 我你前面讲的大白话我是听懂了但是我想知道同步代码块和同步方法是如何避免并发问题的 声好我先给你讲讲同步代码块实现原理
java代码
public class SynTest {public void synBlock() {synchronized (this) {System.out.println(dreamzuora);}}
}让我们看看汇编javap -verbose SynTest.class public void synBlock();descriptor: ()Vflags: ACC_PUBLICCode:stack2, locals3, args_size10: aload_01: dup2: astore_13: monitorenter4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;7: ldc #3 // String dreamzuora9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V12: aload_113: monitorexit14: goto 2217: astore_218: aload_119: monitorexit20: aload_221: athrow22: return说到synchronized我们脑子立马就想到monitor计数器、monitor enter、monitor exit现在我结合上图来回答怎么这三个直接是怎么结合使用的。 [画图能力不行这个图可能有些出入以下面文字为准]
monitor enter
执行 monitorenter 的线程尝试获得 monitor 的所有权会发生以下这三种情况之一
a. 如果该 monitor 的计数为 0则线程获得该 monitor 并将其计数设置为 1。然后该线程就是这个 monitor 的所有者。
b. 如果线程已经拥有了这个 monitor 则它将重新进入并且累加计数。
c. 如果其他线程已经拥有了这个 monitor那个这个线程就会被阻塞直到这个 monitor 的计数变成为 0代表这个 monitor 已经被释放了于是当前这个线程就会再次尝试获取这个 monitor。
monitor exit
monitorexit 的作用是将 monitor 的计数器减 1直到减为 0 为止。代表这个 monitor 已经被释放了已经没有任何线程拥有它了也就代表着解锁所以其他正在等待这个 monitor 的线程此时便可以再次尝试获取这个 monitor 的所有权
面试题synchronized抛出异常是如何保证能正常释放锁
细心的同学能够看到反汇编后出现两处monitorexit 13: monitorexit14: goto 2217: astore_218: aload_119: monitorexitJVM 要保证每个 monitorenter 必须有与之对应的 monitorexitmonitorenter 指令被插入到同步代码块的开始位置而 monitorexit 需要插入到方法正常结束处和异常处两个地方这样就可以保证抛异常的情况下也能释放锁
我那同步方法和同步代码实现原理是一样的 同步方法
public synchronized void synMethod() {
}
同步方法汇编指令 public synchronized void synMethod();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack0, locals1, args_size10: returnLineNumberTable:line 16: 0声同步代码块和同步方法实现不一样同步方法并不是依靠 monitorenter 和 monitorexit 指令实现的在同步方法中有一个ACC_SYNCHRONIZED标记这个方法是同步方法当线程进入该方法会先判断该方法是否有这个标记如果有则需要先获得 monitor 锁然后才能开始执行方法方法执行之后再释放 monitor 锁。其他方面 synchronized 方法和刚才的 synchronized 代码块是很类似的例如这时如果其他线程来请求执行方法也会因为无法获得 monitor 锁而被阻塞。
推荐一个大牛文章将synchronized底裤都扒了
面试题进入synchronized获取对象锁后调用Thread.sleep()方法会释放锁资源
我调用Thread.sleep()后线程会出让时间片段进入WAITING状态不会释放锁资源代码演示如下
package net.dreamzuora.thread;import org.junit.jupiter.api.Test;import java.util.concurrent.CountDownLatch;public class SynSleepDemo {Object obj new Object();void a() {System.out.println(a:enter);synchronized (obj) {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(a:exit);}}void b() {System.out.println(b:enter);synchronized (obj) {System.out.println(B);}System.out.println(b:exit);}void c() {synchronized (obj) {System.out.println(C);}}Testpublic void singleThread() throws InterruptedException {this.a();this.b();this.c();}Testpublic void multiThread() throws InterruptedException {CountDownLatch countDownLatch new CountDownLatch(1);new Thread(() - a()).start();new Thread(() - b()).start();new Thread(() - c()).start();countDownLatch.await();}}
控制台输出 结论可以看出三个线程同时只能有一个线程获取对象锁而其中一个线程获取锁以后并且执行Thread.sleep()方法的时候其他线程仍然是BLOCKED状态等到线程1休眠3秒以后其他线程才开始执行。 wait和notify
声你知道wait()方法作用 我在wait()调用之前必须先获得对象锁并且必须与synchronized一起使用wait()使当前线程处于waiting状态并且主动释放对象锁。 声你知道notify()和notifyAll()作用 我notify()或者notifyAll()也必须和synchronized一起使用notify()用来唤醒调用wait()后处于waiting状态的线程当有多个线程时随机唤醒一个线程对其发出通知但是并不会立马被唤醒需要等待正在执行notify()的线程释放锁以后才可以。notifyAll()方法用来通知所有waiting()的线程。
笔试题如何用wait和notify实现生产者消费者模式
package net.dreamzuora.thread;import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;public class CustomMQ {public static void main(String[] args) {QueueString queue new LinkedList();Producer producer new Producer(queue);Consumer consumer new Consumer(queue);new Thread(producer, producer-thread-1).start();new Thread(producer, producer-thread-2).start();new Thread(consumer, consumer-thread-1).start();new Thread(consumer, consumer-thread-2).start();}}
class Producer implements Runnable{private QueueString queue;public Producer(QueueString queue) {this.queue queue;}Overridepublic void run() {while (true){synchronized (queue){String uuid UUID.randomUUID().toString();System.out.println(thread: Thread.currentThread().getName() producer: uuid);queue.add(uuid);queue.notify();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
}class Consumer implements Runnable{private QueueString queue;public Consumer(QueueString queue) {this.queue queue;}Overridepublic void run() {while (true){synchronized (queue){if (queue.isEmpty()){try {System.out.println(Thread.currentThread().getName() wait...);queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}String uuid queue.poll();System.out.println(thread: Thread.currentThread().getName() consumer: uuid);}}}
}
代码案例和详细解释可以看看这个大牛的博客
面试题为什么wait()必须和synchronized一起使用
面试题为什么Java要把wait()和notify()放在如此Object类里面而不是像sleep放在Thread中呢
面试题wait()的时候对象锁会释放锁
我调用wait()线程不会出让CPU时间片段但是会释放锁我可以做个案例如下
package net.dreamzuora.thread;import org.junit.jupiter.api.Test;import java.util.Date;
import java.util.concurrent.CountDownLatch;public class WaitDemo2 {Object obj new Object();void a() {synchronized (obj) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(1);}void b() {synchronized (obj) {System.out.println(2);}}void c() {synchronized (obj) {System.out.println(3);}}Testpublic void method() throws InterruptedException {this.a();this.b();this.c();}Testpublic void thread() throws InterruptedException {CountDownLatch countDownLatch new CountDownLatch(1);System.out.println(new Date());new Thread(() - a()).start();new Thread(() - b()).start();new Thread(() - c()).start();Thread.sleep(5000);System.out.println(new Date());synchronized (obj){obj.notifyAll();}countDownLatch.await();}
}
控制台 结论最先启动的线程占用锁调用wait()方法都进入等待状态其他线程仍然在执行而等待的线程到了五秒被notifyAll()唤醒才执行由此可以得出结论wait()方法时线程会释放锁。 面试题wait()和sleep()区别 wait()不会出让CPU时间片段会释放锁sleep()出让CPU时间片段不释放锁。 interruptedException和interrupt()方法 当其他线程通过调用当前线程的 thread.interrupt() 方法表示向当 前线程打个招呼告诉他可以中断线程的执行了至于什么时候中断取决于当前线程自己。 线程通过检查是否被中断来进行相应可以通过 Thread.currentThread().isInterrupted()来判断是否被中断。 interrupt() 方法并不像在 for 循环语句中使用 break 语句那样干脆马上就停止循环。调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记并不是真的停止线程。
声什么情况下抛出InterruptedException
我obj.wait()、Thread.sleep()、Thread.join()等方法申请了中断异常才会抛出异常
面试官给我说说轻量级阻塞和重量级阻塞
我这个概念我都没听过~ 声你基础还是不行遇到面试这种基础题你都不会直接被pass掉了我来给你讲讲。 声前面我们讲过线程的几种状态还记得? 我脑海回想…NEW、RUNNABLE、BLOCKED、WAITING、Time_Waiting、SLEEP、time_sleep 声记忆不错首先我们要知道轻量级阻塞是处于waiting、time_waiting状态而重量级阻塞是blocked状态的然后我们会发现只有线程进入synchronized尝试获取类锁或者对象锁多线程竞争情况线程进入Blocked状态而synchronized不会声明interruptedException因此此时即使调用thread.interrupt()正在blocked线程是无法感知中断信息由此可以确定synchronized修饰的代码会使线程重量级阻塞相反线程中使用wait()、sleep()等方法会声明interruptedException因此会响应中断其属于轻量级阻塞。 我那居然synchronized是重量级阻塞不会响应中断那我在synchronized中调用thread.sleep()方法是不是就无法接收到中断信号 声你问这个问题说明你还没明白阻塞和等待线程状态我们前面说过synchronized使线程BLOCKED是多线程竞争类锁或者对象锁时候那些等待获取锁的线程进入阻塞状态而一旦获取锁以后就不再是阻塞状态了那么接下来执行的thread.sleep()也是这样能响应中断的呀我给你举个例子
package net.dreamzuora.thread;public class ThreadInterruptedDemo {public static void main(String[] args) {Thread thread new Thread(new SynSleep());thread.start();try {Thread.sleep(3 *1000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();}
}
class SynSleep implements Runnable{Overridepublic void run() {synchronized (this){System.out.println(sleep);try {Thread.sleep(1000000);} catch (InterruptedException e) {e.printStackTrace();}}}
}console:
声你了解线程中断后线程复位和被动复位
1.Thread.interrupted()手动复位将中断状态true-false 和Thread.currentThread().isInterrupted()方法一起使用。 2.进入InterruptedException异常前中断状态true-false java线程的中断的方式原理分析
声如何优雅的关闭线程
Thread.currentThread().stop():官方不推荐使用,强制杀死线程
Thread.currentThread().destroy()官方不推荐使用,搞不懂官方为什么会有这个方法 回到这节标题如何优雅的关闭线程 停止线程方法有三种
设置标志位 while(flag) flag为false结束run方法退出线程这种方法注意线程安全问题。调用线程stop()方法强制杀死线程严重影响业务逻辑。利用Thread.interrupted()方法中断线程原理和1类似给线程设置中断标志符具体停止线程逻辑交给业务处理并且不会像flag一样有线程安全问题。 总结利用interrupted()方法关闭线程
并发核心概念
声这一章节可能比较枯燥都是一些概念
并发与并行 并发在同一时间段同时运行多个任务单核CPU中运行多任务通过操作系统调度很快从一个任务运行切换到另一个任务 并行在同一时刻同事运行多个任务多核CPU同时运行多任务 同步
声说到同步我们可能会想到同步代码块如果你知道同步代码块的作用那么就好理解同步概念了同步代码块是为了防止多线程并发访问共享资源而导致线程安全问题那么同步就是用来协调两个或者多个任务能够按照我们想要的顺序去执行获得我们预期想要的结果那么这就是同步的概念。
声你能想到日常开发当中都会用过同步机制 我synchronized 声恩说的不错其实还有Semaphore等现在让我们学习两种同步方法
1.控制同步 当前任务结束输出作为下个任务输入 例如 面试题如何控制多个线程执行顺序给你三个线程如何顺序打印数字
顺序打印代码参考我之前总结的博客
package net.dreamzuora.thread;import org.junit.jupiter.api.Test;import java.util.concurrent.CountDownLatch;public class MultiThreadIncrement {int num 1;Object obj new Object();class Worker1 implements Runnable {Object object;public Worker1(Object object) {this.object object;}Overridepublic void run() {synchronized (obj) {while (num ! 1) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() A);num 2;obj.notify();}}}class Worker2 implements Runnable {Object obj;public Worker2(Object obj) {this.obj obj;}Overridepublic void run() {synchronized (obj) {while (num ! 2) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() B);num 3;obj.notifyAll();}}}class Worker3 implements Runnable {Object obj;public Worker3(Object obj) {this.obj obj;}Overridepublic void run() {synchronized (obj) {while (num ! 3) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() C);}}}Testpublic void main() throws InterruptedException {CountDownLatch countDownLatch new CountDownLatch(1);MultiThreadIncrement multiThreadIncrement new MultiThreadIncrement();new Thread(multiThreadIncrement.new Worker3(obj), thread-3).start();new Thread(multiThreadIncrement.new Worker1(obj), thread-1).start();new Thread(multiThreadIncrement.new Worker2(obj), thread-2).start();countDownLatch.await();}}
控制台 并发中有不同的同步机制比较流行的有以下几种
信号量(Semaphore)监视器一种在共享资源上实现互斥的机制。它有一个互斥、一个条件变量、两种操作等待 条件和通报条件。一旦你通报了该条件在等待它的任务中只有一个会继续执行。
package net.dreamzuora.thread;import org.junit.jupiter.api.Test;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;public class JUCDemo {int num 0;AtomicInteger atomicInteger new AtomicInteger(0);/*** CAS不是通过线程同步解决线程安全性问题*/Testpublic void casTest(){Object obj new Object();ExecutorService executorService Executors.newFixedThreadPool(1000);for (int i 0; i 1000; i) {executorService.submit(() - {synchronized (obj){atomicInteger.incrementAndGet();}});}System.out.println(atomicInteger);}/*** 同步代码块*/Testpublic void synTest(){Object obj new Object();ExecutorService executorService Executors.newFixedThreadPool(1000);for (int i 0; i 1000; i) {executorService.submit(() - {synchronized (obj){num;}});}System.out.println(num);}/*** 信号量达到线程同步* throws Exception*/Testpublic void semaphoreTest() throws Exception {CountDownLatch countDownLatch new CountDownLatch(1);final Semaphore semaphore new Semaphore(1);ExecutorService executorService Executors.newFixedThreadPool(1000);for (int i 0; i 1000; i) {executorService.submit(() - {try {semaphore.acquire();num;} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();}});}System.out.println(num);countDownLatch.await();}}
2.数据访问同步 当两个或更多任务访问共享变量时再任意时间里只有一个任务可以访问该变量。 不可变对象
面试官String为什么设计成不可变对象
我节省内存开销、避免线程安全问题
原子操作和原子变量
声相信我们项目中用过很多原子操作或者原子变量吧 我的确用过很多原子操作像操作数据库的时候我们会利用Transaction事务要不成功要么失败像redis jedis.set(lockKey, requestId, “NX”, “EX”, expireTime)实现原子操作原子变量像利用voliate修饰的bool变量。
声你回答的不错让我们明确一下他们的居然定义
原子操作是一种发生在瞬间的操作。在并发应用程序中可以通过一 个临界段来实现原子操作以便对整个操作采用同步机制。
原子变量是一种通过原子操作来设置和获取其值的变量。可以使用某种同步机制来实现一个原子变 量或者也可以使用CAS以无锁方式来实现一个原子变量而这种方式并不需要任何同步机制。
并发问题
面试官多线程场景会出现哪些并发问题你项目中是如何解决的
数据竞争
我最常见的多线程访问共享资源并对其进行增删改操作导致数据混乱问题需要利用同步机制或者CAS解决。
死锁
我多线程情况下每个线程都占用对方要使用的锁却没有释放导致死锁死锁问题有典型的哲学家就餐问题
面试题什么叫死锁死锁必须满足哪些条件如何定位死锁问题有哪些解决死锁策略
我的博客总结
活锁 马路中间有条小桥只能容纳一辆车经过桥两头开来两辆车A和BA比较礼貌示意B先过B也比较礼貌示意A先过结果两人一直谦让谁也过不去。(这个形象的比喻摘自死锁、活锁、饥饿) 资源不足 多线程访问共享资源时候需要先获取锁如果有个线程持续占有锁而其他线程会一直白白等待。解决方案加入计时等待机制减少CPU开销 JMM(java memory model)内存模型
我之前在面试中面试官就会问给我讲讲jvm内存模型我巴拉巴拉答了一大堆面试官说你这答的是jmm内存模型啊
面试官你知道JMM内存模型、java内存模型、jvm内存模型区别是什么
我JMM内存模型就是java内存模型而jvm内存模型就程序计数器、堆、虚拟机栈、本地方法栈那什么是JMM内存模型呢
声我相信大多数人都不知道他们区别那我来给你讲讲
声什么是JMM JMM是和多线程相关的一组规范需要各个JVM的实现遵守JMM规范实现Java代码在不同JVM运行都能得到相同结果从 Java 代码到 CPU 指令的这个转化过程要遵守哪些和并发相关的原则和规范这就是 JMM 的重点内容 我你说的概念都是什么鬼听不懂能不能具体点这种抽象概念谁记得住啊老子面试怎么打 声你这急性子我还没讲完呢得嘞我来讲讲具体点的 JMM与处理器、缓存、并发、编译器有关我想我们应该都听过CPU多级缓存、处理器优化、指令重排序等导致结果不一致问题JMM很好的解决了这些问题。 我想我们都使用过synchronized、volatile、Lock等同步工具和关键字而它们的实现都遵循了JMM规范。 面试官问道JMM我们应该立马想到JMM最最重要的3个内容原子性、内存可见性、指令重排序
我听着还是好复杂呀那什么是内存可见性
我什么是原子操作我们应该注意什么呢
我什么是指令重排序
面试官给我讲讲jvm内存模型以及jdk1.7和1.8版本有何区别
我jmm内存划分程序计数器、堆、方法区、虚拟机栈、本地方法栈jdk1.8中将废除了永久代引入了元空间 关于java内存模型讲解网上一大把推荐一篇大牛博客总结的很好Java内存管理-JVM内存模型以及JDK7和JDK8内存模型对比总结
jvm的汇总之后会在jvm常见面试题博客中进行详细梳理
happen-before
声: happen-before我想很多人并不熟悉这节的内容概念性比较多可能有点枯燥耐心看哟
as if serial(串行)语义
单线程重排序 对于单线程程序CPU和编译器都可以对其重排序并不会导致结果不一致问题这就是as if serial语义 多线程重排序 多线程场景中数据依赖复杂编译器和CP无法理解之间关系做出合理优化编译器和CPU只能保证单线程的as if serial 面试官:什么是happen-before?
如果A happen-before B 也就是A的执行结果对B可见但是并不是意味着A一定要在B之前执行在多线程中AB执行顺序不确定又由于指令重排序因此很容易会出现并发问题为了解决这种问题java定义了许多内存可见性的约束:
单线程每个操作happen-before后续操作也就是as-if-serial语义保障synchronized解锁happen-before后续线程加锁对volatile写入happen-before对这个变量的读取对final域的写happen-before后续对final域的读
voliate关键字
final关键字