------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
在我大四的时候,自己去面试过几家软件公司,虽然都是以失败告终,但是我知道失败乃成功之母。
每一次的面试题我都会记录下来,然后认真研究,环绕着题目核心去做例子。
今天在看视频学习的过程中,我遇到了和面试时候一样的一道题,我却做不出来,当时的我很郁闷,慢慢地我学会总结,
希望通过总结来加深我自己的记忆。
面试题一:解释Runnable接口与Thread类的区别
解释Runnable接口与Thread类的区别。大家都知道,Runnable接口与Thread类都是用来创建线程类的,它们都需要实现run()方法,似乎没什么区别。其实,它们还是有区别的,请看以下。
首先大家都应该知道,JAVA的类不允许多继承的,也就是只能继承自一个类。
那么,如果线程类继承了Thread以后,它就不能再继承其他的类了。
而Runnable接口,就不会有这样的问题,因为类是可以实现多个接口的。
另外,Thread提供了很多关于线程的方法,例如,获取线程id、线程名、线程状态等方法。
对于比较复杂一点的线程,可能就需要run()方法中调用这些方法,而Runnable接口使用起来就没有那么方便了。
如果让一个线程类实现Runnable接口,那么当调用这个线程的对象开辟多个线程时,
可以让这些线程调用同一个变量;若这个线程是由继承Thread类而来,那么就会麻烦一些,
则要通过内部类来实现上述功能,利用的就是内部类可以任意访问外部变量这一特性。
可以让这些线程调用同一个变量;若这个线程是由继承Thread类而来,那么就会麻烦一些,
则要通过内部类来实现上述功能,利用的就是内部类可以任意访问外部变量这一特性。
举个例子:
总结:class MyThread implements Runnable { //实现Runnable接口 int index = 0; //index变量 public void run() { ............... } } class MyThread { int index = 0; private class InnerClass extends Thread { //定义一个内部类,继承Thread public void run() { ............. } } Thread getThread() { return new InnerClass(); //这个函数的作用是返回InnerClass的一个匿名对象 } }
(1)、线程类继承自Thread则不能继承自其他类,而Runnable接口可以。
(2)、线程类继承自Thread相对于Runnable来说,使用线程的方法更方便一些。
(3)、实现Runnable接口的线程类的多个线程,可以更方便的访问同一个变量,而Thread类则需要内部类来进行替代。
面试题二:如何用sychronized来让线程同步
多线程一旦操作同一块内存的数据,就可能造成数据的混乱,也就是常说的线程安全。
Java针对这样的问题,采用sychronized关键字来帮助开发者解决这样的问题。那么sychrnized是怎么使用的呢,下面我来讲解一下。
Java针对这样的问题,采用sychronized关键字来帮助开发者解决这样的问题。那么sychrnized是怎么使用的呢,下面我来讲解一下。
我先给出一个出现线程安全问题的代码:
Class MyThread extends Thread {
public static int index; //静态变量index
public void run() {
for(int i = 0;i<100;i++) {
//循环打印index加1以后的值
System.out.println(getName()+":"+index++);
}
}
}
public class SyncTest {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
通过以上的运行结果可以看出,线程之间并没有等谁执行完以后再执行,而是交织着执行的,这不符合程序的本意
这就出现了一个好办法,就是让它们可能会出现混乱的代码同步,也就是线程之间依次排队获取资源。
在Java中,使用sychronized关键字来保证线程的同步。sychronized的工作原理是这样的:
每个对象都可以有一个线程锁,sychronized可以用任何一个对象的线程锁来锁住一段代码,
任何想进入该段代码的线程必须在解锁以后才能继续执行,否则就进入等待状态。
其中,只有等占用该锁资源的线程执行完毕以后,该锁资源才会被释放。
例如,以上程序代码中的MyThread类可以这样来写,就可以很好地解决冲突的问题:
每个对象都可以有一个线程锁,sychronized可以用任何一个对象的线程锁来锁住一段代码,
任何想进入该段代码的线程必须在解锁以后才能继续执行,否则就进入等待状态。
其中,只有等占用该锁资源的线程执行完毕以后,该锁资源才会被释放。
例如,以上程序代码中的MyThread类可以这样来写,就可以很好地解决冲突的问题:
class MyThread extends Thread {
public static int index;
public static Object obj = new Object();
//用任意一个对象来加锁
public void run() {
synchronized(obj) {
for(int i = 0;i<100;i++) {
System.out.println(getName()+":"+index++);
}
}
}
}
即使其他线程进入run()方法,也需要在获得锁的情况下,才能继续运行。
另外,sysnchronized还可以加在成员方法上,这就叫做 同步方法 ,此时的锁是加在this所引用得对象上的。
加在方法上面,还可以达到把代码段进行进一步细分的作用,也免去了需要找一个对象来加锁的步骤。
注意:不论是同步代码块,还是同步方法,它们都会损失一些效率。
因此,开发者应该根据线程可能出现问题的地方加上同步,
而不必大段大段的加上同步锁,这样可能是程序的性能降低。
因此,开发者应该根据线程可能出现问题的地方加上同步,
而不必大段大段的加上同步锁,这样可能是程序的性能降低。
总结:
sychronized关键字代表要为某一段代码加上一个同步锁,这样的锁是绑定在一个对象上边的。
如果是同步代码块,需要为该sychronized关键字提供一个对象的引用;
如果是同步方法,只需要加一个sychronized关键字的修饰。
sychronized关键字代表要为某一段代码加上一个同步锁,这样的锁是绑定在一个对象上边的。
如果是同步代码块,需要为该sychronized关键字提供一个对象的引用;
如果是同步方法,只需要加一个sychronized关键字的修饰。
面试题三:什么是序列化
序列化,又称“串化”,可以形象的把它理解为把JAVA对象内存中的数据采编成一串二进制的数据。
然后把这些数据存放在可以持久的数据存储设备,如磁盘。
当需要还原这些数据的时候,再通过反序列化的过程,把对象又重新还原到内存中。
java.io.Serializable接口是可以进行序列化的类的标志接口,该接口本身没有任何需要实现的抽象方法。
它仅仅是用来告诉JVM该类的对象的可以进行序列化的,并且它的序列化ID由静态的serialVersionUID变量提供。
serialVersionUID变量其实是一个静态的long型的常量,它的作用在序列化和反序列化的过程中,起到了辨别一个类的作用。
在反序列化的时候,如果两个类的类名完全相同,
就通过serialVersionUID来判断该类是否符合要求,如果不行,则抛出异常。
Java的I/O提供了一对类用作对象的序列化和反序列化,主要包括ObjectOutputStream和ObjectInputStream。
它们的用法和字节流相似,只不过此时处理的是对象,而不仅仅是字节数据了。
总结:
序列化本质上就是把对象内存中的数据按照一定的规则,变成一系列的字节数据,然后再把这些字节数据写入到流中。
而反序列化的过程相反,先读取字节数据,然后再重新组装成Java对象。
所有需要进行序列化的类,都必须实现Serializable接口,必要时还需要提供静态的常量serialVersionUID。
然后把这些数据存放在可以持久的数据存储设备,如磁盘。
当需要还原这些数据的时候,再通过反序列化的过程,把对象又重新还原到内存中。
java.io.Serializable接口是可以进行序列化的类的标志接口,该接口本身没有任何需要实现的抽象方法。
它仅仅是用来告诉JVM该类的对象的可以进行序列化的,并且它的序列化ID由静态的serialVersionUID变量提供。
serialVersionUID变量其实是一个静态的long型的常量,它的作用在序列化和反序列化的过程中,起到了辨别一个类的作用。
在反序列化的时候,如果两个类的类名完全相同,
就通过serialVersionUID来判断该类是否符合要求,如果不行,则抛出异常。
Java的I/O提供了一对类用作对象的序列化和反序列化,主要包括ObjectOutputStream和ObjectInputStream。
它们的用法和字节流相似,只不过此时处理的是对象,而不仅仅是字节数据了。
总结:
序列化本质上就是把对象内存中的数据按照一定的规则,变成一系列的字节数据,然后再把这些字节数据写入到流中。
而反序列化的过程相反,先读取字节数据,然后再重新组装成Java对象。
所有需要进行序列化的类,都必须实现Serializable接口,必要时还需要提供静态的常量serialVersionUID。