线程、多线程和线程池面试题(四)

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

方式一:继承thread

public class First extends Thread {
    public void run(){
        System.out.println("first thread ..");
    }
}

@Test
    public void firstTest(){
        new First().start();
    }

方式二:实现Runnable接口

public class RunableTest implements Runnable {
    @Override
    public void run() {
        System.out.println("runable thread ..");
    }
}
@Test
    public void runableTest(){
        new Thread(new RunableTest()).start(); 
   }

方式三:通过Callable和Future创建线程

public class Fucture implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("Fucture thread ..");
        return 1;
    }
}

@Test
    public void runableFucture() {
        Fucture fucture = new Fucture();
        FutureTask<Integer> futureTask = new FutureTask<>(fucture);
        new Thread(futureTask, "有返回").start();
    }
2、多线程同步机制的理解?

在多线程情况下尽量采用系统安全的类,比如Vector,StringBuffer,hashTable,stack能保证一般情况下同步不会出现问题。
如果没法用系统类进行规避的情况需要了解深入一点了解同步锁机制
首先讨论Synchronized关键字,从锁的本质看分为两类:
  1、锁类的对象实例,针对于某个具体实例普通方法/语句块的互斥,简称对象锁;
  2、锁类的Class对象,针对于Class类静态方法/语句块的互斥,简称类锁;
  方法锁和语句块锁只是一种展现形式,最终还是锁的当前的实例对象或者class对象
ReentrantLock (可重入锁)
  与Synchronized的实现原理类似,采用的都是互斥同步策略,用法和实现效果上来说也很相似,也具备可重入的特性。
  不同点是,Sychronized是原生语法层面上同步控制,其颗粒度更大;相比而言,ReentranLock是从API层面来控制锁(lock()unlock()方法),开发者的自主权更强,可控制粒度更细,优化空间更高

CAS指令与原子性
  与上述两种互斥同步不同,CAS是一种原子操作,因为互斥同步需要额外消耗时间,属于悲观策略,对于极高并发情况并不适合;而CAS则属于乐观锁,是通过内存值比较和替换来实现的。
  CAS 操作包含三个操作数 —— 内存位置(V)预期原值(A)新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。这种实现主要是cpu层的支持,因为CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。所以利用CPUCAS指令,同时借助JNI来完成Java的非阻塞算法。

volatile 关键字
 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序。

摘自《深入理解Java虚拟机》:
  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令
  lock前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供3个功能:
  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
  2)它会强制将对缓存的修改操作立即写入主存;
  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

3、为什么要有线程,而不是仅仅用进程?

进程:系统进行资源分配和调度的一个最小单位
线程:CPU进行调度和分派的最小单位,它和其他线程共享同一进程下的所有资源
可以说进程的颗粒度更大,而线程颗粒度更小,更适合并发执行多个任务以及需要共享资源的情况。

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

很明显,真正启动线程的方法就是start()方法,run()方法只不过是一个普通的方法,如果直接调用run()方法,还是在主线程顺序执行。

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

使用Semaphore控制, Semaphore mSemaphore = new Semaphore(5);表示生成五个信号量的实例,保证只有5个线程可以同时执行;
semaphore.acquire()请求一个信号量,这个时候信号量个数 -1;
semaphore.release() 释放一个信号量,此时信号量个数+1;

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

 wait 和 sleep 都能将线程状态变成等待状态,除了这点相同点,其它基本都不同。比如:
   1、wait()是定义在Object 类中的,是一个final修饰的非静态方法;而sleep是被定义在Thread类中的,是一个static修饰的静态方法;
   2、wait()必须用于同步环境,如synchronized方法或者同步代码块、生产-消费者队列,通常配合notify()方法使用,wait 让生产者线程等待,notify唤醒消费者线程,通知队列非空;而sleep()没有这个限制。
   3、调用wait()时会释放当前持有的锁,而sleep()不会释放任何锁。

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

   如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
   当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或notify()方法(只随机唤醒一个wait线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
   优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

8、什么导致线程阻塞?

   线程阻塞特点:该线程放弃CPU的使用,暂停运行
   A、线程执行了Thread.sleep(int millsecond);方法,当前线程放弃CPU,睡眠一段时间,然后再恢复执行
   B、线程执行一段同步代码,但是尚且无法获得相关的同步锁,只能进入阻塞状态,等到获取了同步锁,才能回复执行。
   C、线程执行了一个对象的wait()方法,直接进入阻塞状态,等待其他线程执行notify()或者notifyAll()方法。
   D、线程执行某些IO操作,因为等待相关的资源而进入了阻塞状态。比如说监听system.in,但是尚且没有收到键盘的输入,则进入阻塞状态。

9、线程如何关闭?

1、使用退出标志终止线程:

public class ThreadSafe extends Thread {
    public volatile boolean exit = false; 
        public void run() { 
        while (!exit){
            //do something
        }
    } 
}
ThreadSafe.exit=true;

2、使用interrupt()方法中断当前线程

public class ThreadSafe extends Thread {
    public void run() { 
        while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
            try{
                Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
            }catch(InterruptedException e){
                e.printStackTrace();
                break;//捕获到异常之后,执行break跳出循环。
            }
        }
    } 
}
//未阻塞调用如下代码
ThreadSafe t=new ThreadSafe ();
t.interrupt();

3、不推荐的方法stop();该方法因为其

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

   thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用stop方法来终止线程。

11、数据一致性如何保证?

   解决这个问题要从线程的安全和线程同步方面去考虑,所以也是对后两个问题的总结,
下面是处理数据一致性中几个关键字的区别:

关键字对比.png

在第二个问题中已经对这几种进行了说明;对于实际开发而言,Synchronized是比较基础简单的,涉及同步锁;volatile 关键字修饰共享变量,一个线程修改,其他线程立即可见;
而通过CAS,我们可以实现一种非阻塞同步策略,粒度更细,涉及CPU层支持,效率更高,是很多系统并发SDK的基础,如下几个包:
Atomic:原子数据的构建。
Locks:基本的锁的实现,最重要的AQS框架和lockSupport
Concurrent:构建的一些高级的工具,如线程池,并发队列等。

12、如何保证线程安全?

见上

13、如何实现线程同步?

见上

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

   可以进行同时读写,但为了保证数据的正确,必须要针对进程访问的共享临界区进行处理;两个进程不能同时进入临界区,否则会导致数据错乱。常见的处理方式有:信号量、管程、会合、分布式系统
信号量
信号量是一个计数器,它只支持2种操作:P操作(进入临界区)和V操作(退出临界区)。假设有信号量SV,则对它的P、V操作含义如下:
   P(SV),如果SV的值大于0,意味着可以进入临界区,就将它减1;如果SV的值为0,意味着别的进程正在访问临界区,则挂起当前进程的执行;
   V(SV),当前进程退出临界区时,如果有其他进程因为等待SV而挂起,则唤醒之;如果没有,则将SV加1,之后再退出临界区。
管程
   提出原因:信号量机制不足,程序编写困难、易出错
   方案:在程序设计语言中引入一种高级维护机制
   定义:是一个特殊的模块;有一个名字;由关于共享资源的数据结构及在其上操作上的一组过程组成。进程只能通过调用管程中的过程间接访问管程中的数据结构
  1)互斥:管程是互斥进入的 为了保证数据结构的数据完整性
管程的互斥由编译器负责保证的,是一种语言机制
  2)同步:设置条件变量及等待唤醒操作以解决同步问题
可以让一个进程或者线程在条件变量上等待(先释放管程的管理权),也可以通过发送信号将等待在条件变量上的进程线程唤醒

15、线程间操作List

1、声明list的地方用Collections.synchronizedList()包一层,表示可以同步访问;
2、使用synchronized(this){}锁操作List
逻辑代码块
3、使用concurrent同步代码包中封装类

16、Java中对象的生命周期

1、创建阶段(Created)
  检测类是否被加载没有加载的先加载为新生对象分配内存将分配到的内存空间都初始化为零值对对象进行必要的设置执行<init>方法把对象进行初始化
对象的加载大小是类加载中就已经确定好了的,类加载过程就相当复杂了,如下图:

类加载过程.png

2、应用阶段(In Use)
  至少有一个强引用使用着

3、不可见阶段(Invisible)
  程序的执行已经超出了该对象的作用域了

4、不可达阶段(Unreachable)
  程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”

5、收集阶段(Collected)
  垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。

6、终结阶段(Finalized)
  当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。

7、对象空间重分配阶段(De-allocated)
  垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象彻底消失了,称之为“对象空间重新分配阶段”。

17、Synchronized用法
18、synchronize的原理
19、谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解
20、static synchronized 方法的多线程访问和作用
21、同一个类里面两个synchronized方法,两个线程同时访问的问题
22、volatile的原理

  在多线程并发编程中synchronizedVolatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
原理特性:
可见性
  1、浅显的讲,不论线程是如何如何的访问带volatile字段的对象,都会访问到内存中最新的一份值,这就是可见性的大致阐述;
  2、具体的讲,当我们在java代码中书写的那行对volatile对象进行写操作时,JVM会向处理器发送一条Lock指令,
Lock指令锁住(锁总线)确保变量对象所在缓存行数据会更新到主内存中去,确保更新后如果再有其他线程访问该对象,
其他线程一律强制从主内存中重新读取最新的值。
  3、因为所有内存的传输都发生在一条共享的总线上,并且所有的处理器都能看到这条总线,那么既然所有处理器都能看到这条总线,
总不至于看见了不干点啥吧?
  4、没错,每个处理器都会通过一种嗅探技术,不停的嗅探总线上传输的数据,以便来检查自己缓存中的数据是否过期。
当处理器发现高速缓存中的数据对应的内存地址被修改,会将该缓存数据置为失效,当处理器下次访问该内存地址
数据时,将强制重新从系统内存中读取。
  5、而且CPU制造商也曾制定了一个这样的规则:当一个CPU修改缓存中的字节对象时,服务器中其他CPU会被通知,它们的缓存将视为无效。
当那些被视为无效变量所在的线程再次访问字节对象时,则强制再次从主内存中获取最新值
  6、至于第2点提到Lock锁总线,其实最初采用锁总线,虽说能解决问题,但是效率地下,一旦锁总线,其他CPU就得干等着,
光看不干效率不行嘛。所以后来优化成了锁缓存,效率也高了,开销也自然就少了,总之Lock目的很明确,确保锁住的那份值最新,
且其他持有该缓存的备份处都得失效,其实这种锁缓存过程的思想也正是缓存一致性协议的核心思想。
  7、综上所述,所以不论何时不论何地在哪种多线程环境下,只要你想获取被volatile修饰过的字段,都能看到最新的一份值。

有序性
  1、浅显的讲,A1,A2,A3三块代码先后执行,A2有一行代码被volatile修饰过,那么在被反编译成指令进行重排序时,A2必须等到A1
执行完了才能开始,但是A1内部的指令可以支持重排指令;而A3代码块的执行必须等到A2执行完了才能开始,但是A3内部的指令可以支持
重排指令,这就是有序性,只要A2夹在中间,A2必须等A1执行完才能干活,A2没干完活,A3是不允许开工的。
  2、具体的讲,Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,
也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
  3、综上所述,有序不是我们通常说的自然顺序,而是在有volatile修饰时,存在类似尊卑等级的先后有序这么一说。

非原子性
  1、本不该拿到台面上讲是不是属于volatile的特性,因为我们不能认为仅仅只是因为可见性随处都是最新值,那么就认为是原子性操作。
  2、对于第1种想法,简直是大错特错,因为可见性只是将volatile变量这回主内存并使得其他CPU缓存失效,但是不带代表对volatile变量回写主内存的动作和对volatile变量的逻辑操作是捆绑在一起的。因此既要逻辑操作,又要写回主内存,这本来就违背了volatile特性的本意,所以volatile并不是原子操作的。

2、谈谈volatile关键字的用法

①.状态标记量

volatile boolean flag = false;
 //线程1
while(!flag){
    doSomething();
}
  //线程2
public void setFlag() {
    flag = true;
}

上面功能可以根据状态标记结束线程,根据volatile关键字的可见性。

②.单例模式中的double check

class Singleton{
    private volatile static Singleton instance = null;
     
    private Singleton() {
         
    }
     
    public static Singleton getInstance() {
        if(instance==null) {
            synchronized (Singleton.class) {
                if(instance==null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}
3、谈谈volatile关键字的作用
4、谈谈NIO的理解
5、synchronized 和volatile 关键字的区别

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

6、synchronized与Lock的区别
两者的区别.png
7、ReentrantLock 、synchronized和volatile比较

ReentrantLock可重入锁,和同步锁功能类似,不过需要显示的创建、销毁。特点:
  1.ReentrantLocktryLock方法,如果锁被其他线程持有,返回false,可避免形成死锁。
  2.创建时可自定义是否可抢占。
  3.ReentrantReadWriteLock,用于读多写少,且读不需要互斥的场景,大大提高性能。

1、尝试获取一次    
    ReentrantLock lock = new ReentrantLock();  
    if (lock.tryLock()) {  //得到执行,得不到不执行,就一次。  
        try {  
            //操作  
        } finally {  
            lock.unlock();  
        }  
    }
2、同步执行,类似synchronized(也是使用最多的)
    ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁:可抢占
    ReentrantLock lock = new ReentrantLock(true); //公平锁:严格按照请求锁的排队顺序获取锁
      
    lock.lock(); //如果被其它资源锁定,会在此等待锁释放,阻塞  
    try {  
        //操作  
    } finally {  
        lock.unlock();  
    }  
3、尝试等待固定时间再次获取
    ReentrantLock lock = new ReentrantLock(true); //公平锁  
    try {  
        if (lock.tryLock(5, TimeUnit.SECONDS)) {      
            //如果已经被lock,尝试等待5s,看是否可以获得锁,如果5s后仍然无法获得锁则返回false  
            try {  
                //操作  
            } finally {  
                lock.unlock();  
            }  
        }  
    } catch (InterruptedException e) {  
        e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException                   
    }
4、可中断锁的同步执行
    ReentrantLock lock = new ReentrantLock(true); //公平锁  
    lock.lockInterruptibly();  
    try {  
        //操作  
    } catch (InterruptedException e) {  
        e.printStackTrace();  
    } finally {  
        lock.unlock();  
    }
8、ReentrantLock的内部实现
9、lock原理
10、死锁的四个必要条件?
11、怎么避免死锁?
12、对象锁和类锁是否会互相影响?
13、什么是线程池,如何使用?
14、Java的并发、多线程、线程模型
15、谈谈对多线程的理解
16、多线程有什么要注意的问题?
17、谈谈你对并发编程的理解并举例说明?
18、谈谈你对多线程同步机制的理解?
19、如何保证多线程读写文件的安全?
20、断点续传的实现

断点续传:文件传输过程中需要中途中断时,我们把已传输的文件和断点指针进行保存;等待下次再次开启传输时直接移动指针,从断点的地方继续传输的这个过程称为断点续传。
用途:本机I/O流的读取或者本机和服务器传输大文件时采用,java语言提供了一个文件操作类RandomAccessFile,通过该类我们可以在随意位置读取

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容