Java多线程中的锁

Java多线程的锁主要有4种情况,

1. 静态方法中的锁,此时,虚拟机使用的锁为该方法所在类的Class,即用来创建所有该类实例的那个模版,是全局唯一的,无论何种情况下调用该方法,均是线程安全的:

public class Test {  
    public static void main(String[] args) {  
        for(int i = 0;i<5;i++){  
            new R().start();  
        }  
    }  
}  
  
class R extends Thread{  
    static int i=0;  
    static Set set = new HashSet();  
  
    public void run(){  
        while(i<1000000)  
            multiThread();  
    }  
  
    public static synchronized void multiThread(){  
        if(!set.add(i++))  
            System.out.println(i+" is already exists");  
    }  
}

2. 非静态方法中的锁,此时虚拟机使用这个类的实例来作为锁,如果创建了多个实例,各个实例再同时调用该方法,那么这个方法便不安全了:

public class Test {  
    public static void main(String[] args) {  
  
        for(int i = 0;i<5;i++){  
            new R().start();  
        }  
    }  
}  
  
class R extends Thread{  
    static int i=0;  
    static Set set = new HashSet();  
  
    public void run(){  
        while(i<1000000)  
            multiThread();  
    }  
  
    public synchronized void multiThread(){  
        if(!set.add(i++))  
            System.out.println(i+" is already exists");  
    }  
}

这时候会打印一大堆信息出来,如果这个类只创建了一个实例,别的类来调用这个实例的synchronized方法,便是线程安全的:

public class Test {  
    public static void main(String[] args) {  
        for(int i = 0;i<5;i++){  
            new T().start();  
        }  
    }  
}  
  
class R{  
    static Set set = new HashSet();  
    static Integer i=0;  
  
    public synchronized void multiThread(){  
        if(!set.add(i++))  
            System.out.println(i+" is already exists");  
    }  
}  
  
class T extends Thread{  
    static R r = new R();  
  
    public void run(){  
        int i = 0;  
        while(i++<100000)  
            r.multiThread();  
    }  
}

3.  代码片段中加上synchronized,使用本实例做锁:

public class Test {  
    public static void main(String[] args) {  
        for(int i = 0;i<5;i++){  
            new R().start();  
        }  
    }  
}  
  
class R extends Thread{  
    static int i=0;  
    static Set set = new HashSet();  
    public void run(){  
        while(i<1000000)  
            multiThread();  
    }  
  
    public void multiThread(){  
        synchronized(this){  
            if(!set.add(i++))  
            System.out.println(i+" is already exists");  
        }          
    }  
}

这种情况和2一样,只有在这个实例仅存在一个的时候,代码段才是线程安全的,上面的代码中创建了5个R的实例,所以看起来线程安全的代码并不安全,此时,应该加上一个static的对象来作为线程锁,即:

4. 代码片段中加上synchronized,创建一个static的对象做锁:

public class Test {  
    public static void main(String[] args) {  
        for(int i = 0;i<5;i++){  
            new R().start();  
        }  
    }  
}  
  
class R extends Thread{  
    static int i=0;  
    static Set set = new HashSet();  
    static Object lock = new Object();  
    public void run(){  
        while(i<1000000)  
            multiThread();  
    }  
  
    public void multiThread(){  
        synchronized(lock){  
            if(!set.add(i++))  
            System.out.println(i+" is already exists");  
        }          
    }  
}

此时,该方法是绝对线程安全的。

一般来说,采用方法的synchronized比采用代码块的synchronized执行效率要高,从他们生成的虚拟机执行码可以分析出来(没证实过,有兴趣的可以用javap去反编译一下class文件比较一下)

JDK1.5以后,java.util.concurrent这个包里面有大量的多线程环境下可以使用的类,有能够用上的就最好用这个包里面的类,不要再自己去写同步代码了,例如典型的售票程序,可以使用AtomicInteger类来处理:

public class Test {  
    public static void main(String[] args) {  
        for(int i = 0;i<5;i++){  
            new R().start();  
        }  
    }  
}  
  
class R extends Thread{  
    static AtomicInteger ticket = new AtomicInteger();  
    static Set set = new HashSet();      
    public void run(){  
        while(ticket.get() <1000000 )  
            multiThread();  
    }  
  
    public void multiThread(){  
        if(!set.add(ticket.addAndGet(1)))  
            System.out.println(ticket.get()+" is already exists");     
    }  
}

这样子看起来是不是感觉要清爽一点了?

Java新型垃圾回收器G1深入探索

g1垃圾回收器
“g1垃圾回收”的英文全称是 garbage-first garbage collector (又被称作g1 gc),这是一个新型的垃圾回收器,由jdk 7中的java hotspot vm 引入。这个技术曾经在java se 6 update 14版本中出现过一个试验性的,然后 g1 被 hotspot的 反应快速(low-latency)的 concurrent mark-sweep gc (简称 cms)长期取代。
属性
g1 是一个“服务器风格(server-style)”的垃圾回收器,它主要有下面的这些属性:
并行和并发 。 g1 可以从今天最新的硬件中获得并行的能力。它能够使用所有可用的cpu(cpu多核,硬件多线程,等)来加速它的 “stop-the-world” 机制(这个机制简称stw,即,在执行垃圾收集算法时,java应用程序的其他所有除了垃圾收集帮助器线程之外的线程都被挂起)。
分代 处理 。 就像其它的hotspot 垃圾回收器,g1 是分代的,也就是说,它在处理新分配的对象(年轻代)和已经生存了一段时间的对象(年老代)时会不同,它会更多地考虑一些新创建的对象实例,因为越新创建 的就越有最大的可能性被回收,老对象只是偶尔访问一下。对于大多数的java应用来说,这个机制可以极大地提高回收效率。
紧凑内存 (碎 片整理)。 不像cms,g1 会对堆进行内存整理。压缩可以消除潜在的内存碎片的问题,这样程序就可以更长时间的平滑运行。
预见性的 。 g1 比起 cms 来有更多的预见性。这个主要还是用来消除内存碎片的问题。内存的碎片少了,stop-the-world的暂停时间也会被减少。
描 述
比起其它的hotspot 垃圾回收器来说,g1 使用了一种非常不同寻常的方法来管理堆内存的布局。在g1中,在对象新生代和老一代上没有在物理上把他们分隔开来。取而代之的是,它把一个连续的堆内存拆 分成了几个相同大小的区域。新产生的对象和老的对象都会被放在一系列可能不会连续的区域中。之所以这样做,就是为了让g1可以更灵活地移动老对象所占用的 资源给新的对象。
g1中的内存收集会发生 “疏散暂停”,当内存从一系例区域开始回收时,这些区域所引用的 collection set 会被疏散到另一些区域中,这样,我们会有一整块的内存来重新被申请。疏散会发生整个程序的暂停,但“疏散”这些内存可以被并行运行,当然,你要有多核或多 线程技术来支持。绝大多数的“疏散暂停”会去收集那些可用的比较新的内存区域,因此,这和其它的 hotspot 垃圾回收器是相同的。偶而才会去查看一下老区域中的内存是否可以回收。
在 cms中,其周期性的执行一个 concurrent marking phase。 这个phase中最主要的事情是,识别哪些老的区域中充满了可以回收的对象,因为这是最有效率和最合适的回收。但在g1中,g1不会执行那个所谓的 concurrent sweeping phase, 取而代之的是,去识别那些的最合适的老的区域是在并发的“疏散暂停”中进行的(后面会做介绍)。
使 用 g1
g1 目前仍然还在试验阶段,使用下面两个参数可以打开g1机制:
-xx:+unlockexperimentalvmoptions -xx:+useg1gc
下面是设置垃圾回收器的暂停时间:
-xx:maxgcpausemillis =50 (设置暂停时间为 50ms)
在g1中,你还可以给垃圾回收器的暂停设置一个时间间隔:
-xx:gcpauseintervalmillis =200 (设置暂停时间间隔 200ms)
注意,上面的两个参数只是代表目标,回收器并不保证。他们可能在某些情况下工作地很好,也可能在其它情况下不 行,所以,垃圾回收器并不总是服从这两个参数设置。
另外,新生代的内存大小可以被设置,这个参数同样会影响“疏散暂停”的时间:
-xx:+g1younggensize=512m (设置新生代内存为 512兆字节)
g1 同样可以使用survivor 空间,是的,这就是多少个区域。大小可以由通用的参数所指定(如: -xx:survivorratio=6).
最后,如果你要发挥g1的所有潜能,你可以尝试设置下面两个参数,它们默认上是关闭的,因为在一些很 稀有的情况下,这两个参数会发生race condition(竞争条件):
-xx:+g1parallelrsetupdatingenabled
-xx:+g1parallelrsetscanningenabled
还 有一件事是g1能够报告比其它垃圾回收站更详细的信息,当然,你需要设置下面这个参数:
-xx:+printgcdetails
这个参数 会输出很多有用的信息供你查看性能与以 trouble-shooting。如果你想要简单的日志,你可以把这个开关设置到 -verbosegc 。
状 态
◆g1 开发目前主要关注于解决一些残留的稳定性的问题,以及提高性能,并且去除下面的限制:
◆g1 并不完全支持 jvm tool interface (jvm ti) 或 java management extensions (jmx),所以,这些监控和管理工具无法正确地作用于g1。
◆g1 不支持增量的永生代collection。如果一个应用在卸载很多的类,因些需要很多的永生代collection,目前的g1还不支持,不过最终版会支 持。

关于垃圾回收器的暂停时间,g1的表现比起cms来说是时好时坏。所以,还有很多工作需要让g1的表现更加稳定,绝不能比cms还 差,不然g1还有什么意思呢?

Java 构造函数中的super()

public class Test {  
    public static void main(String[] args) {  
        new Son("baidu");  
        new Son();  
    }  
}  
  
class Farther{  
    public Farther(){  
        System.out.println("I am father");  
    }  
  
    public Farther(String name){  
        System.out.println("I am father:"+name);  
    }  
}  
  
class Son extends Farther{  
    public Son(){  
        System.out.println("I am son");  
    }  
    public Son(String name){  
        //super("google");  
        System.out.println("I am son:"+name);  
    }  
}

Java在实例化一个对象的时候,如果没有显式使用super(),则会先调用父类的无参构造函数(不是和自己构造函数参数数量对应的那个),然后调用子类的构造函数,如果父类不是Object类,则一直向上追溯到Object类为止,super()只能在构造函数的第一行使用,在别的地方使用均为非法,一般情况下构造函数不用写super(),但是如果一个类有多个构造函数的时候,为了便于理解,往往要显式调用super()。

上面代码的运行结果为:

I am father
I am son:baidu
I am father
I am son

HashMap Hashtable LinkedHashMap 和TreeMap区别

java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap
Map主要用于存储健值对,根据键得到值,因此不允许键重复,但允许值重复。
Hashmap 是一个 最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable 与 HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了 Hashtable在写入时会比较慢。
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

public static void main(String[] args) {  
  
        Map<String, String> map = new HashMap<String, String>();  
        for(int i=0;i<100;i++){  
            map.put("a"+i,String.valueOf(i));  
        }  
        map.put("a3", "dd");  
        map.put("a2", "bb");  
        map.put("a1", "cc");  
        for (Iterator iterator = map.values().iterator(); iterator.hasNext();) {  
            String name = (String) iterator.next();  
            System.out.println(name);  
        }  
  
        Map<String, String> map1 = new LinkedHashMap<String, String>();  
        map1.put("a3", "dd");  
        map1.put("a2", "bb");  
        map1.put("a1", "cc");  
        for (Iterator iterator = map1.values().iterator(); iterator.hasNext();) {  
            String name = (String) iterator.next();  
            System.out.println(name);  
        }  
  
         Map<String, String> map2 = new TreeMap<String, String>(new Comparator<Object>(){  
            Collator collator = Collator.getInstance();  
            public int compare(Object o1, Object o2) {  
                CollationKey key1 = collator.getCollationKey(o1.toString());  
                CollationKey key2 = collator.getCollationKey(o2.toString());  
                return key2.compareTo(key1);  
                //return collator.compare(o1, o2);  
            }});  
        map2.put("a3", "dd");  
        map2.put("a2", "bb");  
        map2.put("a1", "cc");  
        for (Iterator iterator = map2.values().iterator(); iterator.hasNext();) {  
            String name = (String) iterator.next();  
            System.out.println(name);  
        }  
    }

运行这段代码可以看出这几个之间的区别。

解决Tomcat启动时错误日志不详细的问题

以前用Tomcat用得好好的,项目启动的时候错误日志都会输出到控制台,不知道从啥时候开始,Tomcat的详细错误日志不见了,只报一个万恶的Context [] startup failed due to previous errors,却找不到previous errors具体是啥东西,郁闷了很久,在网上查了一下资料,终于解决了这个问题。

Tomcat的官方原文在http://tomcat.apache.org/tomcat-6.0-doc/logging.html 可以找到。

首先找一个log4j的lib包放在tomcat的lib目录下,log4j包在基本的java项目里面都有,随便找一个扔进去应该就可以了,然后在lib目录新建一个log4j.properties文件,内容为:

log4j.rootLogger=ERROR  
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender  
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout  
log4j.appender.CONSOLE.layout.ConversionPattern=[%p]%t-%c-%m%n  
  
log4j.logger.org.apache.catalina=INFO,CONSOLE

更多的配置内容可以参考log4j的配置。

http://apache.freelamp.com/tomcat/tomcat-6/v6.0.26/bin/extras/ 下载两个文件:

tomcat-juli-adapters.jar放到tomcat的lib目录下,tomcat-juli.jar覆盖到tomcat的bin目录下,注意要下载对应你的tomcat版本的文件。

现在重启tomcat,详细的日志文件又回来了。