1.equals
实现对非null对象的等价关系判断
- 自反性,对于任何非null引用对象x,x.equals(x)=true
- 对称性,对于任何非null引用对象x和y,x.equals(y)=y.equals(x)
- 传递性,对于任何非null引用对象x,y和z,如果x.equals(y),y.equals(z),则x.equals(z)
- 一致性,对于任何非null引用对象x和y,多次调用x.equals(y)得到的结果是一致的,前提是不修改对象的equals比较中使用的信息(默认equals实现是根据内存地址进行比较,即是说只要不把x或y指向另一个对象,而不是说修改对象的属性,修改属性依然是同一个对象;举例,如果person1与person2两对象通过身份证号作为信息进行比较是否相等,那么这两个对象的身份证号不能被修改)
- 对于任何非null引用对象x,x.equals(null)=false
2.hashCode
默认是通过内存地址转换成的一个整数
应该遵循以下约定
- 在一次java应用执行期间,同一对象无论调用多少次hashCode方法,都得到同一个整数值,前提是不修改对象的equals比较中使用的信息(参考equals第4点说明)
- 如果x.equals(y)==true,那么应该有x.hashCode()=y.hashCode()
- 如果x.equals(y)==false,那么应该有x.hashCode()!=y.hashCode(),我们应该有意识,对于不相等的对象,应该产生不同的hash code,这样有得于提高哈稀表的性能(可以尽量将元素打散在不同bucket中)
重写equals方法时需要一起重写hashCode,相等的对象必须有相等的hash code
- 是为了遵守以上equals和hashCode的约定
- HashSet、HashMap、HashTable中,这些容器的一些接口需要进行比较对象,是通过调用对象的hashCode方法来比较的。
3.wait
- 调用
wait()
方法前,必需要首先获取锁对象的监视器(monitor),即需要在synchronized代码块中,否则抛IllegalMonitorStateException - 调用
wait()
方法后,当前线程挂起休眠,释放锁,并进入锁的wait set集合中,直到重新获取到锁再继续执行 - 调用
wait()
后休眠的线程A如何再次唤醒,其它线程对同一个锁调用notify
方法,如果此时wait set中有<线程A,线程B,线程C>,刚好随机唤醒到线程A;或者调用了notifyAll
,则A,B,C都被唤醒;唤醒的线程进入entry set等待竞争锁 - 线程在休眠的时候可能会被虚假唤醒(非notify),虽然这种情况机率小,程序需要防范这种情况,应该把条件判断写在while循环中
synchronized (lock) {
while (<条件未满足>){ //这里加个{}比较好理解,如果是两个线程之间A-B进行通讯用if不会出现什么问题,但是如果有多个线程,本来A在等待B释放锁lock,但是B释放后(条件已满足)调用notifyAll把C给唤醒了,C把条件修改成了未满足并释放锁,此时A就算获取到lock也不满足条件,如果使用if那么A会继续执行“自己写的逻辑”可能会出错,使用while的话,A被唤醒后会再一次判断条件是否满足,不满足继续wait
lock.wait();
}
...写自己的逻辑...
}
锁池与等待池
JVM机会为每个对象维护两个集合:Entry Set和Wait Set;lock的Entry Set存储着等待竞争lock这个锁的所有线程,叫锁池,lock的Wait Set存储着执行了lock.wait()的线程,也就是等待池。
4.notify
- 调用
notify
方法前,必需要首先获取锁对象的监视器(monitor),即需要在synchronized代码块中 - 线程A调用notify后,从锁的wait set中任意唤醒其中一个线程B,B进入到锁的entry set中
- 虽然B被唤醒了,但并不是随后就到B执行,B仍然作为一个普通的线程,与正在等待该锁的其它线程C,D…进行竞争,只有B在竞争中获取到锁后才继续执行
5.notifyAll
- 调用
notifyAll
方法前,必需要首先获取锁对象的监视器(monitor),即需要在synchronized代码块中 - 线程A调用notifyAll后,从锁的wait set唤醒所有线程B,C,D并进入到锁的entry set中
- B,C,D被唤醒后并无特权,作为普通线程对锁进行竞争,只有在竞争中获取到锁后才继续执行
wait/notify在工作中的应用:等待通知机制(消费者-生产者模式),线程间进行通讯
6.clone
- Cloneable是一个标志性的接口,如果类(或父类,非Object)没实现该接口并且调用了clone()方法,则会抛CloneNotSupportedException
- clone是一个native方法,效率高,使用该方法时在jvm中实现field-for-field copy(浅复制),比new一个对象然后赋值好
- clone是一个protect方法,只能在继承链上的类中能使用,实现类override为pulic
- 如果类只有基本属性和不可变属性,那么可以不修改clone方法,类似Person中有Address对象属性,则需要override实现深度复制
public class Person implements Cloneable {
public String name; // 不可变类型
public int age; // 基本类型
public Address addr;
@Override
public Object clone() throws CloneNotSupportedException { // override为public
Person obj = (Person) super.clone(); // 使用Object.clone()本地方法进行浅复制,addr指向同一个对象,p1.addr==p2.addr
obj.addr = (Address) addr.clone(); // 对mutable对象进行深度复制
// ,addr指向不同对象,p1.addr!=p2.addr
return obj;
}
public Person(String name, Integer age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
}
public class Address implements Cloneable{
private String country;
private String city;
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //jvm实现浅复制,都是基本类型的属性
}
public Address(String country, String city) {
this.country = country;
this.city = city;
}
}
public class CloneableTest {
public static void main(String[] args) throws CloneNotSupportedException {
Address addr1 = new Address("中国", "广州");
Person p1 = new Person("test", 20, addr1);
Person p2 = (Person) p1.clone();
System.out.println(p1 == p2); // false
System.out.println(p1.age == p2.age); // 基本类型 true
System.out.println(p1.name == p2.name); // 不可变类型 true
System.out.println(p1.addr == p2.addr); // true或false主要依赖peron的clone方法实现
}
}