Thinking in Java 读书笔记 (二)

  • RTTI和反射
  • 枚举
  • 注解
  • 并发
  • 死锁
  • ReadWriteLock
  • CountDownLatch
  • CyclicBarrier
  • Exchanger
  • 泛型

RTTI和反射
在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。

所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。
这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。
类加载器首先检查这个类的Class对象是否已经加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加类加载器可能会在数据库中查找字节码)。
在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这是Java中用于安全防范目的的措施之一)。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

使用Class.newInstance()来创建的类,必须带有默认的构造方法。
如果没有声明构造方法,会使用默认的构造方法
如果声明了有参构造方法,需要声明无参的构造方法
可以使用public,protected或者不写,但是不可以使用private

使用类名.class不会初始化静态区域,不会执行构造方法,不会初始化
在使用静态常量时(编译期常量),不会执行静态代码块,不会执行构造方法,不会初始化
在使用静态非常量时,执行静态代码块,不会执行构造方法,会分配存储空间并且初始化该存储空间
在使用编译器常量时,执行静态代码块,不会执行构造方法,会初始化
Class.forName的方式会直接初始化静态代码块,不执行构造方法,会初始化

int.class == Integer.TYPE
int.class != Integer.class

Class ftClass = FancyToy.class ;
FancyToy fancyTop = ftClass .newInstance(); // 得到确切类型
Class《? super FancyToy》 up = ftClass.getSuperclass();
Object obj = up.newInstance(); // Object类型
class.isInstance(t)相当于instanceof,但是通常更好用

RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。
而对于反射机制来说,.class文件在编译时时不可获取的,所以是在运行时打开和检查.class文件。

动态代理是为了提供额外的或不同的操作,而插入的用来代替“实际”对象的对象。
这些操作通常涉及与“实际”对象的通信,因此代理通常充当着中间人的角色。

自己的理解:当一个代理对象调用方法时,会执行InvocationHandler的invoke方法,
在这里我们可以做一些事情,然后我们一般还会传入一个自己的实际对象的引用,再调用这个实际
对象引用的方法。

通过反射修改final域是无效的

枚举
ordinal()方法返回一个int值,这是每个enum实例在声明时的次序,从0开始。
可以使用==来比较enum实例,编译器会自动为你提供equals()和hashCode()方法。
Enum类实现了Comparable接口,所以它具有compareTo()方法。同时,它实现了Serializable接口。
每个元素都是类的static final实例

注解
@Target用来定义你的注解将应用于什么地方
CONSTRUCTOR:构造器的声明
FIELD:域声明(包括enum实例)
LOCAL_VARIABLE:局部变量声明
METHOD:方法声明
PACKAGE:包声明
PARAMERTER:参数声明
TYPE:类,接口(包括注解类型)或enum声明
@Rectetion用来定义该注解在哪一个级别可用,在源代码中(SOURCE),类文件中(CLASS),或者运行时(RUNTIME)
SOURCE:注解被编译器丢弃
CLASS:注解在class文件中可用,但会被VM丢弃
RUNTIME:VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息
@Documented将此注解包含在Javadoc中
@Inherited允许子类继承父类中的注解

没有元素的注解称为标记注解(marker annotation)

如果注解中定义了名为value的元素,并且在应用该注解的时候该元素是唯一需要赋值的,
那么此时不需要使用名-值这种语法,而需要直接在括号中给出所需要的值就可以了。

并发
ExecutorService的submit方法会产生Future对象,它用Callable对返回结果的特定类型进行了参数化。
你可以用isDone方法来查询Future是否已经完成。当任务完成时,它具有一个结果,
你可以调用get()方法来过去结果。
你也可以不用isDone进行检查就直接调用get(),在这种情况下,get()将阻塞,直至结果准备就绪。
你还可以在试图调用get()来过去结果之前,先调用具有超时的get(timeout, unit),
或者调用isDone来查看任务是否完成。

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,
并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,
程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,
只要有任何非后台线程还在运行,程序不会终止。
必须在启动线程之前调用setDaemon方法,才能把它设置为后台线程。
后台线程在不执行finally子句的情况下就会终止其run方法

所有对象都自动含有单一的锁(也成为监视器)。当在对象上调用其任意的synchronized方法的时候,
此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。
对于前面的方法,如果某个任务对对象调用了f(),对于同一个对象而言,就只能等到f()调用结束并释放了锁之后,
其他任务才能调用f()和g()。所以,对于某个特定对象来说,其所有的synchronized方法共享一个锁,
这可以被用来防止多个任务同时访问被编码为对象内存。

JVM负责跟踪对象被加锁的次数。

Java的递增操作不是原子性的。

ExecutorService.awaitTermination如果所有的任务在超时时间达到之前全部结束,则返回true,否则则返回false,表示不是所有的任务有结束了。
shutdownNow结束启动的所有线程,通过调用每个线程的interrupte()
Future通过cancel(true)中断由Executors启动的单个线程

一旦底层资源被关闭,任务将解除阻塞

与sleep()不同的是,对于wait()而言
1)在wait()期间对象所是释放的
2)可以通过notify()、notifyAll(),或者令时间到期,从wait()中恢复执行
第二种,也是更常用形式的wait()不接受任何参数。这种wait()将无限等待下去,知道线程接收到notify()
或者notifyAll()消息

wait()、notify()、notifyAll()必须在同步方法或者同步代码块中执行,不然编译的时候会抛出大致是没有拥有锁的异常
当notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒

死锁
1.互斥条件。任务使用的资源中至少有一个是不能共享的。
这里,一根Chopstick一次就只能被一个Philosopher使用。
2.至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。
也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。
3.资源不能被任务抢占,任务必须把资源释放当做普通时间。
Philospher很有礼貌,他们不会从其他Philospher那里抢Chopstick。
4.必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。
在DeadlockingDiningPhilosophers.java中,因为每个Philospher都试图先得到右边Chopstick,然后得到左边的Chopstick,所以发生了循环等待。

ReadWriteLock
适用于数据结构相对不频繁的写入,但是有多个任务要经常读取这个数据结构的情况

CountDownLatch
它被用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用await()的方法都讲阻塞,直至这个计数值到达0。
其他任务在结束其工作时,可以在该对象上调用countDown()来减少这个计数值。
CountDownLatch被设计为只触发一次,计数值不能被重置。
如果你需要能够重置计数值的版本,则可以使用CyclicBarrier。
调用countDown()的任务在产生这个调用时并没有被阻塞,只有对await()的调用会被阻塞,直至计数值到达0。

方法摘要voidawait()
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。booleanawait(long timeout, TimeUnit unit)
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。voidcountDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。longgetCount()
返回当前计数。StringtoString()
返回标识此锁存器及其状态的字符串。

CyclicBarrier
CyclicBarrier适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一个步骤之前等待,
直至所有任务都完成(看起来有点像join())。它使得所有的并行任务都将在栅栏处列队,因此可以一致地向前移动。
这非常像CountDownLatch,只是CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。
可以向CyclicBarrier添加一个“栅栏动作”,它是一个Runnable,当数值到达0时自动执行——这是CyclicBarrier和CountDownLatch的另一个区别。

方法摘要intawait()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。intawait(long timeout, TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。intgetNumberWaiting()
返回当前在屏障处等待的参与者数目。intgetParties()
返回要求启动此 barrier 的参与者数目。booleanisBroken()
查询此屏障是否处于损坏状态。voidreset()
将屏障重置为其初始状态。

Exchanger
Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。
当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行

泛型
泛型是1.5之后的特性
擦除只是为了兼容之前的版本不得不采取的折中的处理
在泛型中创建数组,使用Array.newInstance()是推荐的方式
newInstance()没有默认的构造器将失败
在泛型之前,所有集合中的对象都是Obejct
取出来都需要向下转型,异常发生在编译期
有了泛型,编译器告诉你需要哪些类型


猜你喜欢

转载自blog.csdn.net/jthou20121212/article/details/72905420