1 )并行和并行的区别是什么? 并行是指两个或多个事件在同一时间发生。 同时是指两个或多个事件以相同的时间间隔发生。 没有同时断开CPU资源; 并发线程必须抢占CPU资源。
并行执行的线程之间没有切换; 并发操作系统根据任务调度系统向线程分配线程的CPU执行时间,并切换线程的执行。
2 )线程和进程之间的区别? 1、进程是资源分配的最小单元,线程在程序运行的最小单元(资源调度的最小单元)程序中至少有一个进程,进程中至少有一个线程。
2、进程有自己的独立地址空间,每次进程启动时,系统都会为其分配地址空间,并建立数据表维护代码段、堆栈段和数据段。 这个操作非常昂贵。 由于线程共享进程中的数据并使用相同的地址空间,因此CPU切换线程的成本远远小于进程,同时创建线程的开销也远远小于进程。
3、线程间通信更方便,同一进程下的线程共享全局变量、静态变量等数据,但进程间的通信需要通过通信方式(IPC )进行。 但是,如何处理同步和互斥是编写多线程程序的难点。
4、但是,多线程程序更结实,多线程程序只要死一个线程,整个进程也会死。 一个过程死了,也不会影响另一个过程。 因为进程有自己的地址空间。
3 )创建线程有哪些方法?(1):继承Thread类
这是最直观的方法,通过类继承Thread重写run方法并将其设置为new来创建新线程。
(2):实现Runnable接口
通过实现Runnable接口的run方法,可以获得“可执行任务”,并在new Thread时传递该任务。
(3):Callable+FutureTask
1:首先,让类实现Callable (通用)接口的call方法。 这一步是写“调用的任务”。
2:更新另一个FutureTask (“未来任务”),同时捕获上一步的Callable;
3:最后刷新Thread,同时导入Future。
4:请注意这种方式和上述两种方式的区别。 这种方式可以把返回值还给你的任务。 类型可以由你决定。
4 ) runnable和callable有什么区别? Runnable接口的run ()方法的返回值是void,run ) )方法的代码只是简单地执行。
Callable接口的call ) )方法具有返回值,是通用的,可以与Future、FutureTask配合使用来获取异步执行的结果。
5 :线程有什么状态? 线程通常有五种状态:创建、就绪、运行、阻止和死亡。
创建状态。 生成线程对象时,不会调用该对象的start方法。 这是正在创建线程的状态。
准备就绪的状态。 当调用线程对象的start方法时,线程处于就绪状态,但线程调度器没有将线程设置为当前线程。 准备完毕状态。 时间表运行后,等待中或睡眠中返回后,也处于就绪状态。
运转状态。 线程调度器将就绪线程设置为当前线程,线程进入运行状态,并开始执行run函数中的代码。
阻塞状态。 线程运行时暂停通常是因为等待某个时间发生,然后在某个资源准备好后继续运行。 线程可能会通过sleep、suspend、wait等方法被阻止。
死亡状态。 当线程的run方法执行结束或调用stop方法时,线程将死亡。 对于死线程,无法使用start方法将其准备就绪
6 )守护程序线程是什么以及要做什么?
Java线程分为用户线程和守护程序线程。
守护程序线程是在程序运行时在后台提供通用服务的线程。 所有用户线程停止,进程停止所有守护进程,并退出程序。
如何在Java中将线程设置为守护程序:在开始线程之前调用线程的setdaemon(true )方法。 否则,将抛出IllegalThreadStateException异常,默认为用户线程
守护程序线程也是守护程序线程。 守护程序线程不得访问永久资源(如文件和数据库)或向其写入数据。 这是因为会出现资源无法释放、数据写入中断等问题
7 )乐观锁定和悲观锁定的理解以及如何实现,有哪些实现方法?悲观锁:总是假设最坏的情况,每次去取数据都认为别人会修改,所以每次取数据都要锁门。 这样,如果别人试图获取该数据,就会被屏蔽,直到拿到密钥。 Java内同步原语synchronized关键字的实现是悲观锁定。
乐观锁:儒雅的滑板,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。在Java中j原子变量类就是使用了乐观锁的一种实现方式CAS实现的。乐观锁的实现方式: 使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。
java中的Compare and Swap即CAS(比较和交换) ,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
8:什么是CAS操作,缺点是什么?CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。
CAS缺点:
ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
循环时间长开销大: 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
9:在java中wait和sleep方法的不同?(1):在java.lang.Thread类中,提供了sleep(),
而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程
(2):sleep()可以将一个线程睡眠,参数可以指定一个时间。
而wait()可以将一个线程挂起,直到超时或者该线程被唤醒。
wait有两种形式wait()和wait(milliseconds).
(3):最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互, sleep通常被用于暂停执行。
(4):sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
(5):wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,
而sleep可以在任何地方使用
synchronized(x){ x.notify() //或者wait() } 10:notify()和notifyAll()有什么区别?当一个线程进入wait之后,就必须等其他线程notify/notifyall,使用notifyall,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。如果没把握,建议notifyAll,防止notigy因为信号丢失而造成程序异常。
11:为什么wait, notify 和 notifyAll这些方法不在thread类里面?JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
12:怎么检测一个线程是否拥有锁?在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。
13:synchronized与Lock两者区别区别:
1:Lock是一个接口,而Synchronized是关键字。
2:Synchronized会自动释放锁,而Lock必须手动释放锁。
3:Lock可以让等待锁的线程响应中断,而Synchronized不会,线程会一直等待下去。
4:通过Lock可以知道线程有没有拿到锁,而Synchronized不能。
5:Lock能提高多个线程读操作的效率。
6:Synchronized能锁住类、方法和代码块,而Lock是块范围内的
synchronized可以让代码同步,所谓同步代码就是同一时刻只能一个线程执行这段代码,synchronized可以锁对象,可以锁方法,可以锁class对象。synchronized就是锁对象头,java中每个对象都有固定格式的对象头,对象头中有一个沉默的芒果 word,64位虚拟机中沉默的芒果 word有64个bit,在对象头中有两个bit是用来标志锁的,有一个bit标志是否偏向锁,还有一个bit是锁标志位,所以synchronized给对象加锁就是就是修改对象这两个锁标志位的数值,
一个bit: 1偏向锁 ,0非偏向锁
另一个bit: 01无锁 , 00轻量锁 , 10重量级锁 ,11GC标记
synchronized关键字加到static静态方法和非static静态方法区别synchronized关键字加到static静态方法上是给Class类上锁,简称类锁(锁的事当前类的字节码)
而加到非static静态方法是给对象加锁
lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。lock的基本操作还是通过乐观锁来实现的
lock释放锁的过程:修改状态值,调整等待链表。
lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。
15:JVM中哪个参数是用来控制线程的栈堆栈大小的答:-Xss
16:为什么wait, notify 和 notifyAll这些方法不在thread类里面?JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
17:为什么需要线程池?(1):减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
(2):可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
常见线程池①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
submit()/execute():执行线程池
shutdown()/shutdownNow():终止线程池
isShutdown():判断线程是否终止
getActiveCount():正在运行的线程数
getCorePoolSize():获取核心线程数
getMaximumPoolSize():获取最大线程数
getQueue():获取线程池中的任务队列
allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程这些方法可以用来终止线程池、线程池监控等。
说说线程池创建需要的那几个核心参数的含义
ThreadPoolExecutor 最多包含以下七个参数:corePoolSize:线程池中的核心线程数
maximumPoolSize:线程池中最大线程数
keepAliveTime:闲置超时时间
unit:keepAliveTime 超时时间的单位(时/分/秒等)
workQueue:线程池中的任务队列
threadFactory:为线程池提供创建新线程的线程工厂
rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略
说说submit(和 execute两个方法有什么区别?submit() 和 execute() 都是用来执行线程池的,
execute() 执行线程池不能有返回方法
submit() 可以使用 Future 接收线程池执行的返回值。
shutdownNow() 和 shutdown() 两个方法有什么区别?shutdownNow() 和 shutdown() 都是用来终止线程池的。
shutdown(): 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;
shutdownNow() :会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。
线程池的工作原理当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行饱和策略。
线程池为什么需要使用(阻塞)队列?
(1):因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。
(2):创建线程池的消耗较高。