(1)什么是线程?
线程,是程序执行流的最小单元。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。线程也有就绪、阻塞和运行三种基本状态。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
(2)什么是进程?
进程是系统进行资源分配和调度的基本单位,进程是线程的容器,进程是程序的实体,是一个“执行中的程序”。
特征:
(1)动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
(2)并发性:任何进程都可以同其他进程一起并发执行。
(3)独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
(4)异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
(3)线程与进程区别
(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。
(3)进程的创建调用fork或者vfork,而线程的创建调用thread,进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束
(4)线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
(5)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
(6)线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志
(4)如何在Java中实现线程?
继承java.lang.Thread类
直接调用Runnable接口来重写run()方法
使用Callable和Future
(5)用Runnable还是Thread?
Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好 了
Thread 类中的start() 和 run() 方法有什么区别?
tart()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程。
Java中Runnable和Callable有什么不同?
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象
Java中的volatile 变量是什么?
volatile只修饰变量
(1)volatile能保证变量在多个线程之间的可见性,但不能保证原子性(一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新)
(2)禁止指令重排序优化
volatile 性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
什么是线程安全?Vector是一个线程安全类吗?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
Vector是用同步方法来实现线程安全的
Java中什么是竞态条件? 举个例子说明。
多个线程同时访问相同的资源并进行读写操作,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。在临界区中使用适当的同步就可以避免竞态条件。 界区实现方法有两种,一种是用synchronized,一种是用Lock显式锁实现。
Java中如何停止一个线程?
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
使用interrupt方法中断线程。
一个线程运行时发生异常会怎样?
如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。
如何在两个线程间共享数据?
你可以通过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构。用wait和notify方法实现了生产者消费者模型。
Java中notify 和 notifyAll有什么区别?
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地,notify不能保证获得锁的线程,真正需要锁,并且可能产生死锁。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行
为什么wait, notify 和 notifyAll这些方法不在thread类里面?
(1)JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。
(2)由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象。
(3)如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。
为什么wait、notify和notifyAll要在同步块中执行?
(1)如果调用某个对象的wait()、notify和notifyAll方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()、notify和notifyAll方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
(2)Java API规范规定,如果不在同步块中执行会抛出IllegalMonitorStateException异常
(3)为了避免wait和notify之间产生竞态条件。(竞态条件会导致程序在并发情况下出现一些bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不确定的bugs。这种bugs很难发现而且会重复出现,因为线程间的随机竞争。)
wait()与sleep()的区别
sleep()方法是Thread类的静态方法,仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。sleep可以在任何地方使用。sleep方法会自动唤醒
wait()方法是object类的方法,用于线程间通信,会释放锁;只能在同步方法或者同步块中使用。调用wait方法的线程,不会自己唤醒,需要线程调用notify()/notifyAll()方法唤醒.
什么是ThreadLocal变量?
ThreadLocal并不是一个Thread,而是Thread的局部变量,ThreadLocal成员变量,每个线程都可以保留一份它的备份数据,通过set方法设置;在线程内部用get方法获取自己备份的数据。这个备份并不是JVM自己备份的,而是通过ThreadLocal的set方法完成的,它的本质是以当前线程的Id为key,存储该线程的数据。如果每个线程set的值都没有关联,那么这个成员的值肯定是线程安全的;但是如果两个线程在set时引用了同一个数据,那么就仍然会存在同步问题。ThreadLocal的本质是,每个线程只能获取到自己set的数据。
ThreadLocal、Synchronized区别?
ThreadLocal它只是一个线程的局部变量(其实就是一个Map),ThreadLocal会为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。这样做其实就是以空间换时间的方式(与synchronized相反),以耗费内存为代价,但大大减少了线程同步(如synchronized)所带来性能消耗以及减少了线程并发控制的复杂度。保证了线程的隔离性
synchronized关键字是Java利用锁的机制自动实现的,一般有同步方法和同步代码块两种使用方式
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离
interrupt、interrupted和isInterrupted的区别?
interrupt()方法用于中断线程。调用该方法的线程的状态为将被置为"中断"状态,不会停止线程,需要用户自己去监视线程的状态为并做处理
isInterrupted 只是简单的查询中断状态,不会对状态进行修改。
interrupted是静态方法,返回的是当前线程的中断状态。
interrupted 是作用于当前线程,isInterrupted 是作用于调用该方法的线程对象所对应的线程。(线程对象对应的线程不一定是当前运行的线程。例如我们可以在A线程中去调用B线程对象的isInterrupted方法。),这两个方法最终都会调用同一个方法,只不过参数一个是true,一个是false;
为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。
(在使用等待或通知机制时,要配合boolean值或能够判断真假的条件,在notify之前改变boolean量的值,让wait返回后能退出while循环,一般在wait()方法的外围加一层while循环,以防止早期通知或在通知遗漏后阻塞在wait方法内)
Java中如何停止一个线程
Jdk1.0有stop()、suspend()、resume()的控制方法,但由于潜在的死锁威胁因此在后序的JDK中被弃用。之后的Java API没有提供兼容其线程安全的停止线程的方法。
当run()或者call()方法执行完的时候线程会自动结束。
如果想手动结束线程,可利用volatile布尔变量来退出run()方法的循环或者取消任务来中断线程。
Java中堆和栈有什么不同?
为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了,它要求线程从主存中读取变量的值。
什么是线程池? 为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
如何避免死锁?
死锁现象:当两个或两个以上进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们将一直阻塞下去。
例如:线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2。接下来,当线程A仍然持有lock1时,它试图获取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。
导致死锁的条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁的办法:
(1)阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
(2)只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
(3)尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短
(4)创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁。
Java中活锁和死锁有什么区别?
这是上题的扩展,活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。一个现实的活锁例子是两个人在狭小的走廊碰到,两个人都试着避让对方好让彼此通过,但是因为避让的方向都一样导致最后谁都不能通过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
怎么检测一个线程是否拥有锁?
在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。
JVM中哪个参数是用来控制线程的栈堆栈小的
-Xss参数用来控制线程的堆栈大小。
有三个线程T1,T2,T3,怎么确保它们按顺序执行?
在多线程中有多种方法让线程按特定顺序执行
用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
Thread类中yield()方法的作用?
yield()方法:是静态方法,暂停正在执行的线程对象,交出CPU,让其他具有相同优先级的线程执行。它不会释放对象锁,且yield无法控制交出CPU权限的具体时间,只能让相同优先级的线程有获取运行的权利
作用:让相同优先级的线程之间能轮换执行,大多数yield()调用会使线程从运行状态到就绪状态。
Java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。
Java中Semaphore(信号量)是什么?
Java中的Semaphore是一种新的同步类,它是一个计数信号。从概念上讲,信号量维护了一个许可集合。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。
如果你提交任务时,线程池队列已满。会时发会生什么?
这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行那么ThreadPoolExecutor’s submit()方法将会抛出一个RejectedExecutionException异常。
Java线程池中submit()和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中,而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。
什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
Swing是线程安全的吗? 为什么?
Swing不是线程安全的,当我们说swing不是线程安全的常常提到它的组件,这些组件不能在多线程中进行修改,所有对GUI组件的更新都要在AWT线程中完成,而Swing提供了同步和异步两种回调方法来进行更新。
Java中的ReadWriteLock是什么?
一般而言,读写锁是用来提升并发程序性能的锁分离技术的成果。Java中的ReadWriteLock是Java 5 中新增的一个接口,一个ReadWriteLock维护一对关联的锁,一个用于只读操作一个用于写。在没有写线程的情况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使用JDK中的ReentrantReadWriteLock来实现这个规则,它最多支持65535个写锁和65535个读锁。
多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使用它了。
volatile 变量和 atomic 变量有什么不同?
这是个有趣的问题。首先,volatile 变量和atomic 变量看起来很像,但功能却不一样。Volatile变量可以确保先行关系,即写操作会发生在后续的读操作之前,但它并不能保证原子性。例如用volatile修饰count变量那么count++ 操作就不是原子性的。而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
Java中synchronized和 ReentrantLock有什么不同?