Java多线程面试相关知识

昨天被问到个多线程的实现方式竟然答不上来,从而重新看了一遍基础知识,得到的总结如下。

通过继承Thread创建线程类步骤如下
1.定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体
2.创建Thread子类的实例,即创建了线程对象
3.调用线程对象的start()方法来启动该线程。

Thread.currentThread():currentThread()是Thread类的静态类,该方法总是返回当前正在执行的线程对象。
getName():该方法时Thread类的实例方法

实现Runnable接口创建线程类步骤如下
1.定义Runnble接口的实体类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
3.调用线程对象的statrt()方法来启动该线程
注意:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run方法仅作为线程执行体。

实现Callable接口
创建线程的三种方式对比

通过继承Thread类或实现Runnable、Callable接口都可以实现多线程,不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。
采用实现Runnable、Callable接口方式创建多线程—
线程类只是实现了Runnable、Callable接口,还可以继承其他类
在这种方式下,多线程可以共享同一个target对象,所以非常适合多个相同的线程来处理同一份资源的情况。从而可以将CPU、代码和数据分开。
劣势,编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()
采用继承Thread类的方式创建多线程
劣势:因为线程类已经被继承类Thread类,所以不能再继承其他父类
优势:编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法,直接使用this即可获取当前线程。
一般推荐采用实现Runnable接口、Callable接口的方式来创建多线程
线程的生命周期
当线程被创建并启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态、在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能直接“霸占”这CUP独自运行,所有CPU需要在多余线程之间切换于是线程状态也会多次在运行阻塞之间切换
如果希望调用子线程的start()方法后子线程立即执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒线程或从运行状态转为阻塞状态,阻塞状态的线程会在合适的时候重新进入就绪状态。
调用yield()方法可以让运行状态的线程之间转入就绪状态
调用stop可以直接让线程死亡

当发生以下情况是线程会进入阻塞状态
线程调用sleep()方法主动放弃所占用的处理器资源
线程调用了一个阻塞模式IO方法,在该方法返回之前,该线程被阻塞
线程试图获取一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的只是、后面将有更深入的介绍
线程在等待某个通知(notify)
程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。

针对上面几种情况,当发生如下特定的情况是可以接触上面的阻塞,让该线程重新进入就绪状态
调用sleep()方法的线程经过指定时间
线程调用的阻塞是IO方法已经返回
线程成功的获取了试图同步的监视器
线程正在等待某个通知是,其他线程发出了一个通知
处于挂起状态的线程被调用了resume()回复方法

线程死亡
线程会以如下3种方式结束,结束后就处于死亡状态
1、run()或call()方法执行完成,线程正常结束。
2、线程抛出一个未捕获的Exception或Error
3、直接调用该线程的stop()方法来结束该线程–该方法容易导致死锁、通常不推荐使用

Join线程
Thread提供了让一个线程等待另一个线程完成的方法,join()方法,当在某个程序执行流中
调用其他线程的join()方法时,调用线程将被阻塞,知道被join()方法加入的join线程执行完为之

join()方法有如下3种重载形式
join():等待被join的线程执行完成
join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待
join(long mililis,int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫微妙

后台线程
有一种线程,它是在后台运行的,它的任务是为其他线程服务,这种线程称为后台线程,又被称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。
后台线程的特征:如果所有的前台线程都死亡,后台线程会自动死亡。
调用Thread对象的setDaemon(true)方法可以将指定线程设置为后台线程。
Thread类还提供了一个isDaemon()方法,用于判断指定线程是否为后台线程。
线程睡眠:sleep
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用,Thread类的静态sleep方法来实现,sleep()方法有两种重载形式。
static void sleep(long millis):让当前正在执行的线程暂停millis毫秒加nanos毫秒,并静茹阻塞状态,该方法受到系统计时器和线程调度器的精度的影响
static void sleep(long millis,int nanos:让当前正在执行的线程暂停millis毫秒加nanos毛微妙,并进入阻塞状态,该方法受到系统计时器和线程调度器的进度与准确度的影响。
线程让步:yield
yield()方法时一个和sleep()方法有点相似的方法,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统重新调度一次
关于sleep()和yield()方法的区别如下
1.sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级,但yield()方法只会给优先级相同,或优先级更高的线程执行机会
2.sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会就绪状态而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立刻再次获的处理器资源被执行
3.sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显示声明抛出该异常,而yield()方法则没有声明抛出任何异常。
sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()来控制并发线程

改变线程的优先级
每个线程执行时都具有一定的优先级,优先级搞的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
每个线程默认的优先级都与创建它的父线程的优先级相同,默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级
Thread类提供了setPriority(int newPriority)、getPriorty()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下3个静态常量:
MAX_PRIORITY:其值是10
MIN_PRIORITY:其值是1
NORM_PRIORITY:其值是5

同步代码块
synchronized(obj){
//此处代码就是同步块代码
}
synchronized后括号里的obj就是同步监视器。线程开始执行同步代码块之前,必须先获得对同步监视器的锁定

同步方法
与同步代码块对应,Java多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。对应同步方法而言,无须显示指定同步监视器,同步方法的同步监视器是this,就是该对象本身。
该类的对象可以被多线程安全的访问
每个线程调用该对象的任意方法之后都将得到正确结果
每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态

释放同步监视器的锁
当线程的同步方法、同步代码块执行结束、当前线程即是否同步监视器
当前线程在同步代码块、同步方法中遇到break、return终止该代码块、该方法的继续执行、当前线程将会是放同步监视器。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法异常结束时,当前线程将会释放同步监视器
当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器
如下情况线程不会释放同步监视器
线程执行同步代码块同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。当然,我们应该尽量避免使用suspend()和resunme()方法来控制线程。

同步锁(Lock)
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是Java5新提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类;为ReentrantReadWriteLocak实现类。

传统的线程通信
假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者,–现在假设系统有一种特殊的要求,系统要求存款者和取钱者不断的重复存款,取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不许取钱者连续两次取钱
为了实现这个功能,可以借助于Object类提供的wait()、notify()和notifyAll()3个方法这3个方法并不属于Thread类,而是属于Object类。但这3个方法必须由同步监视器对象来调用,这可分成以下两种情况。
对于使用synchronized修饰的同步方法因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用者3个方法
对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所有必须使用该对象调用这3个方法。
wait():导致当前线程等待,知道其他线程调用该同步监视器的notify()方法或notiyAll()方法来唤醒该线程,该wait()方法有3种形式,无时间参数的wait(一直等待,直到其他线程唤醒该线程)。带毫秒参数的wait和带毫秒、微妙参数的wait(这两种方法都是等待指定时间后自动苏醒)。调用wait()方法的当前线程会释放对该同步监视器的锁定。
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的,只有当线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程
notifyAll():唤醒在此同步监视器上等待的所有线程

线程池
Java 5新增了一个Executors工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程池。
newCachedThradPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中
newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
newSingleThreadExecuntor():创建一个只有单线程的线程池,它相当于调用newFixedThread Pool()方法时传入参数为1.
newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。
newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。

ExecutorService代表尽快执行线程的线程池(只要线程池中有空闲线程,就立即执行线程任务),程序只要将一个Runnable对象或Callable对象(代表线程任务)提交给线程池,该线程池就会尽快执行该任务。ExecutorService提供了如下3个方法
Future<?>submit(Runnable task):将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务。其中Future对象将在run()方法执行结束后返回null.但可以调用Future的isDone()、isDone()、isCancelled()方法来获取Runnable对象的执行状态。
Futuresubmit(Runnable task,T result):将一个Runnable对象提交给指定的线程池,线程池将有空闲线程是执行Runnable对象代表的任务。其中result显示指定线程执行结束后额返回值,所以Future对象将在run()方法执行结束后返回result
Futuresubmimt(Callable task):将一个Callable对象提交给指定的线程池,线程池将在有空闲线程是执行Callable对象代表的任务。其中Future代表Callable对象里Call()方法的返回值

ScheduledExecutorService 代表可在指定延迟或周期性的执行线程任务的线程池,它提供了如下4中方法。
ScheduledFuture schedule(Calable callable,long delay,TimeUnit unit):指定callable任务将在delay延迟后执行。
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit):指定command任务将在delay延迟后执行。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):指定command任务将在将在delay延迟后执行,而且一设定频率重复执行。也就是说,在initialDelay后开始执行,依次在initialDelay+period、initialDelay+2*period处重复执行,依次类推

使用线程池来执行线程任务的步骤如下
1,调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
2.创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
3.调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例。
4.当不想提交任何任务是,调用ExecutorService对象的shutdown()方法来关闭线程池。

Java7提供了ForkJoinPool来支持CUP多核拆分成多个“小任务”并行计算,在把多个“小任务”结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此事一种特殊的线程池。ForkJoinPool提供了如下两个常用的构造器。
ForkJoinPool(int parallesm):创建一个包含parallelism个并行线程的ForkJoinPool.
ForkJoinPool():以Runtime.availableProcesors()方法的返回值作为parallelism参数来创建 了ForkJoinPool实例之后,就可调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJOinTask task) 方法来执行指定任务了。其中ForkJoinTask代表一个可以并行、合并的任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值