Terminated
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
➢新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建
状态
➢就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
具备了运行的条件,只是没分配到CPU资源
➢运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线
程的操作和功能
➢阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中
止自己的执行,进入阻塞状态
➢死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
======================================================================
多个线程共同操作共享数据时,会造成操作的不完整性,会破坏数据
当多个线程在操作共享数据时,一个线程只执行了一部分语句,另一个线程也参与进来。会导致共享数据的错误
代码如下:
package com.example.www.d4;
/**
-
@ClassName WindowTest
-
@Description 卖票
-
例子:创建三个窗口卖票,总票数为100张
-
现在存在线程的安全问题
-
@Author Jack
-
@Date 2021/11/21 23:33
-
@Version 1.0
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName(“窗口一”);
w2.setName(“窗口二”);
w3.setName(“窗口三”);
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
// private static Object obj =new Object();
@Override
public void run() {
while (true) {
// synchronized (Window.class){
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + “:卖票,票号为” + ticket);
ticket–;
}else {
break;
}
// }
}
}
}
输出结果为:
…
…
窗口三:卖票,票号为13
窗口二:卖票,票号为13
窗口一:卖票,票号为13
窗口一:卖票,票号为10
窗口三:卖票,票号为10
窗口二:卖票,票号为8
窗口三:卖票,票号为7
窗口一:卖票,票号为6
窗口二:卖票,票号为5
窗口三:卖票,票号为4
窗口二:卖票,票号为4
窗口一:卖票,票号为4
窗口三:卖票,票号为1
窗口二:卖票,票号为0
窗口一:卖票,票号为-1
解决方法:
当一个线程操作共享数据时,等它操作完了其他线程才能继续操作
Java对这个问题提供了专业的解决方法:同步机制
理解同步和异步
同步和异步关注的是消息通信机制
同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 调用方不会等待结果,而是在调用发出后调用者可用继续执行后续操作,哪个调用先执行完先处理哪个
同步异步 , 举个例子来说,一家餐厅来了5个客人,同步的意思就是说,来第一个点菜,点了个鱼,好, 厨师去捉鱼杀鱼,过了半小时鱼好了给第一位客人,开始下位一位客人,就这样一个一个来,按顺序来
相同, 异步呢,异步的意思就是来第一位客人,点什么,点鱼,给它一个牌子,让他去一边等吧,下一位客人接着点菜,点完接着点让厨师做去吧,哪个的菜先好就先端出来,
同步的优点是:同步是按照顺序一个一个来,不会乱掉,更不会出现上面代码没有执行完就执行下面的代码, 缺点:是解析的速度没有异步的快;
异步的优点是:异步是接取一个任务,直接给后台,在接下一个任务,一直一直这样,谁的先读取完先执行谁的, 缺点:没有顺序 ,谁先读取完先执行谁的 ,会出现上面的代码还没出来下面的就已经出来了,会报错;
1.同步代码块:
synchronized (对象){
//需要被同步的代码;
2.synchronized还可 以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
}
实现如下
package com.example.www.d4;
/**
-
@ClassName WindowTest
-
@Description 卖票
-
例子:创建三个窗口卖票,总票数为100张
-
现在存在线程的安全问题
-
@Author Jack
-
@Date 2021/11/21 23:33
-
@Version 1.0
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName(“窗口一”);
w2.setName(“窗口二”);
w3.setName(“窗口三”);
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
// private static Object obj =new Object();
@Override
public void run() {
while (true) {
synchronized (Window.class){
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + “:卖票,票号为” + ticket);
ticket–;
}else {
break;
}
}
}
}
}
输出结果如下:
…
…
窗口三:卖票,票号为20
窗口三:卖票,票号为19
窗口二:卖票,票号为18
窗口二:卖票,票号为17
窗口三:卖票,票号为16
窗口三:卖票,票号为15
窗口三:卖票,票号为14
窗口三:卖票,票号为13
窗口三:卖票,票号为12
窗口三:卖票,票号为11
窗口三:卖票,票号为10
窗口三:卖票,票号为9
窗口一:卖票,票号为8
窗口三:卖票,票号为7
窗口三:卖票,票号为6
窗口三:卖票,票号为5
窗口三:卖票,票号为4
窗口三:卖票,票号为3
窗口二:卖票,票号为2
窗口三:卖票,票号为1
在《Thinking in Java》 中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么 ?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class) 、非静态方法(this )
同步代码块:自己指定,很多时候也是指定为this或类名.class
注意
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class) ,所有非静态方法共用同一把锁(this) ,同步代码块(指定需谨慎)
同步的范围
1、如何找问题,即代码是否存在线程安全? (非常 重要)
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?
(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中
3、切记
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终 止了该代码块、
该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。(后面会提到wait方法)
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread. sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
如下
package com.caq.www;
import static java.lang.Thread.sleep;
/**
-
@ClassName ThreadTest
-
@Description 演示线程的死锁问题
-
1.死锁的理解:
-
不同的线程分别占用对方需要的同步资源不放弃,
-
都在等待对方放弃自己需要的公共资源,这样就形成了线程的死锁
-
2.说明:
-
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
-
2)我们使用同步时,要避免出现死锁
-
@Author Jack
-
@Date 2021/11/23 11:13
-
@Version 1.0
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
//通过匿名内部类继承Thread类的方式开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s1){
s1.append(“a”);
s2.append(“1”);
//线程拿着s1锁,等待s2锁
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append(“b”);
s2.append(“2”);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
//通过实现Runnable接口的方式开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append(“c”);
s2.append(“3”);
//线程拿着s2锁,等待s1锁
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append(“d”);
s2.append(“4”);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
从JDK 5.0开始,Java提供了更强大的线程同步机制一通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
改进后的实例
package com.caq.www;
import java.util.concurrent.locks.ReentrantLock;
/**
-
@ClassName LockTest
-
@Description 解决线程安全问题方式三:Lock锁 —JDK5.0新增
-
1.面试题:synchronized 与Lock的异同
-
同:二者都可以解决线程安全问题
-
不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
-
lock需要手动的启动同步(lock()),同时结束同步也需要手动执行(unlock())
-
2.面试题:如何创建多线程?
-
1.通过继承Thread类重写run方法。生成子类的对象并调用start方法
-
2.通过实现Runnable接口并把实现类作为参数传递到Thread类的构造器中,创建Thread对象调用start()启动线程
-
3.面试题:如何解决线程安全问题?有几种方式?
-
1.通过synchronized的方式{同步代码块,同步方法}
-
2.通过Lock锁的方式,手动开启同步锁对线程进行同步,并手动释放同步锁结束
-
@Author Jack
-
@Date 2021/11/23 15:57
-
@Version 1.0
*/
class Window implements Runnable{
private int ticket = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);//为true的话是公平的先进先出,默认是false抢占形
@Override
public void run() {
while (true){
try {
//2.调用锁定方法lock
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+“:售票,票号为:”+ticket);
ticket–;
}else {
break;
}
}finally {
//3.调用解锁方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName(“窗口一”);
t2.setName(“窗口二”);
t3.setName(“窗口三”);
t1.start();
t2.start();
t3.start();
}
}
注意:如果同步代码有异常,要将unlock()写 入finally语句块
synchronized与Lock的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)
======================================================================
可以理解为交替
wait()与notify()和notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或ntifyll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll():唤醒正在排队等待资源的所有线程结束等待
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.langllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
package com.caq.java2;
/**
-
@ClassName CommunicatrionTEst
-
@Description 线程通信的例子;使用两个线程打印1-100.线程1,线程2,交替打印
-
涉及到的三个方法:
-
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
-
notify():一旦执行此方法,就会唤醒一个wait的线程,如果有多个线程被wait有限释放优先级高的
-
nitifyAll():一旦执行此方法,就会唤醒所有的wait的线程
-
说明:
-
1.wait, notify(),nitifyAll()三个方法必须使用在同步代码块或同步方法中
-
2.wait, notify(),nitifyAll()三个方法调用者必须是同步块或同步方法中的同步监视器
-
3.wait, notify(),nitifyAll()三个方法都是java.lang.Object类中方法
-
否则会出现IllegalMonitorStateException异常
-
面试题:sleep()和wait()方法的异同
-
相同点:一旦执行方法都会使线程进入阻塞状态
-
不同点:1)两方法声明的位置不一致,一个是Thread一个是Object
-
2)调用的要求不一致,sleep()方法可以在任何需要场景下调用,wait()必须使用在同步代码块中或同步方法中
-
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
-
@Author Jack
-
@Date 2021/11/23 22:52
-
@Version 1.0
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this) {
notify();//当线程1wait后,释放锁。线程2进来调用notify唤醒线程1
if (number < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+“:”+number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
//进入wait状态会释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName(“线程1”);
t2.setName(“线程2”);
t1.start();
t2.start();
}
}
package com.caq.java2;
/**
-
@ClassName ProductTest
-
@Description 线程通信的应用:经典例题:生产者 消费者问题
-
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer) 从店员处取走产品,
-
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
-
会叫生产者停一下, 如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品
-
了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
-
-
分析:
-
1.是否是多线程问题? 是,生产者线程,消费者线程
-
2.是否有共享数据? 是,店员(或产品)
-
3.如何解决线程的安全问题?同步机制,有三种方法
-
4.是否涉及线程的通信? 是
-
@Author Jack
-
@Date 2021/11/23 23:21
-
@Version 1.0
*/
class Clerk {
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if (productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName()+“:开始生产第”+productCount+“个产品”);
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount > 0){
System.out.println(Thread.currentThread().getName()+“:开始消费第”+productCount+“个产品”);
productCount–;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread {//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + “:开始生产产品…”);
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Consumer c1 = new Consumer(clerk);
Consumer c2 = new Consumer(clerk);
p1.setName(“生产者”);
p1.start();
c1.setName(“消费者1”);
c2.setName(“消费者2”);
c1.start();
c2.start();
}
}
===============================================================================
与使用Runnable相比,Callable功 能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助Future Task类,比如获取返回结果
Future接口
可以对具体Runnable、Callable任 务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
实例如下
package com.caq.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
-
@ClassName ThreadNew
-
@Description 创建线程的第三种方式
-
实现Callable接口 --jdk5.0
-
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
-
1.call()可以有返回值
-
2.call()可以抛出异常,被外面的操作捕获。获取异常的信息
-
3.Callable是支持反省的
-
@Author Jack
-
@Date 2021/11/24 8:41
-
@Version 1.0
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <=100; i++) {
if (i %2 ==0){
// System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中的call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现重写的call()的返回值
Object sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
优点
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize: 核心池的大小.
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
…
线程池相关API
JDK 5.0起提供了线程池相关API: ExecutorService 和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一 般用来执行Runnable
Future submit(Callable task): 执行任务,有返回值,一 般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个 只有一个线程的线程池
Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
实例
package com.caq.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
最后
最后,强调几点:
- 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
- 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
- 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;
我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。
面试答案
量特别大的资源,比如并发情况下的线程,对性能影响很大
思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
优点
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize: 核心池的大小.
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
…
线程池相关API
JDK 5.0起提供了线程池相关API: ExecutorService 和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一 般用来执行Runnable
Future submit(Callable task): 执行任务,有返回值,一 般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个 只有一个线程的线程池
Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
实例
package com.caq.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
最后
最后,强调几点:
- 1. 一定要谨慎对待写在简历上的东西,一定要对简历上的东西非常熟悉。因为一般情况下,面试官都是会根据你的简历来问的; 能有一个上得了台面的项目也非常重要,这很可能是面试官会大量发问的地方,所以在面试之前好好回顾一下自己所做的项目;
- 2. 和面试官聊基础知识比如设计模式的使用、多线程的使用等等,可以结合具体的项目场景或者是自己在平时是如何使用的;
- 3. 注意自己开源的Github项目,面试官可能会挖你的Github项目提问;
我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
以上面试专题的答小编案整理成面试文档了,文档里有答案详解,以及其他一些大厂面试题目。
面试答案
[外链图片转存中…(img-FOdwRR9v-1714696662618)]
[外链图片转存中…(img-qnbHFIlz-1714696662619)]
[外链图片转存中…(img-zE6NWv62-1714696662619)]