并发操作问题
1. 什么是进程
进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)。
比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左边的IE浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。
2. 什么是线程
进程是表示自愿分配的基本单位。而线程则是进程中执行运算的最小单位,即执行处理机调度的基本单位。通俗来讲:一个程序有一个进程,而一个进程可以有多个线程。
3. 多线程的几种实现方式
(1) 继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法将启动一个新线程,并执行run()方法。这种方式实现多线程比较简单,通过自己的类直接继承Thread,并重写run()方法,就可以启动新线程并执行自己定义的run()方法。
(2) 实现Runnable接口创建线程
如果自己的类已经继承了两一个类,就无法再继承Thread,因此可以实现一个Runnable接口
(3) 实现Callable接口通过FutureTask包装器来创建Thread线程
(4) 使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。
可返回值的任务必须实现Callable接口;无返回值的任务必须实现Runnabel接口。
执行Callable任务后,可以获取一个Future对象,在该对象上调用get()方法就可以获取到Callable任务返回的Object了。(get()方法是阻塞的,线程无返回结果,该方法就一直等待)
4. 什么是线程局部变量*
ThreadLocal并非是一个线程本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更合适。线程局部变量(ThreadLocal)功能非常简单,就是为每一个使用该变量的线程都提供了一个变量值副本,是java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
5. 进程间如何通讯
管道(pipe)
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
有名管道(namedpipe)
有名管道也是半双工的通信方式,但是它云溪无亲缘关系进程间的通信。
信号量(semaphore)
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
消息队列(messagequeue)
消息队列里有消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递消息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点
信号(signal)
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
共享内存(shared memory)
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
套接字(socket)
套接口也是一种进程间通信机制,以其他通信机制不同的是,它可用于不同进程间的通信
6. 线程间如何通讯
锁机制:包括互斥锁、条件变量、读写锁
互斥锁提供了以排他方式防止数据结构被并发修改的方法
读写锁允许多个线程同时读共享数据,而对写操作是互斥的
条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
信号量机制:包括无名线程信号量和命名线程信号量
信号机制:类似进程间的信号处理
线程间的通信目的只要是用于新城同步,所以线程没有像进程通信中的用于数据交换的通信机制。
7. 同步和异步有何不同,在什么情况下分别使用它们?举例说明
如果数据将在线程间共享。例如:正在写的数据以后可能会被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效。
同步交互:指发送一个请求,需要等待返回,然后才能发送下一个请求,有个等待的过程
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。
区别:一个需要等待,一个不需要等待
8. ConcurrentHashMap 和 Hashtable的区别
它们都可以用于多线程的环境,但当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。
HashTable的任何操作都会把整个表锁住,是阻塞的。好处是:总能获取最实时的更新,比如说线程A调用putAll()写入大量数据,期间线程B调用get(),线程B就会被阻塞,直到线程A完成putAll(),因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都需要排队,效率较低。
ConcurrentHashMap是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是:严格来说,读取操作不能保证反映最近的更新。例如线程A调用putAll()写入大量数据,期间线程B调用get(),则只能get()到目前为止已经顺利插入的部分数据。
JDK8的版本,与JDK6的版本有很大差异。实现线程安全的思想也已经完全变了,它摒弃了Segment(分段锁)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由数组+链表+红黑树的方式思想,但是为了做到并发,又增加了很多复制类,例如TreeBin、Traverser等对象内部类。CAS算法实现无锁化的修改至操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。
9. Hashtable与Hashmap的区别
HashTable与Hashmap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类。
在HashMap中,null可以作为键,这样的键只能有一个;可以有一个或者多个键所对应的值为null;当get()方法返回null时,既可以表示Hashmap中没有该键,也可以表示该键锁对应的值为null。因此,在Hashmap中不能由get()方法来判断HashMap中是否存在某个键,而应该使用containsKey()方法来判断。
在HashTable中,无论键key还是值value都不能为null
这两个 类最大的不同之处在于:
HashTable是线程安全的,它的方法是同步的,可以直接用于多线程环境中
HashMap是线程不安全的,在多线程环境中,需要手动实现同步机制
10. ArrayBlockingQueue的用法
一个线程向一个固定大小的队列里面不停地存放数据,另一个线程不停地向这个队列里面取数据,当队列满了,还继续存放数据,此时出现阻塞,直到队列有空闲的位置;反之,当队列为空,还继续取数据,则也出现阻塞,直到队列中有数据为止
11. 线程和进程有什么区别
线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。
12. 用Runnable还是用Thread
大家知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,哪个方法更好呢?什么情况下使用哪种呢?如果你要继承其他的类,就实现Runnable接口
13. Thread类中的strat()和run()方法有什么区别?
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会在原来的线程中调用,没有新的线程启动,而调用start()方法会启动一个新的线程。
14. java中Runnable和Callable的区别
Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。
他们的主要区别是Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。
15. 什么是java内存模型
java内存模型定义了java虚拟机在计算机内存中的工作方式。JMM决定了一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。
16. 什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果都是一样的,而且其他变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。
17. java中如何停止一个线程
java提供了很丰富的API但没有为停止线程提供API。JDK1.0本来有一些像stop()、suspend()和resume()的控制方法但是由于潜在的死锁威胁,因此在后续的JDK版本中他们被摒弃了,之后Java API的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run()或者call()方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以使用volatile布尔变量来推出run()方法的循环或者是取消任务来中断线程
18. 一个线程运行时发生异常会怎样?
如果异常没有被捕获该线程将会停止执行。
Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌借口。当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理**
19. 如何在两个线程间共享数据?
可以通过共享对象来实现这个目的,或者是使用阻塞队列,或者使用wait()和notify()方法**
20. java中的notify和notifyAll有什么区别?
如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁
当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或者notify()方法(只随机唤醒一个wait线程),被唤醒的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify方法后只要一个线程会由等待池进入锁池,而notifyAll方法会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
notify可能会产生死锁
21. 为什么wait、notify和notifyAll这些方法不再thread类里面
这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不合理的事务的看法。回答这类问题的时候,你要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是java提供的锁时对象级的而不是线程级的,每个对象都有所,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在thread类中,线程正在等待的是哪个锁就不明显了。
22. 什么是FutureTask?
在java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能返回,如果运算尚未完成,get()方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnbale接口所以它可以提交给Executor来执行
23. 为什么wait和notify方法要在同步快中调用?
主要是因为java API强制要求这样做,如果你不这样做,你的代码会抛出IllegalMonitorStateException异常。
为了避免wait和notify之间产生竞态条件
24. java中堆和栈有什么不同?
为什么把这个问题归类在多线程和并发面试题里?因为栈是一块和线程紧密相关的内存区域。每个线程都有自己的栈内存,用于存储本地变量、方法参数和栈调用,一个线程中存储的变量对其他线程是不可见的。而堆是所有线程共享的一片公共内存区域。对象都在堆里创建,为了提升效率,线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile变量就可以发挥作用了,它要求线程从主存中读取变量的值
25. 什么是线程池?为什么要使用它?
创建线程需要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变成,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,他们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,java API提供了Executor框架让你可以创建不同的线程池。比如单线程吃,数目固定的线程池等
26. 如何避免死锁?
java多线程中的死锁:是指两个或者两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因此死锁会让你的程序挂起无法完成任务,死锁的发生必须满足一下四个条件
互斥条件:一个资源每次只能被一个进程使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
避免死锁最简单的方法就是阻塞循环等待条件,将系统中所有资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或者降序)做操作来避免死锁
27. java中synchronized和ReentrantLock有什么不同?
java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁以外的方法或者块边界,尝试获取锁时不能中途取消等。java5 通过Lock接口提供了更复杂的控制来解决这些问题。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义且它还具有可扩展性。
28. 有三个线程T1、T2和T3,怎么确保它们按照顺序执行?
在多线程中有多重方法让线程按特定的顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成
29. Thread类中的yield方法有什么作用?
yield方法可以暂停当前正在执行的线程对象,让其他有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其他线程一定能占用CPU,执行yield的线程有可能在进入到暂停状态后马上又被执行
30. java中ConcurrentHashMap的并发度是什么?
ConcurrentHashMap把实际map划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数,默认值为16,这样在多线程情况下就能避免争用。
31. java线程池中submit()和execute()方法有什么区别?
两个方法都可以向线程池提交任务,execute()方法的返回类型时void,它定义在Executor接口中,而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口。
32. volatile变量和atomic变量由什么不同?
首先volatile变量和atomic变量看起来很像,但是功能却不一样。
volatile变量可以确保线性关系,即写操作会发生在后续的读操作之前,但它不能保证原子性。例如用volatile修饰count变量,那么count++操作就不是原子性的。
AtomicInteger类提供的atomic方法可以让这种操作具有原子性,如:getAndIncrement()方法会原子性的进行增量操作把当前值加1.,其他数据类型和引用变量也可以进行相似操作。
33. 有哪些不同的线程生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建、就绪、运行、阻塞和死亡5种状态。尤其是当线程启动以后,它不可能一直“霸占”着CPU肚子运行,所以CPU需要在多调线程之间切换,于是线程状态也会多次在运行和阻塞之间切换
————————————————
版权声明:本文为CSDN博主「Cynthia_wpp」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41050155/article/details/88047556