日期:2014-05-20  浏览次数:20714 次

讨论:深入理解“一个对象一把锁”
Java多线程中,临界资源以对象的形式存在,当多个线程访问同一个临界资源时首先要获取该对象的锁,而且一个对象只有一把锁,想必这个原理大家都知道。先看一个例子:现在要从1顺次数到90,开启三个线程,每个线程数三十个,不能重复,不能乱序。题目很简单,只要做好同步就行了。
首先是代码一:
Java code

public class Demo
{
    public static void main(String args[])
    {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        new Thread(mt1, "线程1").start();
        new Thread(mt2, "线程2").start();
        new Thread(mt3, "线程2").start();
    }
}

class MyThread implements Runnable
{
    private static Integer id = 0;

    public void run()
    {
        for(int i = 0; i < 30; i ++)
        {
            synchronized(id)
            {
                id++;
                System.out.println(Thread.currentThread().getName() + " id = " + id);
            }
        }
    }
}


这段代码没能实现要求的结果,出现了同步问题。原因是什么呢?初一看,main方法中new了三个MyThread 对象,但问题是id是static的,三个MyThread 对象共享同一个id,为什么synchronized(id)没有起到应有的作用呢?
好,现在做下改动,把main方法改为下面的样子,其他的不变:
  public static void main(String args[])
  {
  MyThread mt = new MyThread();
  new Thread(mt, "线程1").start();
  new Thread(mt, "线程2").start();
  new Thread(mt, "线程2").start();
  }
运行后会发现,依然存在同步问题,这究竟是什么原因呢?(其实要写出正确的代码很简单,只需要把synchronized(id)改为
synchronized(this)就不会有任何问题了。)

我个人的看法是问题出在id上,,他是Integer类型,跟String类型类似,他们都是final的,不仅类是final的,其底层实现中存储值的变量也是final的,以String为例,其底层实现是final char[],因此每一次赋值都会重新分配内存来存储新的值。Integer类型也是这样,当前运行的线程每次做完id++,就会重新分配内存来存储加一后的id(也会有新的锁,个人猜测),而此线程持有的锁依然是以前的锁,这样当其他的线程试图获取新id的锁就不会有任何障碍了,于是同步错误发生了。
为了验证我的猜测,写了一段代码:
Java code

public class Demo5
{
    public static void main(String args[])
    {
        MyThread5 mt = new MyThread5();
        new Thread(mt, "线程1").start();
        new Thread(mt, "线程2").start();
    }
}

class MyThread5 implements Runnable
{
    private static Integer id = 0;
    
    public void run()
    {
        synchronized(id)
        {
            id++;
            String name = Thread.currentThread().getName();
            if(name.equals("线程1"))
            {
                try
                {
                    Thread.sleep(100);
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            System.out.println(name + " id = " + id);
        }
    }
}



在我机器上,出现最多的结果是
线程2 id = 2
线程1 id = 2
可以看出来,同步失败了。我猜想的过程如下,线程1开始运行,首先看能否获得id的锁,没问题,因为没有其他线程持有该锁,接下来id++,此时内存中有两个Integer,一个内容为0,一个内容为1,id引用的对象是1,而线程1持有的是0的锁(猜想),然后sleep(),sleep不会释放锁,线程1仍然持有0的锁,然后线程2开始运行,也是先看能否获得id的锁,没问题,于是id++,这时id=2,输出结果“线程2 id = 2”,线程1睡醒了,继续运行,输出“线程1 id = 2”

在看一段代码:
Java code

public class Demo5
{
    public static void main(String args[])
    {
        MyThread5 mt = new MyThread5();
        new Thread(mt, "线程1").start();
        new Thread(mt, "线程2").start();
    }
}

class MyThread5 implements Runnable
{
    private static StringBuilder id = new StringBuilder("0");
    
    public void run()
    {
        synchronized(id)
        {
            int t = Integer.parseInt(id.toString());
            t++;
            id.delete(0, id.length());
            id.append(t);
            String name = Thread.currentThread().getName();
            if(name.equals("线程1"))
            {
                try
                {
                    Thread.sleep(100);
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            System.out.println(name + " id = " + id);
        }
    }
}