java 内存回收相关
Java 中只需简单地忘记对象,不需强行破坏它们。垃圾收集器会在必
要的时候自动回收内存。 这里忘记他们,应该把指向对象的引用设置为null
假如垃圾收集器发现一个句柄仍在使用,就不会清除对象。若认为自己的句柄可能象现在这
样被挂起,那么最好将其设为 null ,使垃圾收集器能够正常地清除它们。 一般来说,这类事情,GC会帮你管理,自己不用显式地去设置变量为null。但是如果调用了非java类代码,如C类语言new了内存,这时候需要显示编写finilize函数来释放内存了
垃圾回收这里其实还有比较多深入的内容这里并没有过度展开
final 相关
关于final的解释:对于基本数据类
型, final 会将值变成一个常数;但对于对象句柄, final 会将句柄变成一个常数。进行声明时,必须将句柄
初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。
如果说整个类都是 final(在它的定义前冠以 final 关键字),就表明自己不希望从这个类继承,或者不允
许其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出
于安全方面的理由,我们不希望进行子类化(子类处理)。
注意数据成员既可以是 final,也可以不是,取决于我们具体选择 . 应用于 final 的规则同样适用于数据成
员,无论类是否被定义成 final。将类定义成 final 后,结果只是禁止进行继承—— 没有更多的限制。然
而,由于它禁止了继承,所以一个 final 类中的所有方法都默认为 final。因为此时再也无法覆盖它们。
为什么要把一个方法声明成 final 呢?正如上一章指出的那样,它能防止其他人覆盖那个方法。但也许更重
要的一点是,它可有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定。这样一来,编译器就可
为 final 方法调用生成效率更高的代码。
单例模式,构造器设为私有,然后通过静态方法才能创建实例
抽象类和接口的意义
“过载”是指同一样东西在不同的地方具有多种含义,类似于多态子类和父类方法不同;而“覆盖”是指它随时随地都
只有一种含义,只是原先的含义完全被后来的含义取代了。
继承抽象类必须实现抽象类里面所有的抽象方法,不然继承的也是抽象类。抽象类不能实例化,这也是它的意义所在,为了调用子类的方法而实现的很多接口,不能用于实例化.抽象类里面可以有非抽象方法
接口是更加纯粹的抽象类,并且里面不能有实例化方法。
接口中定义的字段会自动具有 static 和 final 属性。它们不能是“空白 final”,但可初始化成非常数表达
式。 由于置入一个接口的所有字段都自动具有 static 和 final 属性,所以接口是对常数值进行分组的一个好工
具,它具有与 C 或 C++的 enum 非常相似的效果 。 接口里面的方法即为static public属性
关于java的访问控制
使用内部类简化操作
内部类使用的很大一个原因是因为它方便
普通内部类会默认引用外部类的句柄,这在某些情况可能会带来内存泄露的问题,这里可以考虑使用weakReference等方式使得内部类对外部类的引用减轻
匿名内部类不能有构造器,因为它是匿名的,而且要接触的局部变量应该是final类型的
为正确理解 static 在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个句柄
静态内部类的效果
为创建一个 static 内部类的对象,我们不需要一个外部类对象。这里也就是说静态内部类不持有外部类的句柄
当创建一个内部类并且这个内部类与外部类无紧密联系时候考虑静态内部类,因为它不会默认持有外部类的引用,因此也不会存在内存回收的问题。
如果一个类要被声明为static的,只有一种情况,就是静态内部类。如果在外部类声明为static,程序会编译都不会过。在一番调查后个人总结出了3点关于内部类和静态内部类(俗称:内嵌类)
静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法
静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。
静态内部类可以单独初始化:
Inner i = new Outer.Inner();
普通内部类初始化:
Outer o = new Outer();
Inner i = o.new Inner();
4. 静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计,在知道如何初始化静态内部类,在《Effective Java》第二章所描述的静态内部类builder阐述了如何使用静态内部类:
一个常见的使用方式,在effective java里面有描述到:
public class Outer {
private String name;
private int age;
public static class Builder {
private String name;
private int age;
public Builder(int age) {
this.age = age;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withAge(int age) {
this.age = age;
return this;
}
public Outer build() {
return new Outer(this);
}
}
private Outer(Builder b) {
this.age = b.age;
this.name = b.name;
}
}
生成一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:Outer.Inner in = new Outer.Inner();而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类(正常情况下,你不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分,因为它是static 的。只是将嵌套类置于接口的命名空间内,这并不违反接口的规则)
java集合类
集合类,主要用的就是Set,List,Map等抽象类接口,具体实现类的选择可以根据不同情况仔细选择。
集合类都可以考虑用父类抽象调用子类具体方法,这里的意思是可以用父类的api来调用子类的方法,可替换性更强
Collects,和Arrays等类有许多有用的静态方法,好多常见的找最大最小,使Collect变为线程安全或是变成不可改变的方法都有。要实现自己的集合类时候,可以从AbstractList等类中继承,能达到减少代码的作用。
集合类里面也有分为线程安全的集合类和非线程安全的集合类,这些在多线程保护数据有效性时候也是比较重要的事情
Exception相关
违例分为Exception和Error,其中Exception才是我们应该管的
IOException和RuntimeException又是其中两大类,IOException需要我们自己去捕获处理,而RuntimeException往往是编译器能帮我们捕获而无须自己捕获。构造器要特别注意违例的问题,子类Exception的捕获要在父类之前才可以更准确捕获Exception
创建对象clone和hashCode的注意事项
创建一个对象时候,如果希望该对象具有clone能力应该做如下事情
实现 Cloneable 接口。这个接口使人稍觉奇怪,因为它是空的
interface Cloneable {}
这里是为了考虑到标记是不是这个类真的有实现Cloneable接口
if(myHandle instanceof Cloneable)
第二个原因是考虑到我们可能不愿所有对象类型都能克隆。所以 Object.clone()会验证一个类是否真的是实
现了 Cloneable 接口。若答案是否定的,则“掷”出一个 CloneNotSupportedException 违例。所以在一般情
况下,我们必须将“implement Cloneable”作为对克隆能力提供支持的一部分。
克隆过程的第一个部分通常都应该是调用 super.clone()。通过进行一次准确的复制,
这样做可为后续的克隆进程建立起一个良好的基础。随后,可采取另一些必要的操作,以完成最终的克隆。
这里的clone,Object.clone()只有基本类型才会做深拷贝,其它都是做浅拷贝,只是拷贝句柄
try-catch
首先了解java线程的调度机制
线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。
要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
join()方法,Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,B不能工作。
Thread t = new MyThread();
t.start();
t.join();
Thread t2=new MyThread().start();
这里表示t执行完后,t2才能执行,多线程就变成有顺序执行了.
另外,join()方法还有带超时限制的重载版本。 例如t.join(5000);则让线程等待5000毫秒,如果超过这个时间,则停止等待,变为可运行状态。
除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:
多个线程之间的管理和复用以前的线程资源可以使用ExcutorService,Excutor等线程管理类,具体详看java并发编程
线程之间的相互作用的许多使用类在concurrent包里面,里面有例如ThreadLocal等线程局部变量,还有Java并发编程:CountDownLatch、CyclicBarrier和Semaphore这些类的使用等。具体详看java并发编程
同步锁:
一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。
同步锁一些细节需要注意
线程的锁有哪些,除了synchronized语言特性上的锁以外,还有在concurrent包里面的lock对象,这里是在api层面对资源进行同步
sychonized是java内置的关键字,通过给方法加上同步快来实现同步,而当一个线程拥有锁的时候,其他线程必须等待,如一个线程进行同步的IO操作,其他线程就不能进行读取IO操作,
这时候效率就比较低,因为其他线程必须等待正在执行的线程完成同步后才能进行操作,而且不能中断线程等待状态,只能一直位于等待状态。
由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。这就是说这是sychonized可中断版本,在没获得锁时候,线程B会默认等待,
假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。然而也可以通过自己调用interrupt()方法进行中断
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
所以,一般情况下通过tryLock来获取锁时是这样使用的:
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
这里的lock是可中断锁,而synchronized语言特性确是不可中断的。
lock还有些子类, ReentrantLock,意思是“可重入锁”,ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
ReentrantReadWriteLock可以让读操作非同步进行,从而提高效率
锁能依照特性分为几类
可重入锁
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
可中断锁
在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。
ReentrantLock lock = new ReentrantLock(true)
ReentrantLock lock = new ReentrantLock(true);
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
如果线程不能不能获得锁会怎么样
如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止。
当考虑阻塞时,一定要注意哪个对象正被用于锁定:
调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
何时需要同步
线程同步小结
线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
对于同步,要时刻清醒在哪个对象上同步,这是关键。
编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
volatile变量的使用:
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
void wait()
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
当然,wait()还有另外两个重载方法:
void wait(long timeout)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos)
导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
以上这些方法是帮助线程传递线程关心的时间状态。
关于等待/通知,要记住的关键点是:必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
concurrent包里面有具有一些多线程较为高级的使用,具体详看concurrent