java基础知识-面试(三)

线程

  • 创建线程有几种不同的方式
    java创建线程有三种方式:
    1.继承Thread类创建线程类
    2.通过Runnable接口创建线程类
    3.通过Callable和FutureTask创建线程,支持返回值,封装在FutureTask中

这里写图片描述

这里写图片描述

实现Runnable和Callable接口的方式基本相同,不过是后者执行call方法有返回值
1.如果要访问当前线程,必须调用Thread.currentThread()方法
2.继承Thread类的线程类不能再继承其它父类(java的单继承决定)

注:一般推荐采用实现接口的方式来创建多线程

线程的生命周期介绍下?
操作系统中线程和进程的概念?
现在的操作系统是多任务操作系统,多线程是实现多任务的一种方式
进程是指一个内存中运行的应用程序,每个进程都有自己独立的内存空间,一个进程中可以启动多个线程,比如在windows中,一个.exe就是一个进程

线程指的是进程中的一个执行流程,一个进程中可以运行多个线程,比如java.exe进程中可以运行很多线程,线程总是属于某个进程,进程中的多个线程共享进程的内存

同时“执行”是人的感觉,实际上线程之间存在轮换执行
多线程能满足程序员高效的编写来达到充分利用CPU的目的

2.线程的生命周期
线程是一个动态执行的过程,它也有一个从创建到死亡的过程

新建状态:执行start方法
就绪状态:执行run方法,系统调度,获取CPU资源
运行状态:run方法运行完成
死亡状态:可用stop和destory函数强制终止
阻塞状态:线程阻塞,让出CPU资源

新建状态:使用new关键字和Thread类或者其子类建立一个线程对象后,该线程就属于新建状态,它保持这个状态到start()这个线程

就绪状态:当线程对象调用了start方法之后,该线程就进入就绪状态,就绪状态的线程处于就绪队列之中,要等待jvm里线程调度器的调度

运行状态:如果就绪状态的线程获取CPU资源,就可执行run(),此时的线程便处于运行状态,处于运行状态的线程最为复杂,它可以变为阻塞状态,就绪状态和死亡状态
阻塞状态:如果一个线程执行了sleep(睡眠),suspend(挂起)方法,失去占用的资源后,该线程就从运行状态进入阻塞状态,在睡眠时已获得设备资源后就可以重新进入就绪状态,可以分为三种:
等待阻塞:运行状态中的线程执行wait方法,使线程进入到阻塞状态(wait会释放持有的锁)
同步阻塞:线程在获取synchronize同步锁是被其它线程占用,因为同步所被其他线程占用,则jvm会把该线程放入锁池中
其它阻塞:通过调用线程的sleep或join发出了I/O请求时,线程就会进入到阻塞状态,当sleep状态超时,join等待线程终止或超时,或者I/O处理完毕,线程重新转入就绪状态(注意sleep不会释放线程所持有的锁)

死亡状态:一个运行状态的线程火气其它终止条件发生时,该线程就切换到终止状态

sleep和wait方法的区别?
这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait方法是属于Object的成员方法
最主要的是sleep方法没有释放锁,而wait方法释放了锁,使得其它线程可以使用同步控制块或方法

wait和notify和notifyAll只能在同步控制方法或同步控制块中使用,而sleep可以在任何地方使用。

sleep方法:表示让一个线程进入休眠状态,等待一定的时间后,自动醒来会进入到可运行状态,不会马上进入到运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep之后,并不会释放它所持有的对象锁,所以也就不会影响其他线程对象的运行,但在sleep的过程中有可能被其他对象调用它的interrupt,产生InterruptedException,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会执行catch语句块,可能还有fincally之后代码

wait方法:

wait属于Object方法,一旦一个对象调用了这个防范必须要采用notify和notifyAll方法唤醒该线程,如果线程拥有某个或同步锁,那么在调用了wait方法后,这个线程就会释放它所持有的所有同步资源,而不限于这个被调用了wait方法的对象,wait方法也同样会在运行过程中被其他对象调用interrupt方法而产生InterruptException,效果以及处理方式同Sleep方法

线程中join方法的作用?
等待该线程终止
等待该线程执行完,才去继续执行和同步顺序执行差不多
举个例子:
现在有A,B,C三个事情,只有做完A和B之后才能去做C,而A和B可以并行完成

这里写图片描述

启动一个线程Thread是用run方法还是start方法
启动一个线程是调用satrt方法,使得线程处于就绪状态,以后可以被jvm调度为运行状态,一个线程必须关联一些具体的执行代码,run方法就是该线程所关联的执行代码

实现并启动线程有两种方法:
1.写一个类继承Thread类,重写run方法,用satrt方法启动线程
2.写一个类实现Runnable接口,实现run方法,用 new Thread(Runnable target).start()方法来启动

多线程原理,调用start方法之后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是这个线程置于就绪状态,然后通过JVM,线程Thread会调用run方法,执行本线程的线程体

先调用start方法,后调用run,这么麻烦,为什么不直接调用run?就是为了实现多线程的优点,没这个start不行

这里写图片描述

这里写图片描述

分别调用两个线程的start方法,thread1和thread2交叉执行的
分别调用两个线程的run方法:thread1和thread2顺序执行

1.调用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码(启动线程等待cpu的调度),通过Thread类的start方法来启动一个线程,这时线程是出于就绪状态,并没有运行,(获取CPU资源调度后)然后通过此Thread类调用方法run来完成其运行操作。这里run方法线程体,它包含了要执行的这个线程的那日哦那个,run方法运行结束后此线程终止,然后CPU再调度其它线程

2.run方法当作普通方法的方式调用,程序还是要顺序执行,要等待run方法体执行完毕后,才可以继续执行下面的代码,程序中只有主线程这一个线程,其程序执行路径还是一条,这样就没有达到多线程的目的

简述synchronized和Lock的区别?
1.Lock是一个接口,而synchronized是java中的关键字
2.synchronized在发生异常时,会自动释放线程占用的锁,因此不会导致死锁现象发生,而Lock在发生异常时,如果没有主动去通过unlock方法去释放锁,很可能会导致死锁现象,因此使用Lock时需要在finally块中释放锁

3.Lock可以让等待锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
4.通过Lock可以知道有没有成功获取锁,儿synchronized却无法办到
5.Lock可以提高多个线程进行读操作的效率
在性能上来讲,如果竞争资源不激烈,两者性能差不多的,首先选用synchronized,而当竞争资源非常激烈时(即有大量的线程同时竞争),此时Lock的性能远远高于synchronized,所以说,在具体使用时要根据具体情况选择

synchronized实现的机制依赖于软件层面上的JVM,synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须通过finally来释放锁
ReentantLock继承接口Lock并且实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作以外,还提供了诸如响应中断锁,可轮询锁请求,定时锁等避免多线程死锁的方法

ReentrantLock默认—竞争锁机制

这里写图片描述

这里写图片描述

如何得不到锁直接放弃的话,那么1000个线程不可能全部得到锁,

尽管java实现锁机制有很多种,并且有些锁机制性能也比synchronized高,但是还是强烈建议在多线程应用程序中使用该关键字synchronized,因为实现方便,后续工作由jvm完成,可靠性高,只有在确定锁机制是当前多线程程序的性能瓶颈时,才会考虑其他锁机制,如RenntrantLock等(ReadLock、WriteLock)

RenntrantLock通过方法lock与unlock来进行加锁和解锁的操作,与synchronized会被jvm控制自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁,为了避免程序出现异常而无法解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作

什么是线程局部变量和原理?

1.ThreadLocal概述:
ThreadLocal,顾名思义:它不是一个线程,而是线程的一个本地化对象,当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程分配一个独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本,从线程的角度看,这个变量就是线程的本地变量

1,ThreadLocal不是线程,是线程的一个变量,你可以先简单的理解为线程类的属性变量
2.ThreadLocal是在类中通常定义的静态变量
3.每个线程都有自己的ThreadLocal,它是变量的一个拷贝,修改它不影响其他线程

总结:每个线程独立拥有一个变量,单个线程内共享,多个线程不共享
TheradLocal适用多线程资源共享,但不共享变化

ThreadLocal究竟是如何工作的?
1.Thread类中有一个成员变量叫ThreadLocalMap,它是一个Map,它的key是ThreadLocal类
2.每个线程拥有自己的申明为ThreadLocal类型的变量,所以这个类的名字叫ThreadLocal,线程自己的变量
3.此变量的生命周期是由线程决定的,开始于第一次初始化(get或者set方法)
4.由ThreadLocal的工作原理决定了,每个线程独自拥有一个变量,并非共享或者拷贝

java中常见的线程池?

创建线程需要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限, 为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称作为线程池,里面的线程叫工作线程,从JDK1.5开始,javaAPI提供了Executor框架可以让你创建不同的线程池

常见的线程池:
newSingleThreadExecutor:创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务,如果这个唯一的线程因为异常而结束,那么会有一个新的线程来替代它,此线程池保证所有的任务的执行顺序按照任务的提交顺序执行

newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,知直线程达到线程池的最大大小,线程池大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程

newCachedThreadPool:创建一个可缓存的线程池,如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务增加时,此线程池又可以智能的添加新的线程来处理任务,此线程池不会对线程池的大小做限制,线程池的大小完全依赖于操作系统或者说JVM能够创建的最大线程的大小

newScheduledThreadLocal:
创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求

java对线程池的支持的比较重要的几个概念:
Exector:线程池的顶级接口(相当于集合类的Collection)
ExecutorServcie:真正的线程池接口,继承Exector
ScheduledExecutorServcie:能和timer/timerTask类似,解决那些需要任务重复执行的问题
ThreadPoolExector :ExecutorServcie的默认实现
ScheduledThreadPoolExector:继承ThreadPoolExector的ScheduledExecutorServcie接口的实现,周期性任务调度的类实现

猜你喜欢

转载自blog.csdn.net/qq_19704045/article/details/81412384