> 文章列表 > java面试准备14

java面试准备14

java面试准备14

并发编程中的三个概念

原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程都能立刻看到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。

java内存模型

Java内存模型规定所有的变量都是存在主存当中,每个线程都有自己的工作内存。线程对变量的所有修改都必须在工作内存中进行,而不能直接修改主存,并且每个线程都不能访问其他线程的工作内存。
(1)原子性
在java中,对基本数据类型的变量的读取和赋值操作时原子性操作,即这些操作时不能被中断的,要么执行,要么不执行。要实现更大范围操作的原子性,则可以通过synchronized和Lock实现。
(2)可见性
对于可见性,Java提供了volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取该变量时,此时主存中还是原来的旧值。
另外,通过synchronized和Lock也能保证可见性,synchronized和Lock可以保证同一时刻只有一个线程获取锁然后同步执行代码,并且释放锁后会将对变量的修改刷新到主存中。
(3)有序性
在java程序中,允许编译器和处理器对指令进行重排,但是重排不会影响到单线程程序的执行,会影响到多线程。可以使用volatile关键字保证一定的有序性。也可以通过synchronized和lock保证有序性。

Volatile关键字的两层语义

(1)保证了不同线程之间对变量操作的可见性,即一个线程修改了某个变量的值,这个新值对于其他线程是立即可见的。
(2)禁止指令重排序。
(3)无法保证操作的原子性。

线程的几种状态

(1)新建状态。
(2)就绪状态:线程对象被创建后,其他线程调用了该对象的start()方法,该线程位于可运行线程池中,只等待cpu的使用权。也就是说就绪状态的线程除了CPU外,其他运行的所需资源都已经全部获得。
(3)运行状态:就绪状态的线程获取到了CPU,执行程序代码。
(4)阻塞状态:阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行。
阻塞情况分三种:
一、等待阻塞:运行的线程执行wait()方法,该线程会释放所有资源,JVM将该线程放入等待池中。进入这个状态后,是不能被自动唤醒的,必须依靠其他线程调用notify()方法才能被唤醒。
二、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,则JVM会把该线程放入锁池。
三、其他阻塞:运行的线程执行sleep()或者join()方法,或者发出了I/O请求,JVM将其设置为阻塞状态。当sleep()超时、join()等待线程终止或超时、或者I/O处理完毕后,线程进入就绪态。
(5)死亡状态:线程执行完了或者因为异常退出了run()方法,该线程生命周期结束。

线程间通信,wait和notify

(1)wait( ),notify( ),notifyAll( )都不属于Thead类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( )的功能,因为每个对象都有锁。
(2)当需要调用以上的方法时,一定要对竞争资源加锁,否则会报IllegalMonitorStateException 异常。
(3)当想要调用wait()进行线程等待时,必须要去的这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)代码中。
(4)在while循环里而不是if语句下使用wait,这样会在线程暂停恢复后检查wait的条件,并在条件实际上并未修改的情况下唤醒。
(5)notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)
(6)notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
(7)假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。

什么叫线程安全?举例说明

线程安全就是线程同步,就是当一个程序对一个线程安全的方法或者语句进行访问时,其他的程序不能再对齐进行操作了,必须要等到这次访问结束以后才能对这个线程安全的方法进行访问。

如果你的代码所在进程中有多个线程运行,这些线程有可能会同时运行这些代码,如果每次运行结果一致,就是线程安全的。
线程安全问题都是由全局变量或者局部变量引起的。如果每个线程对全局变量和静态变量只有读操作,没有写操作,那么就是线程安全的。
存在竞争的线程不安全,不存在就安全。

如何保证内存的可见性?

(1)对于普通变量:读操作会优先读取工作内存中的数据,如果工作内存中不存在,则从主内存中拷贝一份到工作内存汇总;写操作只会改变工作内存中的副本数据,这种情况下,其他线程就无法读取变量的最新值。
(2)对于volatile变量,读操作时JMM会把工作内存中对应的值设置无效,要求线程从工作内存中读取数据;写操作时JMM会把工作内存中的数据刷新到主存中,这种情况下,其他线程间就可以读取到变量的最新值。

Java线程实现、创建方式

(1)继承Thread类
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并且执行run方法。
(2)实现Runnable
如果当前类已经extends另一个类,那么久无法直接extends Thread,此时可以实现一个Runnable接口。

线程终止的四种方式

(1)正常运行结束
程序运行结束,线程自动结束
(2)使用退出标志退出线程
定义一个退出标志exit。使用关键字volatile修饰,这个关键字的目的是使exit同步,也就是说同一时刻只能由一个线程修改exit的值。
(3)interrupt()方法中断线程
一、线程处于阻塞状态:如果使用了sleep,同步锁的wait,socket中的accept、receiver等方式时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过捕捉该异常,然后break跳出循环,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。
二、 线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
(4)stop方法终止线程(线程不安全)
创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。

Diving skill