JAVA面试总结——多线程

多线程

1.如何实现多线程
(1)继承Thread类,重写run()方法


package study_java;

class MyThread extends Thread{//创建线程类
	public void run(){
		System.out.println("Thread body");//线程的函数体
	}
}

public class thread {

	public static void main(String[] args){
		MyThread myThread=new MyThread();
		myThread.start();//开启线程
	}
	
}

值得注意的是,调用start()方法后并不是立即执行多线程代码,而是使得该线程变为可运行状态(runnable),什么时候运行多线程由操作系统决定。
(2)实现Runnable接口,实现run()方法


package study_java;

class Mythread2 implements Runnable{//创建线程类
	public void run(){
		System.out.println("Thread body");
	}
}


public class thread2 {

	public static void main(String[] args){
		Mythread2 mythread2=new Mythread2();
		Thread t=new Thread(mythread2);//参数实例化thread对象
		t.start();//开启线程
	}
}

不管是继承Thread类还是通过Runnable接口来实现多线程的方法,最终还是通过Thread对象的API来控制线程。
继承Thread类和实现Runnable接口的区别?
java只能单继承,因此如果是采用继承Thread的方法,那么以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了。
其次如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,很容易实现资源共享。举例:例如让有一个任务,要卖十张票。Thread类实现多线程就相当于给每个线程分配一个任务。而用runnable实现,相当于多个线程共同完成一个任务。
(3)实现Callable接口,重写call()方法
Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能。主要表现为:
①Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。
②Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。
③运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监视目标线程调用call()方法的情况,当调用Future的get()方法以获取结果时,当前线程就会阻塞,直到call()方法结束返回结果。
前两种方式线程执行完都没有返回值,只有最后一种是带返回值的。
2.多线程同步的实现方法有哪些?
(1)synchronized关键字
synchronized关键字主要有两种方法(synchronized方法和synchronized块)
①synchronized方法,在方法的声明前加入synchronized关键字,例如;

public synchronized void multiThreadAccess()

只有把需要被同步的资源的操作放到上面这个方法中,就能保证这个方法在同一时刻只能被一个线程访问,从而保证了多线程访问的安全性。
②synchronized块。synchronized块可以把任意的代码段声明为synchronized,也可以指定上锁的对象。

synchronized(syncObject){
//访问synObject的代码
}

(2)wait()方法与notify()方法
这两个方法是在Object上定义的,也就是说所有对象都具有着两个方法。当一个线程调用一个对象的wait()方法后,该线程进入阻塞状态,直到这个对象的notify()方法别调用后,当前线程方可解除。这样的好处在于协调两个工作时可以更加灵活。
(3)lock
JDK1.5新增加了Lock接口以及它的一个实现类RenntrantLock(重入锁),Lock也可以用来实现多线程的同步,具体而言,它提供了如下一些方法来实现多线程的同步:
①lock()
以阻塞的方式获取锁,也就是说,如果获取到了锁,立即返回;如果别的线程持有锁,当前线程等待,直到获取锁后返回。
②tryLock()
以非阻塞的方式获取锁。只是尝试性地去获取一下锁,如果获取到锁,立即返回true,否则,立即返回false。
③tryLock(long timeout, TimeUnit unit)
如果获取了锁,立即返回true,否则会等待参数给定地时间单元,在等待地过程中,如果获取了锁,就返回true,如果等待超时,返回false。
④lookInterruptibly()
如果获取了锁,立即返回;如果没有获取锁,当前线程处于休眠状态,直到获得锁,或者当前线程被别的线程中断(会收到InterruptedExeption异常)。它与lock()方法最大的区别在于如果lock()方法获取不到锁,会一直处于阻塞状态,且会忽略interrupt()方法。
3.sleep()方法与wait()方法有什么区别
sleep()和wait()都是使线程暂执行一段时间的方法,sleep()方法与wait()方法区别主要如下:
(1)原理不同。sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,自己暂停执行,把执行机会让给其他线程,计时时间一到,便会自动苏醒;wait()方法是Object类的方法,用于线程见的通信,这个方法会使当前拥有该对象锁的进程等待,知道其他线程调用notify()方法才唤醒。和wait()配套的方法有notify()和notifyAll()方法。
(2)对锁的处理机制不同。sleep()不涉及进程间的通信,调用sleep()方法并不会释放锁。而当调用wait()方法后,线程会释放掉它所占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程调用。
(3)使用区域不一样。sleep()方法可以放在任何地方使用。wait()方法必须放在同步控制方法或者同步语句块中使用。
sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。
4.sleep()方法与yield()方法有什么区别?
Thread的静态方法sleep→static void sleep(long ms)
可以使得当前线程进入阻塞状态指定毫秒。当超时后,该线程会自动回到Runnable状态,等待再次分配时间片运行。该方法声明时会抛出一个InterruptException,所以在使用时需要捕获这个异常。给其他线程执行机会时不考虑线程的优先级,因此也会给低优先级的线程运行的机会。

Thread的静态方yield→static void yield()
该方法用于使当前线程主动让出当次cpu时间片回到可执行状态,等待时间片分配。(将一个线程的操作暂时让给其他线程执行),只会给相同优先级或是跟高优先级线程以运行的机会。
5.终止线程的方法有哪些?
(1)stop():Thread.stop()来终止线程时,它会释放已经锁定的所有监视资源。
(2)suspend():当挂起有锁的线程时,会导致程序发生死锁。
由于以上两种方法的不安全性,Java语言已经不建议使用以上两种方式来终止线程了。一般建议是让程序自行结束进入Dead状态。
6.synchronized和Lock有什么异同?
Java提供两种锁机制来实现对某个资源的同步,synchronized使用Object对象本身的notify、wait、notifyAll调度机制,Lock使用Condition进行线程之间的调度,完成synchronized实现的所有功能。
(1)用法不一样
在需要同步的对象中加入synchronized控制,synchronized既可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。而Lock需要显式地指定起始位置和终止位置。synchronized是托管给JVM的,而Lock的锁定是通过代码实现的,具有更精确的线程语义。

(2)性能不一样
在资源竞争不是很激烈的情况下,synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降得非常块,而ReetrantLock的性能基本保持不变。

(3)锁机制不一样
synchronized获得锁和释放的方式都是在块结构中,当获取多个锁时,必须以相反的顺序释放,并且是自动解锁,不会因为出了异常而导致锁没有被释放从而引发死锁。而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。
7.什么是守护线程
守护线程又称“服务线程”,当所有非守护线程终止,只剩下守护线程,则进程运行终止。用户线程设置守护线程的方法就是在调用start()犯法启动线程之前调用对象的setDaemon(true)方法。
8.join()方法的作用是什么
join()方法是让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码。
9.线程池
(1)什么是线程池?
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
(2)为什么使用线程池?

  • 创建线程和销毁线程的花销是比较大的,这些时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程,再加上业务工作线程,消耗系统资源的时间,可能导致系统资源不足。(我们可以把创建和销毁的线程的过程去掉)
  • 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

(3)线程池有什么作用?

线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
(4)几种常见的线程池以及使用场
①newSingleThreadExecutor(创建一个单线程的线程池)
这个线程池只有一个线程在工作,相当于单线程串行执行所有任务。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。如果这唯一的线程因为异常结束,那么会有一个新的线程来替代它。用于需要保证顺序执行的场景,并且只有一个线程在执行。
②newFixedThreadPool(创建固定大小的线程池)
每提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool(创建一个可缓存的线程池)
如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程。当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。比较适合处理执行时间比较小的任务。
④newSchedueledThreadPool(创建一个大小无限的线程池)
此线程支持定时以及周期性执行任务的需求。适用于需要多个后台线程执行周期任务的场景。
(5)线程池的关闭
关闭线程池可以调用shutdownNows和shutdown两个方法来实现
**shutdownNow:**对正在执行的任务全部发出interrupt()停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。
**shutdown:**线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务。
10.并发
从宏观方面来说,并发是同时户必须办法多种时间,实际上这几种时间并不是同时进行的,而是交替进行的。而由于cpu运算速度非常快,会造成我们的错觉,就是在同一时间进行很多事情。
**微观方面来说,**所有并发处理等待唤醒执行,先后键入队列排队等候 执行。
并发的实质是一个物理cpu在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多个用户共享以提高效率。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值