您的当前位置:首页正文

Java之——多线程中的Synchronized和volatile与面试题

2024-11-11 来源:个人技术集锦

1、Synchronized关键字

1、方法中的变量不存在非线程安全问题,都是线程安全的。
2、两个线程访问同一个对象中的同步方法时,一定是线程安全的。
3、关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。但如果多个线程访问多个对象,则JVM会创建多个锁。
4、调用关键字synchronized声明的方法一定是排队运行的。另外需要牢记“共享”两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本没同步的必要。
5、两个线程A和B,虽然线程A先持有了Object对象的锁,但线程B完全可以异步调用非synchronized类型的方法。
6、A线程先持有Object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
7、A线程先持有object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需等待,也就是同步。
8、多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字进行同步。
9、脏读:在读取实例变量时,此值已经被其他线程更改过了。
10、当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
11、当A线程调用anyObject对象加入synchronized关键字的X方法是,A线程就获得了X方法所在的对象锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后调用。
12、脏读一定会出现在操作实例变量的情况下,这就是不同线程争抢实例变量的结果。
13、synchronized锁重入:关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块内部调用本类其他synchronized方法/块时,是永远可以得到锁的。
14、当存在父子关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
15、出现异常,锁自动释放。
16、同步不具有继承性。
17、当两个并发线程访问同一个对象object的synchronized(this)同步代码块时,一段时间只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码以后才能执行改代码块。
18、当一个线程访问object的synchronized同步代码块时,另一个线程仍然可以访问object中的非synchronized(this)同步代码块。
19、当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞。这说明synchronized使用的“对象监视器”是一个。
20、和synchronized方法一样,synchronized(this)代码块也是锁当前对象的。
21、使用synchronized(非this对象X)格式同步代码时,对象监视器必须是同一个对象。否则,结果就是异步调用,会交叉运行。
22、synchronized加到static静态方法上,是给Class上锁,加到非static静态方法上,是给对象上锁。
23、同步synchronized(class)的作用实际上和synchronized static的作用类似。
24、JVM中具有Spring常量池缓存的功能 

25、在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的。如果分别获得所对象,则这些线程之间就是异步的。
26、只有锁对象不变,即使对象的属性变化,那么线程之前还是同步的。

在此我向大家推荐一个架构学习交流群。交流学习群号:575745314 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

2、volatile关键字

1、volatile可以使变量在多个线程间可见。
2、强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中获取数据。
3、增加了实例变量在多个线程之间的可见性,但是不支持原子性。
4、使用场合是在多个线程中可以感知实例变量被更改了,并且可以获取最新的值使用,也就是用多线程获取共享变量时,可以获得最新值使用。

3、面试题

1、什么是线程,什么是进程,它们有什么区别和联系,一个进程里面是否必须有个线程
        进程本质是一个正在执行的程序,一个进程可以有多个线程.线程是进程的最小执行单位,一个进程至少有一个线程
区别:1:多进程程序不受java控制,而多线程则受java控制,
         2:多线程比多进程需要更少的管理成本

2、实现一个线程有哪几种方式,各有什么优缺点,比较常用的是那种,为什么
         实现一个线程有三种方式:

1)      继承Thread类,需要重写run方法,无返回值
a)        优点:可以直接调用start方法启动线程
b)        缺点:java只能单继承,如果已经有了父类,不能用这种方法
2)      实现Runnable接口,需要重写run()方法
a)        优点:即使自己定义的线程类有了父类也可以实现接口,而且接口是多实现
b)        缺点:需通过构造一个Thread把自己传进去,才能实现Thread的方法,代码复杂
3)      实现Callable接口,需要重写call()方法
a)        优点:可以抛出异常,有返回值
b)        缺点:只有jdk1.5以后才支持,结合FuntureTask和Thread类一起使用,最后调用start启动线程

一般用第二种,实现Runnable接口,比较方便,扩展性高

3、一般情况下我们实现自己线程时候要重写什么方法
        使用Thread类,要重写run()方法
        实现Runnable接口时,要实现run()方法
        使用Callable接口时,要重写call()方法

4、start方法和run方法有什么区别,我们一般调用的那个方法,系统调用的是那个方法
        Start用来启动线程
                        当调用start后,线程并不会马上运行,而是处于就绪状态,是否要运行取决于cpu

        Run用来子类重写来实现线程的功能
        我们一般调用的是start方法,系统调用的是run方法

5、sleep方法有什么作用,一般用来做什么
        Sleep是一个Thread类的静态方法
        作用:让调用它的线程休眠指定的时间,用于暂停线程,但不会把线程锁让给其它线程,休眠时间结束,线程进入就绪状态,等待cpu分配的执行机会

6、讲下join,yield方法的作用,以及什么场合用它们
        Join()有严格的先后顺序,调用它的线程需要执行完,其它线程才会执行
        Yield()是暂停当前正在执行的线程对象,把时间让给其它线程

7、线程中断是否能直接调用stop,为什么?
        线程终端不能直接调用stop()方法
        Stop()方法是从外部强行终止一个线程,会导致不可预知的错误,比如线程锁没有归还,io流不能关流
        线程只能调用interrupt()方法中断,而且不是立即中断,(
                   只是发出了一个类似于信号量的东西,通过修改了被调用线程的中断状态来告知那个线程,说它被中断了,至于什么时候中断,这个系统会判断)系统会在一个合适的时候进行中断处理

8、列举出一般情况下线程中断的几种方式,并说明他们之间的优缺点,并且说明那种中断方式最好
        线程中断有4种方式
由interrupt发出中断信号,用户接收中断信号,通过isInterrupted判断线程是否中断
由interrupt发出中断信号,系统接收中断信号,通过sleep抛出中断异常,并把中断信号清除,只能抛出一次
用户自定义中断,将中断信号发出,自己接收该中断信号
调用interrupted(),会把中断信号清除,并中断线程

9、线程有几种状态,他们是怎么转化的
        线程一般分为:新生,就绪,运行,阻塞,死亡五中状态
        当创建一个线程后,并没有运行,处于新生状态,需要通过调用start方法,让线程处于就绪状态,但是否运行取决于cpu分配的执行机会,当得到cpu的执行机会后马上运行,一个正在执行的线程可以通过很多方式进入阻塞状态,当执行完所有操作后就进入死亡状态 

10、在实现Runnable的接口中怎么样访问当前线程对象,比如拿到当前线程的名字
        通过Thread.currentThread().getName()可获得当前线程名字

11、讲下什么是守护线程,以及在什么场合来使用它     
守护线程是用来监听其它线程是否挂掉,这个线程具有最低优先级
用于为系统中的其它线程提供服务
守护线程就像象棋里的车马相仕,非守护线程就是老帅,老帅挂掉,守护线程也就挂掉了
        当主线程和主线程创建的子线程全部退出,守护线程一定会跟着退出
        (比如QQ主程序和QQ聊天窗口,主程序退出,QQ聊天窗口也随之关掉)

12、一般的线程优先级是什么回事,线程优先级高的线程一定会先执行吗?如果不设置优先级的话,那么线程优先级是多少,设置线程优先级用那个函数
        线程的优先级就是设置哪个线程先执行,但不是绝对,只是让优先级高的线程优先运行的概率高一些,
        线程默认优先级是NORM_PRIORITY = 5;
        最小是 MIN_PRIORITY = 1;
        最大是 MAX_PRIORITY = 10;
        设置线程优先级有setPriority()方法

13、为什么Thread里面的大部分方法都是final的
        线程很多方法都是由系统调用的,不能通过子类重写去改变他们的行为

14、什么是线程同步,什么是线程安全
        同步:当两个或两个以上的线程需要共享资源,通过同步方法限制资源在一次仅被一个线程占用
        线程安全:线程安全就是多线程操作同一个对象不会产生数据污染,
线程同步一般来保护线程安全,final修饰的也是线程安全

15、讲下同步方法和同步块的区别,以及什么时候用它们
        同步方法:被synchronized修饰的方法,同步整个方法,且整个方法都会被锁住,同一时间只有一个线程可以访问该方法,
        同步块: 被synchronized修饰的代码块,可以同步一小部分代码
什么时候用?
        同步块越小性能越好,当性能要求比较高,用同步代码块

16、简单说下Lock对象的实现类的锁机制和同步方法或同步块有什么区别
        是JDK1.5才出现的,Lock对象比synchronized更加灵活,可以控制什么时候上锁,什么时候解锁,而使用synchronized必须等代码执行完才会解锁.
               Synchronized在锁定时如果方法块抛出异常,JVM会自动释放锁,而Lock出现异常必须在finally将锁释放,否则将引起死锁
 
17、同步块里面的同步监视器是怎么写的,默认的同步方法里面的同步监视器是那个
        Synchronized(对象){
                        //代码块
        }

默认的同步监视器是this

18、讲下什么是死锁,死锁发生的几个条件是什么
        死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此都不肯放开,处于僵持阶段,此时就造成了死锁
        条件:两个或两个以上的线程,同时想要获得对方的资源,彼此又不肯放开

19、线程间是怎么通信的,通过调用几个方法来交互的
        线程是通过wait , notify等方法相互作用进行协作通信; 
        wait()方法使得当前线程必须要等待,直到到另外一个线程调用notify()或者notifyAll()方法唤醒

-- wait()和notify()方法要求在调用时线程已经获得了对象锁,因此对这两个方法的调用需要放在synchronized修饰的方法或代码块中。

20、wait,notify,notifyAll在什么地方使用才有效,他们是那个类的方法
        Wait,notify,notifyAll都必须在synchronized修饰的方法或代码块中使用,都属于Object的方法,可以被所有类继承,都是final修饰的方法,不能通过子类重写去改变他们的行为

21、wait和sleep有什么区别和联系,他们执行的时候是否都会释放锁
        Wait和sleep都可以使线程暂停,但wait必须在synchronized修饰的方法或代码块中使用,

Sleep()方法是线程类的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其它线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(就绪状态),

wait()是Object类的方法,调用对象的wait()方法会让当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的Notify()或notifyAll()方法才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态

22、yield,sleep方法有什么区别和联系
        Yield和sleep都可以让线程暂停,
1)      Sleep()方法给其它线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行机会…yield()方法只会给相同优先级或更高优先级的线程以运行机会
2)      线程执行sleep()方法后转入阻塞状态,而执行yield()方法后转入就绪状态
3)      Sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常
4)      Sleep()方法比yield()方法具有更好的可移植性

23、线程的启动是哪个方法,调用的是哪个方法?
        Start用于启动线程,当调用start后,线程并不会马上运行,而是处于就绪状态,是否运行取决于cpu给的时间片
        Run()方法用于子类重写来实现线程的功能
        我们一般调用的是start方法,系统调用的是run方法

24、线程安全与线程不安全的区别
               线程安全就是多线程访问时,采用了加锁机制,
               当一个线程访问类中某个数据,进行保护,其它线程不能进行访问,直到该线程执行完,其它线程才可使用---不会导致数据污染
               线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

25、线程的实现方式,线程的生命周期等 
               实现方式
1)  通过继承Thread类
2)  实现Runnable接口
3)  实现Callable接口
               生命周期
               新建
               就绪
               运行
               堵塞
               死亡

26、如何处理线程不安全问题(有2种解决方法)
               第一,是采用原子变量,线程安全问题最根本上是由于全局变量和静态变量引起的,定义变量用sig_atomic_t和volatile。
               第二,就是实现线程间同步,让线程有序访问变量

27、线程中常用方法的区别
               线程中用到最多的是start方法,它的作用是启动一个线程
               Sleep()让线程休眠一会,但不会释放锁
               Join()强制执行一个线程
               Wait()是Object类中的方法,会让当前线程放弃对象锁,进入对象等待池
               Yield()礼让线程,让另外一个线程执行一会,自己再执行,只能让同优先级的线程有执行的机会
               currentThread()用于获得当前线程对象,可以调用getName()方法获得当前线程名字

28、多线程和单线程有什么区别?
         干一件事情一个人干和两个人同时干的区别

29、线程的5种状态,__、_、_、___。启动调用_方法,启动后会调用__方法。
               新生
               就绪
               运行
               阻塞
               死亡
               启动调用start()

30、用同步块与同步方法的区别?
               同步方法是被synchronized修饰的方法,同步整个方法, ,同一时间只有一个线程可以访问该方法
               同步块是被synchronized修饰的代码块,可以同步一小部分代码             
               同步代码越少性能越好,当性能要求比较高时,用同步块       

31、写二个线程,对一个int类型一个i++,一个i--
        
32、说说stop为什么不建议使用。
        Stop()方法是从外部强行终止一个线程,会导致不可预知的错误,比如线程锁没有归还,io流不能关流
        线程只能调用interrupt()方法中断,而且不是立即中断,(
                   只是发出了一个类似于信号量的东西,通过修改了被调用线程的中断状态来告知那个线程,说它被中断了,至于什么时候中断,这个系统会判断)系统会在一个合适的时候进行中断处理
 
33、Runable接口的方法是什么?
        Run
 
34、什么是同步和异步,分别用例子说明,同步有几种方式?
        同步是排队去做事情,异步就是各做各的

35、什么是对象锁?
1.   java中的每个对象都有一个锁,当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法在去访问该syncronized 方法了,直到之前的那个线程执行方法完毕后,其他线程才有可能去访问该synchronized方法。
2.如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronzed方法,那么在该方法没有执行完毕前,其他线程无法访问该对象的任何synchronzied 方法的,但可以访问非synchronzied方法。
3.如果synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchuronized方法所在对象的对应的Class对象,
        [类锁(synchronized修饰的静态方法)]

36、什么是死锁?
死锁就是当有两个或两个以上的线程都获得对方的资源,但彼此有不肯放开,处于僵持状态,此时便造成了死锁。

Top