String/StringBuffer/StringBuilder,Java序列化,线程安全,线程同步,ThreadLocal

来源:互联网 发布:java方法签名 异常 编辑:程序博客网 时间:2024/05/22 00:38

参考:http://blog.csdn.net/cmershen/article/details/51799040

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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

所以底层判断的原则是先看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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

然后使用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();        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

运行程序,发现生成了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();        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行程序,发现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
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

那么假设有两个线程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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

这堆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();    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

所以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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这个跟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();        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

接下来是remove方法:

     public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);     }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

哈哈,写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;                }            }        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

挺简单的,就是Hash命中了以后取消对封装的那个value的引用,然后rehash一次。

接下来还有一个volatile关键字要介绍。用volatile修饰的变量,线程在每次使用变量的时候,都会去内存中读取一下该变量最后的值。这样不同的线程访问同一变量,每次都看到的是最后的值。


0 0
原创粉丝点击