SLF4J的静态日志系统绑定机制

0

Posted by agilejava | Posted in JAVA | Posted on 02-02-2011

标签:

SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。

SLF4J库类似于Apache Commons-Logging,但Apache Commons-Logging采用的是动态绑定机制,使用ClassLoader寻找和载入底层的日志库,而在OSGI中,不同的插件使用自己的ClassLoader,OSGI的这种机制保证了插件互相独立,然而却使得Apache Commons-Logging无法在OSGI的环境下工作了。SLF4J却没有这个问题,它是如何做到的呢?

一个简单的例子:

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld1 {

  public static void main(String[] args) {

    Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
    logger.debug("Hello world.");
  }
}

SLF4J与日志系统的绑定过程是在getLogger方法中完成的!下面介绍整个绑定过程。Business Process Model

 

通过上图大家可以发现整个静态绑定机制的核心是通过查找类路径下面的org/slf4j/impl/StaticLoggerBinder.class,所以类路径下只能有一个这个类。看到这里大家可能会问了,这个类的包名是org.slf4j.impl,找这个类如何与其他的日志系统连接呢?呵呵,这个很简单,其实slf4j的发布包中包含了为多种日志系统写好的绑定代理,每个日志系统都写好了一个独立的StaticLoggerBinder,由这个类调用相关日志系统的对象!

下图是slf4j发布包中的为各个日志系统写好的绑定代理。

image

为什么没有logback的绑定代理?呵呵,因为logback中已经内置了slf4j的绑定代理。

image

SCA架构知识点ppt

0

Posted by agilejava | Posted in JAVA | Posted on 31-12-2010

标签:

JAVA并发编程小结–并发容器

0

Posted by agilejava | Posted in JAVA | Posted on 08-08-2010

标签:

  1. 由于同步容器削弱了并发性,Java5.0/6.0通过提供几种并发容器类改进同步容器。并发容器是为多线程并发访问而设计的。
                   并发容器                同步容器
           ConcurrentHashMap           同步的HashMap
           CopyOnWriteArrayList           同步List  
           CopyOnWriteArraySet           同步Set
           ConcurrentSkipListMap           同步SortedMap
           ConcurrentSkipListSet           同步SortedSet

    CopyOnWriteArrayList适合用在当多数操作为读取操作时。新的ConcurrentMap接口加入了对常见复合操作的支持,比如“缺少即加入(put-if-absent)”、“相等便替换(replace-if-equal)”和“相等便删除(remove-if-equal)”,等都已经被实现为原子操作。

  2. 用并发容器替换同步容器,这种做法以有很小风险带来了可扩展性显著的提高。
  3. Java5.0提供了两个新的容器类型:QueueBlockingQueue。尽管你可以用List来模拟Queue的行为,但使用Queue能得到更高效的并发实现。Queue的操作并不会阻塞。
        ConcurrentLinkedQueue                一个传统的FIFO队列
               PriorityQueue     一个(非并发)具有优先级顺序的队列

    BlockingQueue扩展了Queue,增加了可阻塞的的插入获取操作。如果队列是空的,一个获取操作会一直阻塞到队列中存在可用元素;如果队列是满的(对于有界队列),插入操作会一直阻塞,直到队列中存在可用空间。阻塞队列在生产者-消费者设计中非常有用。

  4. 由于同步容器在每个操作的执行期间都持有一个锁,可能会花费很多时间,并且这段时间内,其他线程都不能访问这个容器。而并发容器由于采用了一个更加细化的锁机制–分离锁,为并发访问带来了更高的吞吐率,同时几乎没有损失单个线程访问的性能。
  5. 并发容器进一步改进了同步容器类:提供不会抛出ConcurrentModficationException的迭代器,因此不需要在容器迭代中加锁。ConcurrentHashMap返回的迭代器具有弱一致性。而非“及时失败”。弱一致性的迭代器可以容许并发修改,当迭代器被创建时,它会遍历已有元素,而且可以(但不保证)感应到在迭代器创建后,对容器的修改。
  6. 并发容器中的sizeisEmpty方法的语义在反映容器并发特性上被轻微弱化了,因为size的结果相对于在计算的时候可能已经过期,它仅仅是一个估算值,所以允许size返回一个近似值而不是一个精确值。这在一开始让人有些困扰,不过事实上像size和isEmpty这样的方法在并发环境下几乎没有什么用处,因为它们的目标是运动的。所以对这些操作的需求被弱化了。相反,应该保证对最重要的操作进行性能优化,首先就要包括getputcontainsKeyremove
  7. 同步Map实现提供的一个特性是为独占访问加锁,这在ConcurrentHashMap中并没有实现。相比于HashTable与synchronizedMap,ConcurrentHashMap有众多优势,而且几乎没有劣势,只有当你的程序需要在独占访问中加锁时,ConcurrentHashMap才无法胜任。
  8. CopyOnWriteArrayList是同步List的一个并发替代品,通常情况下它提供了更好的并发性,并避免了在迭代期间对容器加锁和复制。“写入时复制(copy-on-write)”容器的线程安全性来源于这样一个事实,只要有效的不可变对象被正确发布,那么访问他们将不再需要更多的同步。在每次需要修改时,它们会创建并重新发布一个新的容器拷贝,以此来实现可变性。
    JAVA代码
  9. 写入时复制容器的迭代器保留一个底层基础数组的引用。这个数组作为迭代器的起点,永远不会被修改。通过阅读COWIterator的代码发现,remove、set、add方法都没有实现。
    JAVA代码

    “写入时复制”容器返回的迭代器不会抛出ConcurrentModificationException。并自返回的元素严格与迭代器创建时相一致,不会考虑后续的修改。当容器迭代操作的频率远远高于对容器的修改的频率时,使用“写入时复制”是个合理的选择。

  10. 阻塞队列(Blocking queue)提供了可阻塞puttake方法,它们与可定时的offer与poll是等价的。Queue的长度可以有限,也可以无限。阻塞队列支持生产者-消费者模式。生产者-消费者模式简化了开发,因为它解除了生产者和消费者类之间的相互依赖的代码。
  11. 最常见的生产者-消费者设计是将线程池与工作队列相结合。生产者-消费者模式同样带来了一些性能方面的提高。生产者和消费者可以并发的这行,如果一个受限于I/O,另一个受限于CPU,那么并发执行的全部产出会高于顺序执行的产出。
  12. 有界队列是强大的资源管理工具,用来建立可靠的应用程序:它们遏制那些可以产生过多工作量、具有威胁的活动。从而让你的程序面对超负荷工作时更加健壮。
                    并发容器                 同步容器
           LinkedBlockingQueue                LinkedList
           ArrayBlockingQueue                ArrayList

    SynchoronousQueue,并不是一个真正的队列,因为他不会为队列维护任何存储空间。不过它维护一个排队的线程清单,这些线程等待将元素加入(enqueue)队列或移除(dequeue)队列。它非常直接的移交工作,减少了在生产者和消费者之间移动数据的延迟时间。

  13. Java6新增了两个容器类型,DequeBlockingDeque,它们分别扩展了Queue和BlockingQueue。Deque是一个双端队列,允许高效的在分别进行插入和删除。
                         接口                     实现类
                       Deque                  ArrayDeque
                 BlockingDeque          LinkedBlockingDeque
  14. 双端队列使它们与一种叫做窃取队列(work stealing)的模式相关联。在窃取工作的设计中,每一个消费者都有一个自己的双端队列,如果消费者完成了自己双端队列中的全部工作,它可以偷取其他消费者的双端队列中的末尾任务。因为工作者线程并不会竞争一个共享的队列任务,所以窃取工作模式比传统的生产者-消费者设计有更佳的可伸缩性。
  15. Synchronizer是一个对象,它根据本身的状态调节线程的控制流。阻塞队列可以扮演一个Synchronizer的角色;其他的Synchronizer包括信号量(Semaphore)、关卡(barrier)以及闭锁(latch)。
  16. 闭锁(latch)是一种Synchronizer,它可以延迟线程的进度直到线程到达终止(terminal)状态。闭锁可以用来特定活动直到其他的活动完成后才发生。
  17. CountDownlatch是一个灵活的闭锁实现,允许一个或多个线程等待一个事件集的发生。闭锁的状态包括一个计数器,初始化一个正数,用来表现需要等待的事件数。countDown对计数器做减法操作,表示一个事件已经发生过了,而await方法等待计数器到达零,此时所有需要等待的事件都已经发生。如果计数器入口时值为非零。await会一直阻塞直到计数器为零,或者等待线程中断以及超时。
  18. FutureTask同样可以作为闭锁。(FutureTask的实现描述了一个抽象的可携带结果的计算)。FutureTask的计算是通过Callable实现的,它等价于一个可携带结果的Runnable,并且有三个状态:等待、运行和完成。

JAVA并发编程小结–任务执行

0

Posted by agilejava | Posted in JAVA | Posted on 08-07-2010

标签:

  1. 大多数并发应用程序是围绕执行任务(task)进行管理的。所谓任务就是抽象、离散的工作单元(unit of work)。围绕任务执行来管理应用程序时,第一步要指明一个清晰的任务边界(task boundaries)。理想情况下,任务是独立的活动:它的工作并不依赖于其他任务的的状态、结果或者边界效应(side effect)。另外独立性有利于并发性。
  2. 围绕任务来管理应用程序时,第一步要指明一个清晰的任务边界。清晰的边界加上合适的任务执行策略有助于提高应用程序的吞吐量与响应性,并防止应用负荷过载。
  3. 顺序的处理任务导致资源利用率非常低;“每任务每线程(thread-per-task)”方法是对顺序化执行良好的改进,可以带来更快的响应性和更大的吞吐量。但也存在无限制创建线程的问题:
    1. 线程声明周期的开销:线程的创建与关闭不是免费的。创建线程需要时间,会带来请求的延迟。如果请求是频繁且轻量的,为每个请求创建一个线程的做法会消耗大量资源。
    2. 资源消耗:活动线程会消耗系统资源,尤其是内存。大量线程在竞争CPU资源时,还会产生其他的性能开销。
    3. 稳定性:应该限制可创建线程的数目。在一定的范围内增加线程可以提高系统的吞吐量,一旦超过这个范围,再创建更多的线程只会拖垮你的程序。
  4. java.util.concurrent提供了一个良好的线程池实现。Executor框架可以异步的执行任务,支持多种任务执行策略,为任务提交和任务执行之间的解耦提供了标准的方法,为使用Runnable描述任务提供了通用的方式。Executor基于生产者-消费者模式。提交任务的执行者是生产者(产生待完成的工作单元),执行任务的线程是消费者(消耗掉这些任务单元)。
  5. ExecutorService的生命周期有三种状态:运行(invokeAll)、关闭(shutdown、shutdownNow)、终止(terminated、awaitTerminated、isTerminated)
  6. 重用存在的线程,而不是创建新的线程,这可以带来两个好处:1 在处理多请求时可以抵消线程创建、消亡产生的开销。2  在请求到达时,线程通常已经存在,因此提高了响应性。
  7. 执行策略: 将任务的提交与任务的执行进行解耦,有助于在部署的阶段选择一个与当前硬件最匹配的执行策略。
  8. Callable是可以携带结果的并可以抛出异常,所以Callable更好,在使用Executor框架时优先使用Callable。一个Executor执行的任务生命周期有4个阶段:创建、提交、开始和完成。
  9. Future描述了任务的生命周期(创建、提交、开始、完成),并提供了相关的方法来获得任务的结果、取消任务以及检验任务是否已经完成还是被取消。

Executor框架的主要类图:

Class Model

JAVA并发编程小结–共享对象

0

Posted by agilejava | Posted in JAVA | Posted on 25-06-2010

标签:

  1. 我们不仅希望能够避免一个线程修改其他线程正在使用的对象的状态,而且希望确保当一个线程修改了对象的状态之后,其他线程能够真正看到改变。
  2. 同步还有另一个重要、微妙的方面:内存可见性。
  3. 为了确保跨线程写入的内存可见性,你必须使用同步机制。
  4. 因为set与get方法都访问了value域,如果一个线程访问了set,另一个线程同时访问了get,它可能就看不到更新的数据了。可以采用同步化的setter和gettter解决这个问题。仅仅同步setter是不够的,调用get的线程仍然能够看见过期值。
  5. JVM允许将64位的读或写划分为两个32位的操作。如果读和写发生在不同的线程,这种情况读取一个非volatile类long就可能会得到一个值的高32位和另一个值的低32位。
  6. 当访问一个共享的可变变量时,为什么要求所有线程由同一个锁进行同步?为了保证一个线程对数值的写入,其他线程也都可见!
  7. 锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变量最新的值,读取和写入线程必须使用公共的锁进行同步。
  8. Java提供一种同步的弱形式:volatile变量。它确保对一个变量的更新以可预见的方式告知其他的线程。相关参考:《Java 理论与实践: 正确使用 Volatile 变量》
  9. 读取一个volatile类型的变量时,总会返回由某一个线程所写入的最新值。
  10. volatile变量虽然方便,但是也存在限制。他们通常被当作标识完成、中断、状态的标记使用。使用时请格外小心:比如volatile的语义不足以使自增操作(++)原子化。原子变量提供了“读-写-改”原子操作的支持,更优于volatile变量。
  11. 加锁可以保证可见性与原子性;volatile变量只能保证可见性
  12. 只有满足了下面所有标准之后,你才能使用volatile变量:
    1. 写入变量时不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值;
    2. 变量不需要与其他的状态变量共同参与不变约束;
    3. 而且,访问变量时,没有其他的原因需要加锁。
  13. 发布一个对象的意思是使它能够被当前范围之外的代码所使用,比如将对象的引用放入公共静态域中、从非私有方法中返回引用,也能发布返回的对象、发布一个内部类实例。
  14. 一个对象在尚未准备好时就将它发布,这种情况称为逸出(escape)。
  15. 不要让this引用在构造期间逸出。一个导致this引用在构造期间逸出的常见错误,是在构造函数中启动一个线程,于是新线程在所属对象完成构造之前就能看见它。
  16. 如果想在构造函数中注册监听器或启动线程,你可以使用一个私有的构造函数和一个公共的工厂方法,这样避免了不正确的构建。
  17. 一个可以避免同步的方式是不共享数据,线程封闭技术是实现线程安全的最简单的方式之一。Swing与JDBC线程池是比较典型的线程封闭技术的应用。
  18. 栈限制是线程限制的一种特例,在栈限制中,只能通过本地变量才可以触及对象。
  19. 一种维护线程限制的更加规范的方式是使用ThreadLocal,它允许你将每个线程与持有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置的最新值。
  20. 概念上你可以将ThreadLocal<T>看作map<Thread,T>它存储了与线程相关的值,不过实事上它并非这样实现的。
  21. 为了满足同步的需求,另一种方法是使用不可变对象,不可变对象永远是线程安全的。
  22. 不可变性并不简单地等于将对象中的所有域声明为final类型,因为final域可以获得一个到可变对象的引用。
  23. 正如“将所有的域声明为私有的,除非他们需要更高的可见性”一样,“将所有的域声明为final型,除非他们是可变的”,也是一条良好的实践。
  24. 不可变对象可以在没有额外同步的情况下,安全地用于任意线程;甚至发布它们时也不需要同步。
  25. 一个正确创建的对象可以通过下列条件安全的发布:
    1. 通过静态初始化器初始化对象的引用;public static Holder = new Holer(42);
    2. 将它的应用存储到volatile域或AtomicReference;
    3. 将它的引用存储到正确创建的对象的final域中;
    4. 或者将它的阴用存储到由锁正确保护的域中。
  26. 一个对象在技术上不是不可变的,但是它的状态不会在发布后被修改,这样的对象称作有效不可变对象。 用高效不可变对象可以简化开发,并且由于减少了同步的使用,还会提高性能。
  27. 发布对象的必要条件依赖于对象的可变性:
    1. 不可变对象可以通过任意机制发布;
    2. 高效不可变对象必须要安全发布;
    3. 可变对象必须要安全发布,同时必须要线程安全或者是被锁保护的。

JAVA并发编程小结–同步容器

0

Posted by agilejava | Posted in JAVA | Posted on 30-05-2010

标签:

  1. 同步容器包括两个部分,一个是VectorHashtable;另一个是它们的同系容器,同步包装类。这些类是由Collections.synchronizedXxx工厂方法创建的。这些类都是通过封装它们的状态,并对每个公共的方法进行同步而实现了线程安全,这样一次只有一个线程能够访问容器的状态。
  2. 同步容器都是线程安全的。但是对于复合操作,有时你可能需要使用额外的客户端加锁(client-side locking)进行保护。复合操作包括:迭代(反复获得元素,直到获得容器中的最后一个元素)、导航(根据一定顺序寻找下一个元素)、缺少即加入(put-if-absent)。
  3. 同步容器遵守一个支持客户端加锁的同步策略,因此只要我们知道应该使用哪一个锁,就有可能针对其他的容器操作创建新的原子操作。同步容器类通过对它的对象自身进行加锁,保护它的每一个方法
  4. 造成迭代不可靠的问题同样可以通过在客户端加锁来解决。
  5. 及时失败(fail-fast)”的意思是当它们察觉容器在迭代开始后被修改,会抛出一个未检查的ConcurrentModificationException
  6. 有一些原因造成我们不愿意在迭代期间对容器加锁,比如容器很大,或者对每一个元素执行的任务耗时比较长,会破坏程序的可伸缩性、死锁的风险等。在迭代期间,对容器加锁的另一个替代办法是复制容器,但复制容器会有明显的性能开销;这样做是好是坏取决于许多因素,包括容器的大小、每一个元素的工作量、迭代操作相对于容器其他操作的频率,以及响应性和吞吐量的需求。
  7. 迭代器有时候是隐藏的,标准容器中的toString的实现会通过迭代容器中的每个元素,来获得关于容器内容格式的良好展现。容器的hashCodeequalscontainsAllremoveAllretainAll方法都会对容器进行迭代。这里得到一个教训:状态和保护它的同步之间差距越大,人们越容易忘记在访问状态时正确使用同步
  8. 正如封装一个对象的状态,能够使它更容易的保持不变约束一样,封装它的同步则可以迫使它符合同步策略

JAVA并发编程小结–线程安全

0

Posted by agilejava | Posted in JAVA | Posted on 26-05-2010

标签:

 

  1. 并不是所有的数据都需要锁的保护–只有哪些被多个线程访问的可变数据。
  2. 对象的内部锁:每个对象都有一个内部锁与其对应。如果一个线程需要排他一致性访问对象的字段,它首先要在访问之前获得该对象的内部锁。当访问完成时需要释放该内部锁。线程在获得该锁和释放该锁期间称作拥有该锁。一旦线程拥有内部锁,其他任何线程都不能再获得该锁,它们在获得该锁时会被阻塞。
  3. 竞争条件:在一些偶发时段里,出现错误结果的可能性对于并发程序来言非常重要。
  4. 最常见的一种竞争条件是”检查再运行(check –then–act)”,使用一个潜在的过期值作为决定下一步操作的依据。
  5. 复合操作原子化,使用原子(Atomic)变量 (variable)确保操作是原子化的。
  6. JAVA提供了强制原子性的内置锁机制:synchronized块。一个synchronized块包括两部分:锁对象的引用,以及这个锁保护的代码块。内部锁在Java中扮演了互斥锁(mutual exclusion lock,也称作mutex)的角色,意味着至多只有一个线程可以拥有锁。
  7. 内部锁是可重进入(Reentrancy)的,因此线程在试图获得它自己占有的锁时,请求就会成功。重进入意味着所有的请求基于每线程(per-thread),而不是基于每调用(per-invocation)的。重进入的实现是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数器为0时,认为锁是未被占有的。
  8. 用锁来协调访问变量,每次都需要同一个锁。
  9. 虽然同步方法确保了不可分割操作的原子性,但是把多个原子操作整合到一个复合操作时,还是需要额外的锁。
  10. 通过缩小synchronized块的范围来维护线程安全性,我们很容易提升Servlet的并发性。你应该谨慎的控制synchronized块不要过小;绝不可以将一个原子操作分解到多个synchronized块中。不过应该尽量从synchronized块中分离耗时的且不影响共享状态的操作。
  11. 使用两种不同的同步机制会引起混淆,而且安全性与性能也不能从中得到额外的好处。
  12. 决定synchronized块的大小需要权衡各种设计需求,包括安全性、简单性和性能。有时简单性与性能会产生冲突,需要找到一个合理的平衡。

JAVA 动态代理机制与应用

0

Posted by agilejava | Posted in JAVA | Posted on 25-05-2010

标签:

动态代理类

要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

  • java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。
    清单 1. Proxy 的静态方法
      // 方法 1: 该方法用于获取指定代理对象所关联的调用处理器
      static InvocationHandler getInvocationHandler(Object proxy) 
      
      // 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象
      static Class getProxyClass(ClassLoader loader, Class[] interfaces) 
      
      // 方法 3:该方法用于判断指定类对象是否是一个动态代理类
      static boolean isProxyClass(Class cl) 
      
      // 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
      static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
          InvocationHandler h) 
  • java.lang.reflect.InvocationHandler:这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

    清单 2. InvocationHandler 的核心方法

      该方法负责集中处理动态代理类上的所有方法调用。
      第一个参数既是代理类实例。
      第二个参数是被调用的方法对象
      第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
      Object invoke(Object proxy, Method method, Object[] args) 

    每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见 Proxy 静态方法 4 的第三个参数)。

  • java.lang.ClassLoader:这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。 每次生成动态代理类对象时都需要指定一个类装载器对象(参见 Proxy 静态方法 4 的第一个参数)

    Read the rest of this entry »

  • Spring AOP备忘录–@AspectJ与Schema方式

    2

    Posted by agilejava | Posted in JAVA | Posted on 25-05-2010

    标签:,

    在《Spring AOP备忘录–Spring1.X AOP API》中介绍了Spring1.x版本中AOP的使用方式,这种方式用起来还是比较复杂的,备受批评。在Spring2.0中,Spring AOP已经焕然一新了,主要包括两种方式:第一种是@AspectJ注解方式,第二种是基于新的Schema配置方式。
     

    Read the rest of this entry »

    Spring AOP备忘录–Spring1.X AOP API

    0

    Posted by agilejava | Posted in JAVA | Posted on 24-05-2010

    标签:,

    在Spring中使用AOP可以有四种方式:Spring1.X中定义的Spring AOP API、@AspectJ方式、Schema方式、集成第三方AOP框架(比如AspectJ)
    1 Spring1.X中定义的Spring AOP API,包括:Advice,Advisor,ProxyFactoryBean。
    •    Advice包括四种类型:环绕通知,前置通知,异常通知,后置通知。

    Read the rest of this entry »