阿里面经之解答 by cmershen(3)——String/StringBuffer/StringBuilder,Java序列化,线程安全,线程同步,ThreadLocal
来源:互联网 发布:国有企业分配制度 知乎 编辑:程序博客网 时间:2024/05/22 13:20
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修饰的变量,线程在每次使用变量的时候,都会去内存中读取一下该变量最后的值。这样不同的线程访问同一变量,每次都看到的是最后的值。
- 阿里面经之解答 by cmershen(3)——String/StringBuffer/StringBuilder,Java序列化,线程安全,线程同步,ThreadLocal
- 阿里面经之解答 by cmershen(4)——线程池
- String/StringBuffer/StringBuilder,Java序列化,线程安全,线程同步,ThreadLocal
- 阿里面经之解答 by cmershen(5)——内存泄露,java.util.concurrent包
- 阿里面经之解答 by cmershen(2)——static/final,HashMap/Hashtable/ConcurrentHashMap
- 阿里面经之解答by cmershen(1)——Java的基本特性,面向对象的六大特征等
- Java:代码验证 StringBuffer 线程安全,StringBuilder 非线程安全
- String,StringBuffer,StringBuilder性能比较,线程安全测试,源码解析。
- StringBuffer线程安全StringBuilder线程不安全
- 证明StringBuffer线程安全,StringBuilder线程不安全
- JAVA线程安全之阿里经典面试题
- String 字符串常量,StringBuffer 字符串变量(线程安全),StringBuilder (非线程安全)
- String 字符串常量 StringBuffer 字符串变量(线程安全) StringBuilder 字符串变量(非线程安全)
- JAVA基础——线程、String、StringBuffer
- String,StringBuffer与StringBuilder的区别|线程安全与线程不安全
- String,StringBuffer与StringBuilder的区别|线程安全与线程不安全
- Java中线程安全与线程非安全ArrayList,Vector 和 HashMap,Hashtable 和StringBuffer,StringBuilder
- Java面试题之谈谈String,StringBuilder,StringBuffer区别
- 【JavaScript】匿名函数和闭包
- 安卓7.0已知新特性汇总:这样的Android N你可满意?
- Linux Signal
- 解决:对 PInvoke 函数的调用导致堆栈不对称问题
- Java Web 开发之 Spring 体系结构
- 阿里面经之解答 by cmershen(3)——String/StringBuffer/StringBuilder,Java序列化,线程安全,线程同步,ThreadLocal
- Android自定义View基础学习
- DataTable 转 IEnumerable
- asp.net之Repeater ItemTemplate 图片上传 + 立即显示
- java中替换所有的IP地址
- 10004---弱矩阵、平衡型矩阵和强矩阵简介
- js实现打开页面弹出下载,加载背景图实现
- 正则表达式基本语法
- 大话设计模式--第23章 烤羊肉串引来的思考——命令模式