[置顶] 最近积累的一些java细节

最近几天一直在钻研多线程问题,因为这一块一直是弱项,之前一直近不下心来细细学习,这些天网上看了一些资料,感觉受益匪浅,在此记录一下。

昨天看到关于静态类的并发问题,开始看着挺蛋疼的,擦,静态类里面怎么可能会有并发问题,设计有问题吧,用静态类处理全局变量,找抽么@。@。于是蛋疼实验了一下,哈哈,蛮好玩的!如有错误之处,欢迎指正,谢谢!

下面是代码:

首先是一个工具类,用于时间相关计算

public final class TimeUtil {
    /* 这里故意声明了一个全局的calendar实例,让static方法改变这个实例(到底是实例还是句柄,待我灯下测试即知)的日期,凸显并发问题! */
    private static Calendar calendar = Calendar.getInstance();

    /**
     * 根据用户输入时间获取这一年的第几天
     * @param date
     * @return dayOfYear
     */
    public static int getDay(Date date) {
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_YEAR);
    }

    /**
     * 根据用户输入时间获取月份
     * @param date
     * @return month 值为月份-1
     */
    public static int getMonth(Date date) {
        calendar.setTime(date);
        return calendar.get(Calendar.MONTH);
    }

    /**
     * 根据给定字符串获得对应日期
     * @param str 格式 yyyy-MM-dd hh:mm:ss
     * @return
     */
    public static Date getTimeByStr(String str) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        Date time = null;
        try {
            time = df.parse(str);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return time;
    }
接着是一个测试线程类

public class TestStaticThread implements Runnable {

    String timeStr;

    public TestStaticThread(String timeStr) {
            this.timeStr = timeStr;
    }

    @Override
    public void run() {
        try {
            //不加sleep时间效果不明显啊!
            Thread.sleep(100L);

            Date time = TimeUtil.getTimeByStr(timeStr);
            int day = TimeUtil.getDay(time);
            int month = TimeUtil.getMonth(time);

            System.out.println("线程[" + Thread.currentThread().getName() + "]指定日期为[" + timeStr + "], " +
                    "经过幸苦的计算后, 得到的天数为[" + day + "], 得到的月份为[" + (month + 1) + "月]");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最后是程序的入口

public class StaticTest {
    public static void main(String[] args) {
        TestStaticThread testStaticThread1 = new TestStaticThread("2013-02-05 15:15:15");
        TestStaticThread testStaticThread2 = new TestStaticThread("2013-04-10 18:45:30");
        TestStaticThread testStaticThread3 = new TestStaticThread("2013-06-15 18:45:30");
        Thread t1 = new Thread(testStaticThread1, "t1");
        Thread t2 = new Thread(testStaticThread2, "t2");
        Thread t3 = new Thread(testStaticThread3, "t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

很明显,上面的getDay()和getMonth()是有问题的!什么?你说你不相信?好吧就让结果来说明问题!

线程[t2]指定日期为[2013-04-10 18:45:30], 经过幸苦的计算后, 得到的天数为[36], 得到的月份为[2月]
线程[t1]指定日期为[2013-02-05 15:15:15], 经过幸苦的计算后, 得到的天数为[36], 得到的月份为[2月]
线程[t3]指定日期为[2013-06-15 18:45:30], 经过幸苦的计算后, 得到的天数为[166], 得到的月份为[6月]

Process finished with exit code 0

哈哈,果断错了有木有!!

那么,要改正很简单,有两种方法:

1、把getDay()和getMonth()设置成同步方法,即改成如下

public static synchronized int getDay(Date date)
public static synchronized int getMonth(Date date)

2、把上面声明的calendar放到方法体内,即如下所示


public static int getDay(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.DAY_OF_YEAR);
    }
public static synchronized int getMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.MONTH);
    }

但是第一种方法其实还是存在问题的,由于静态锁锁的是这个Class,并不是锁Object,所以如果程序别处用TimeUtile tu = new TimeUtil() 去实例化一个对象,然后一个线程去调用getDay()(用TimeUtil.getDay()),另一线程调用getMonth()(用tu.getMonth() 此处需要把getMonth()改成非静态方法,如果是静态方法是安全的!),其实还是会出现问题的!当然,我们可以重写这个类的构造方法,禁止实例化来保证安全。

而方法二有个弊端,就是如果这个工具类中方法很多,每次都Calendar calendar = Calendar.getInstance();,可能会有一点小问题,和内存相关,由于我现在还不是很清楚静态方法里面GC回收对象的机制是否和非静态方法里面的对象回收机制一样,所以不好给出结论,晚上自己再写程序试一下!

另外有一个小知识点,关于多线程的,那就是wait()方法调用后,线程会立即释放锁,但是notifyAll()只会唤醒其他在等待该锁资源而阻塞的线程,并不会释放当前拥有的锁,这一点非常关键!

此外,要特别感谢一下熔岩的博客,让我学到了不少新姿势,下面是他的关于多线程方面的专题链接:

点击打开链接



你可能感兴趣的:(java,多线程,synchronized,static)