标签:
11.String,StringBuffer,StringBuilder的区别
(1)都是final的,不能被继承。
(2)String长度不可变,另外两个长度是可变的(例如StringBuffer有append方法)
(3)StringBuffer是线程同步的,里面的每一个API都添加了synchronized修饰,而StringBuilder不是线程同步的,因此拥有更好的性能。
12.String有重写Object的hashCode()和toString()方法吗?如果重写equals不重写hashcode会怎么样?
有。Object.java的hashCode是native方法,应该是调用了C++的代码。
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
toString()方法返回的是该对象本身。
重写equals不重写hashCode会怎样?
还是看HashMap.java的代码片段:
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
所以底层判断的原则是先看hashcode是不是相等,再看equals是不是相等。如果重写equals不重写hashcode,就有可能出现两个对象的hashCode不相等,但equals相等,这时HashMap就get不出对应的value,虽然用户重写equals,用自己的逻辑已经判定是“同一对象”。
(如果你认为这两个对象不相同,那么你重写equals令其相同做什么?)
13.Java的序列化
(1)什么是Java的序列化?有什么用?
将一个对象转换成与平台无关的二进制流储存在外存储器中,或在网络中传输。其他程序一旦获得这个二进制流(从文件、从数据库、从网络等)就可以将其转化为Java对象进行操作。
举例:Java的远程方法调用(RMI)就是序列化的具体应用。RMI可以让一个JVM上的对象调用其他JVM上对象的方法。RMI是J2EE的基础。
也可以将一个JavaBean序列化后存储在数据库中。
(2)怎么实现Java的序列化和反序列化:
让一个类实现Serializable接口:
public class Student implements Serializable{
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Student(int id) {
this.id = id;
}
}
然后使用ObjectOutputStream流写入这个类:
public static void main(String[] args) {
try {
ObjectOutputStream s = new ObjectOutputStream(new FileOutputStream("E:\\1.txt"));
Student s1 = new Student(1);
s.writeObject(s1);
s.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
运行程序,发现生成了E:\1.txt文件,其中存储的就是s1对象。
接下来反序列化:
public static void main(String[] args) {
try {
ObjectInputStream s = new ObjectInputStream(new FileInputStream("E:\\1.txt"));
Student s1 = (Student)s.readObject();
System.out.println(s1.getId());
s.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
运行程序,发现s1的学号1已经被读出。
自定义序列化:在特殊情况下,我们有时候不能按照java底层的默认机制对一个对象序列化。这时我们需要在序列化的类中添加readObject()和writeObject()方法,按照自己的逻辑进行序列化和反序列化。如果还需要引用默认序列化方法,则分别调用defaultReadObject()和defaultWriteObject().
有时候我们不能将所有属性都序列化(例如密码等敏感信息),这时需要在不想序列化的属性前面添加transient关键字。
注意:(1)static修饰的属性不能被序列化;(2)如果被序列化对象的属性里面有对象(有点绕),要保证这个对象也是可序列化的。(3)对象的类名,属性会被序列化,而方法不会被序列化。(4)反序列化时要有序列化对象的.class文件,否则强转时会报错。
(5)最好显示声明serialVersionUID:private static final long serialVersionUID = 7247714666080613254L;
因为不同JVM有可能对同一个类生成的serialVersionUID不同,也可能该类的属性改变,这都会导致反序列化不回去,但如果人为指定了serialVersionUID,就不存在上述情况。
常见的序列化协议:XML,JSON
14.Java如何实现多线程?
这个基本上是必问的了,有3种方式:继承Thread类重写run函数,实现Runnable接口,实现Callable接口。
三种方式有什么区别?
继承Thread类,重写run方法,并new这个类调用start方法。(因为java没有多继承,所以这种方式用的很少)
实现Runnable接口,也重写run方法,并使用new Thread(MyThread).start()执行线程。
实现Callable接口和Runnable差不多,实现的是call方法,但可以有返回值。
15. 线程安全
什么是线程安全?如果一个类在多线程访问的情况下,其行为永远与预期一致,就称为线程安全。
反例:如果一个ArrayList类的addItem方法如下实现:
public void addItem(int item) {
items[Size]=item;
Size++;
}
那么假设有两个线程1和2并发调用这个方法,假设此时数组为空,Size=0。
线程1执行到items[Size]=item;
的时候系统调度到线程2工作,线程1暂停,那么线程2也执行items[Size]=item;
语句,那么就会覆盖掉线程1加入的那个数据(因为此时Size都是0),而执行后,Size变成2,数组中却只有1个元素,这就造成了混乱。
如何保证线程安全?
可以对变量使用volatile修饰;也可以对程序段或方法加synchronized修饰。
非线程安全的类(如ArrayList、HashMap等),不能在多线程中共享,但可以在多线程环境中作为某个线程独享的属性。
16.多线程环境中如何进行信息交互?Object类中的wait(),notify(),notifyAll()方法都是干什么用的?
关于这三个方法,JavaAPI是这么解释的(节选自Object.java):
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* <p>
* The current thread must own this object‘s monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object‘s monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
*/
public final void wait() throws InterruptedException {
wait(0);
}
/**
* Wakes up a single thread that is waiting on this object‘s
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object‘s
* monitor by calling one of the {@code wait} methods.
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively competing to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege or disadvantage in being
* the next thread to lock this object.
* @throws IllegalMonitorStateException if the current thread is not
* the owner of this object‘s monitor.
* @see java.lang.Object#notifyAll()
* @see java.lang.Object#wait()
*/
public final native void notify();
/**
* Wakes up all threads that are waiting on this object‘s monitor. A
* thread waits on an object‘s monitor by calling one of the
* {@code wait} methods.
* <p>
* The awakened threads will not be able to proceed until the current
* thread relinquishes the lock on this object. The awakened threads
* will compete in the usual manner with any other threads that might
* be actively competing to synchronize on this object; for example,
* the awakened threads enjoy no reliable privilege or disadvantage in
* being the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object‘s monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*/
public final native void notifyAll();
这堆E文翻译过来大概就是说,wait()方法使得持有该对象的锁的线程阻塞掉,而notify()则是唤醒一个等待该对象的线程,notifyAll()是唤醒所有等待该对象的线程。
调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。而如果有多个线程等待该对象,则notify()方法唤醒的具体是哪一个则由JVM底层的进程调度决定。
17. 多线程共用一个变量需注意什么?
如果我们实例化了一个实现Runnable接口的类的对象代表一个线程,这个类中定义了全局变量且run方法会修改变量时,如果有多个线程同时修改这个变量,就会出现异常情况。(因为全局信息被并行的修改会造成错误。)
而ThreadLocal解决了这个问题。ThreadLocal类可以封装一个对象进去,被ThreadLocal封装的对象对每个线程来说是独享的,也就是说即使 被ThreadLocal封装的对象是全局的,它也会保证在各个线程间独立。
接下来我们简单的研究一下ThreadLocal.java的源码。
ThreadLocal提供了三个API,get(),set(),remove()。分别用于取出线程本地变量、设置、清空。
ThreadLocal底层维护了一个ThreadLocalMap,它是Thread类的一个属性。所以每个ThreadLocalMap均与当前线程一一对应,再里面则是一个Entry[]数组,数组下标为当前线程的Hash值,对应的Entry对象里面封装了Object value。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
所以get方法的具体实现逻辑是,先获得当前线程,再把当前的ThreadLocal对象本身(其中包含被封装对象在每个线程中的版本)传进去,如果当前线程的Hash值命中,且对应下标中存有Entry对象,则返回这个对象,再取出封装在里面的value,强制转换并返回。
set方法的实现细节是:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这个跟get差不多,如果当前线程中有ThreadLocalMap,则把value放进对应的下标中去。当然如果这个下标有可能在数组中不存在或者出现重复,这就要rehash了,在这里不做讨论。
ThreadLocalMap的set实现细节如下:
private void set(ThreadLocal<?> key, Object value) {
// We don‘t use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
接下来是remove方法:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
哈哈,写remove的时候好像作者都变懒了,把get和set里面的前两行压缩到了一行。最后调用了map的remove方法,再看看map的remove方法怎么实现的:
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
挺简单的,就是Hash命中了以后取消对封装的那个value的引用,然后rehash一次。
接下来还有一个volatile关键字要介绍。用volatile修饰的变量,线程在每次使用变量的时候,都会去内存中读取一下该变量最后的值。这样不同的线程访问同一变量,每次都看到的是最后的值。
标签:
原文地址:http://blog.csdn.net/cmershen/article/details/51799040