多线程 / 高并发 bat面试题

多线程 / 高并发
1. stop() 和 suspend() 方法为何不推荐使用?
反对使用 stop(),是因为它不安全 。它会解除由线程获取的所有锁定,而且如果对象
处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出
真正的问题所在。
suspend() 方法容易发生死锁 。调用 suspend() 的时候,目标线程会停下来,但却仍
然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被 "挂
起" 的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任
何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread
类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()
命其进入等待状态。若标志指出线程应当恢复,则用一个 notify() 重新启动线程。
2. sleep() 和 wait() 有什么区别?
sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时
间过后,cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep
方法并不会释放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住
了的线程也无法得到执行。wait 是指在一个已经进入了同步锁的线程内,让自己暂时
让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用
了 notify 方法(notify 并不释放锁,只是告诉调用过 wait 方法的线程可以去参与获
得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果 notify 方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在 notfiy 方法后
增加一个等待和一些代码,看看效果),调用 wait 方法的线程就会解除 wait 状态和
程序可以再次得到锁后继续向下运行。
3. 同步和异步有何异同,在什么情况下分别使用他们?
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读
的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等
待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
4. 当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其
它方法?
其他方法前是否加了 synchronized 关键字,如果没加,则能。
如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不
能。
如果其他方法是 static,它用的同步锁是当前类的字节码,与非静态的方法不
能同步,因为非静态的方法用的是 this。
5. 简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?
主要相同点:Lock 能完成 synchronized 所实现的所有功能。
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。
synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。Lock 还有更强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿
锁。
举例说明(对下面的题用 lock 进行了改写)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
/**
* @param args
*/
private int j;
private Lock lock = new ReentrantLock();
public static void main (String[] args) {
// TODO Auto-generated method stub
ThreadTest tt = new ThreadTest();
for ( int i= 0 ;i< 2 ;i++)
{
new Thread(tt.new Adder()).start();
new Thread(tt.new Subtractor()).start();
}
} private class Subtractor implements Runnable
{
@Override
public void run () {
// TODO Auto-generated method stub
while ( true )
{
/*synchronized (ThreadTest.this) {
System.out.println("j--=" + j--);
// 这里抛异常了,锁能释放吗?
}*/
lock . lock ();
try
{
System. out .println( "j--=" + j--);
} finally
{
lock .unlock();
}
}
} }
private class Adder implements Runnable
{
@Override
public void run () {
// TODO Auto-generated method stub
while ( true )
{
/*synchronized (ThreadTest.this) {
System.out.println("j++=" + j++);
}*/
lock . lock ();
try
{
System. out .println( "j++=" + j++);
} finally
{
lock .unlock();
}
}
} }
}
6. 概括的解释下线程的几种可用状态。
新建 new。
就绪 放在可运行线程池中,等待被线程调度选中,获取 cpu。
运行 获得了 cpu。
阻塞
o 等待阻塞 执行 wait() 。
o 同步阻塞 获取对象的同步琐时,同步锁被别的线程占用。
o 其他阻塞 执行了 sleep() 或 join() 方法)。
死亡。
7. 什么是 ThreadLocal?
ThreadLocal 用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全
局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用
同步的时候,我们可以选择 ThreadLocal 变量。
每个线程都会拥有他们自己的 Thread 变量,它们可以使用 get()\set() 方法去获取他
们的默认值或者在线程内部改变他们的值。ThreadLocal 实例通常是希望它们同线程
状态关联起来是 private static 属性。
8. run() 和 start() 区别。
run( ): 只是调用普通 run 方法 start( ): 启动了线程, 由 Jvm 调用 run 方法
启动一个线程是调用 start() 方法,使线程所代表的虚拟处理机处于可运行状态,这意
味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run() 方法可以产
生必须退出的标志来停止一个线程。
9. 请说出你所知道的线程同步的方法。
wait(): 使一个线程处于等待状态,并且释放所持有的对象的 lock。 sleep(): 使一个
正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
InterruptedException 异常。 notify(): 唤醒一个处于等待状态的线程,注意的是在
调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒
哪个线程,而且不是按优先级。 notityAll(): 唤醒所有处入等待状态的线程,注意并
不是给所有唤醒线程一个对象的锁,而是让它们竞争。
10. 线程调度和线程控制。
线程调度(优先级):
与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线
程获取 CPU 资源的概率较大,优先级低的并非没机会执行。
线程的优先级用 1-10 之
间的整数表示,数值越大优先级越高,默认的优先级为 5。 在一个线程中开启另外一
个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
线程控制
sleep( ) // 线程休眠 join( ) // 线程加入 yield( ) // 线程礼让
setDaemon( ) // 线程守护 中断线程
stop( ) interrupt( ) ==(首先选用)==
11. 什么是线程饿死,什么是活锁?
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
JavaAPI 中线程活锁可能发生在以下情形:
当所有线程在序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生
活锁直到在相应的对象上有线程调用 Object.notify() 或者 Object.notifyAll()。
当所有线程卡在无限循环中。
12. 多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法 wait(), sleep() 或 yield() 它
们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么做
的目的是为了保留 CPU 缓存。
在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。
为了避免重建缓存和减少等待重建的时间就可以使用它了。
13. volatile 变量是什么?volatile 变量和 atomic 变量有什么不同?
volatile 则是保证了所修饰的变量的可见。因为 volatile 只是在保证了同一个变量在
多线程中的可见性,所以它更多是用于修饰作为开关状态的变量,即 Boolean 类型的
变量。 volatile 多用于修饰类似开关类型的变量、Atomic 多用于类似计数器相关的变量、其
它多线程并发操作用 synchronized 关键字修饰。
volatile 有两个功用
这个变量不会在多个线程中存在复本,直接从内存读取。
这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后
面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障
之前。
14. volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?
volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。
在 Java 中除了 long 和 double 之外的所有基本类型的读和赋值,都是原子性操作。
而 64 位的 long 和 double 变量由于会被 JVM 当作两个分离的 32 位来进行操
作,所以不具有原子性,会产生字撕裂问题。但是当你定义 long 或 double 变量时,
如果使用 volatile 关键字,就会获到(简单的赋值与返回操作的)原子性。
 
 

线程、多线程和线程池面试专题

1、开启线程的三种方式?

1)继承Thread类,重写run()方法,在run()方法体中编写要完成的任务 new Thread().start();

 

2)实现Runnable接口,实现run()方法 new Thread(new MyRunnable()).start();

 

3)实现Callable接口MyCallable类,实现call()方法,使用FutureTask类来包装Callable对象,使用FutureTask对象作为Thread对象的target创建并启动线程;调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

 

FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());

 

new Thread(ft).start();

 

2、run()和start()方法区别

run()方法只是线程的主体方法,和普通方法一样,不会创建新的线程。只有调用start()方法,才会启动一个新的线程,新线程才会调用run()方法,线程才会开始执行。

 

3、如何控制某个方法允许并发访问线程的个数?

创建Semaphore变量,Semaphore semaphore = new Semaphore(5, true); 当方法进入时,请求一个信号,如果信号被用完则等待,方法运行完,释放一个信号,释放的信号新的线程就可以使用。

 

4、在Java中wait和seelp方法的不同

wait()方法属于Object类,调用该方法时,线程会放弃对象锁,只有该对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

 

sleep()方法属于Thread类,sleep()导致程序暂停执行指定的时间,让出CPU,但它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep()方法中线程不会释放对象锁。

 

5、谈谈wait/notify关键字的理解

notify: 唤醒在此对象监视器上等待的单个线程

 

notifyAll(): 通知所有等待该竞争资源的线程

 

wait: 释放obj的锁,导致当前的线程等待,直接其他线程调用此对象的notify()或notifyAll()方法

 

当要调用wait()或notify()/notifyAll()方法时,一定要对竞争资源进行加锁,一般放到synchronized(obj)代码中。当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此等待线程虽被唤醒,但仍无法获得obj锁,直到调用线程退出synchronized块,释放obj锁后,其他等待线程才有机会获得锁继续执行。

 

6、什么导致线程阻塞?

(1)一般线程阻塞

 

1)线程执行了Thread.sleep(int millsecond)方法,放弃CPU,睡眠一段时间,一段时间过后恢复执行;

 

2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;

 

3)线程执行了一个对象的wait()方法,直接进入阻塞态,等待其他线程执行notify()/notifyAll()操作;

 

4)线程执行某些IO操作,因为等待相关资源而进入了阻塞态,如System.in,但没有收到键盘的输入,则进入阻塞态。

 

5)线程礼让,Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得CPU时间。线程自闭,join()方法,在当前线程调用另一个线程的join()方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程再由阻塞转为就绪态。

 

6)线程执行suspend()使线程进入阻塞态,必须resume()方法被调用,才能使线程重新进入可执行状态。

 

7、线程如何关闭?

1) 使用标志位

 

2)使用stop()方法,但该方法就像关掉电脑电源一样,可能会发生预料不到的问题

 

3)使用中断interrupt()

 

public class Thread {

    // 中断当前线程

    public void interrupt();

    // 判断当前线程是否被中断

    public boolen isInterrupt();

    // 清除当前线程的中断状态,并返回之前的值

    public static boolen interrupted();   

}

但调用interrupt()方法只是传递中断请求消息,并不代表要立马停止目标线程。

 

8、讲一下java中的同步的方法

之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一性和准确性。

 

1)synchronized修饰同步代码块或方法

 

由于java的每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需获得内置锁,否则就处于阴塞状态。

 

2)volatile修饰变量

 

保证变量在线程间的可见性,每次线程要访问volatile修饰的变量时都从内存中读取,而不缓存中,这样每个线程访问到的变量都是一样的。且使用内存屏障。

 

3)ReentrantLock重入锁,它常用的方法有ReentrantLock():创建一个ReentrantLock实例

 

lock()获得锁 unlock()释放锁

 

4)使用局部变量ThreadLocal实现线程同步,每个线程都会保存一份该变量的副本,副本之间相互独立,这样每个线程都可以随意修改自己的副本,而不影响其他线程。常用方法ThreadLocal()创建一个线程本地变量;get()返回此线程局部的当前线程副本变量;initialValue()返回此线程局部变量的当前线程的初始值;set(T value)将此线程变量的当前线程副本中的值设置为value

 

5) 使用原子变量,如AtomicInteger,常用方法AtomicInteger(int value)创建个有给定初始值的AtomicInteger整数;addAndGet(int data)以原子方式将给定值与当前值相加

 

6)使用阻塞队列实现线程同步LinkedBlockingQueue<E>

 

9、如何保证线程安全?

线程安全性体现在三方法:

 

1)原子性:提供互斥访问,同一时刻只能有一个线和至数据进行操作。

 

JDK中提供了很多atomic类,如AtomicInteger\AtomicBoolean\AtomicLong,它们是通过CAS完成原子性。JDK提供锁分为两种:synchronized依赖JVM实现锁,该关键字作用对象的作用范围内同一时刻只能有一个线程进行操作。另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性是ReentrantLock。

 

2)可见性:一个线程对主内存的修改及时被其他线程看到。

 

JVM提供了synchronized和volatile,volatile的可见性是通过内存屏障和禁止重排序实现的,volatile会在写操作时,在写操作后加一条store屏障指令,将本地内存中的共享变量值刷新到主内存;会在读操作时,在读操作前加一条load指令,从内存中读取共享变量。

 

3)有序性:指令没有被编译器重排序。

 

可通过volatile、synchronized、Lock保证有序性。

 

10、两个进程同时要求写或者读,能不能实现?如何防止进程的同步?

我认为可以实现,比如两个进程都读取日历进程数据是没有问题,但同时写,应该会有冲突。

 

可以使用共享内存实现进程间数据共享。

 

11、线程间操作List

 

 

12、Java中对象的生命周期

1)创建阶段(Created):为对象分配存储空间,开始构造对象,从超类到子类对static成员初始化;超类成员变量按顺序初始化,递归调用超类的构造方法,子类成员变量按顺序初始化,子类构造方法调用。

 

2)应用阶段(In Use):对象至少被一个强引用持有着。

 

3)不可见阶段(Invisible):程序运行已超出对象作用域

 

4)不可达阶段(Unreachable):该对象不再被强引用所持有

 

5)收集阶段(Collected):假设该对象重写了finalize()方法且未执行过,会去执行该方法。

 

6)终结阶段(Finalized):对象运行完finalize()方法仍处于不可达状态,等待垃圾回收器对该对象空间进行回收。

 

7)对象空间重新分配阶段(De-allocated):垃圾回收器对该对象所占用的内存空间进行回收或再分配,该对象彻底消失。

 

13、static synchronized 方法的多线程访问和作用

static synchronized控制的是类的所有实例访问,不管new了多少对象,只有一份,所以对该类的所有对象都加了锁。限制多线程中该类的所有实例同时访问JVM中该类对应的代码。

 

14、同一个类里面两个synchronized方法,两个线程同时访问的问题

如果synchronized修饰的是静态方法,锁的是当前类的class对象,进入同步代码前要获得当前类对象的锁;

 

普通方法,锁的是当前实例对象,进入同步代码前要获得的是当前实例的锁;

 

同步代码块,锁的是括号里面的对象,对给定的对象加锁,进入同步代码块库前要获得给定对象锁;

 

如果两个线程访问同一个对象的synchronized方法,会出现竞争,如果是不同对象,则不会相互影响。

 

15、volatile的原理

有volatile变量修饰的共享变量进行写操作的时候会多一条汇编代码,lock addl $0x0,lock前缀的指令在多核处理器下会将当前处理器缓存行的数据会写回到系统内存,这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。同时lock前缀也相当于一个内存屏障,对内存操作顺序进行了限制。

 

16、synchronized原理

synchronized通过对象的对象头(markword)来实现锁机制,java每个对象都有对象头,都可以为synchronized实现提供基础,都可以作为锁对象,在字节码层面synchronized块是通过插入monitorenter monitorexit完成同步的。持有monitor对象,通过进入、退出这个Monitor对象来实现锁机制。

 

17、谈谈NIO的理解

NIO( New Input/ Output) 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。  NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待。

 

-synchronized 和volatile 关键字的区别

-synchronized与Lock的区别

-ReentrantLock 、synchronized和volatile比较

1)volatile:解决变量在多个线程间的可见性,但不能保证原子性,只能用于修饰变量,不会发生阻塞。volatile能屏蔽编译指令重排,不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。多用于并行计算的单例模式。volatile规定CPU每次都必须从内存读取数据,不能从CPU缓存中读取,保证了多线程在多CPU计算中永远拿到的都是最新的值。

 

2)synchronized:互斥锁,操作互斥,并发线程过来,串行获得锁,串行执行代码。解决的是多个线程间访问共享资源的同步性,可保证原子性,也可间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。可用来修饰方法、代码块。会出现阻塞。synchronized发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。非公平锁,每次都是相互争抢资源。

 

3)lock是一个接口,而synchronized是java中的关键字,synchronized是内置语言的实现。lock可以让等待锁的线程响应中断。在发生异常时,如果没有主动通过unLock()去释放锁,则可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。

 

4)ReentrantLock可重入锁,锁的分配机制是基于线程的分配,而不是基于方法调用的分配。ReentrantLock有tryLock方法,如果锁被其他线程持有,返回false,可避免形成死锁。对代码加锁的颗粒会更小,更节省资源,提高代码性能。ReentrantLock可实现公平锁和非公平锁,公平锁就是先来的先获取资源。ReentrantReadWriteLock用于读多写少的场合,且读不需要互斥场景。

 

-ReentrantLock的内部实现

-lock原理

-死锁的四个必要条件?

-怎么避免死锁?

-对象锁和类锁是否会互相影响?

-什么是线程池,如何使用?

-Java的并发、多线程、线程模型

-谈谈对多线程的理解

-多线程有什么要注意的问题?

-谈谈你对并发编程的理解并举例说明

-谈谈你对多线程同步机制的理解?

-如何保证多线程读写文件的安全?

-多线程断点续传原理

-断点续传的实现

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值