首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > JAVA > J2SE开发 >

议论:深入理解“一个对象一把锁”

2012-07-03 
讨论:深入理解“一个对象一把锁”Java多线程中,临界资源以对象的形式存在,当多个线程访问同一个临界资源时首

讨论:深入理解“一个对象一把锁”
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);        }    }} 


这里使用了StringBuilder,因为StringBuilder可以直接改变内容而不会重新分配内存,于是同步机制发挥作用,输出结果是
线程1 id = 1
线程2 id = 2

不知道我的解释是否正确,跟大家讨论一下。

[解决办法]
在3个线程的运行过程中对象锁id的值也就是它所指向的引用对象发生了改变,所以在线程中再去取对象锁id的时候取到的不是同一把锁,所以这样三个线程取到的不是同一把锁当然不能实现同步。关于楼主同步线程的用法如果使对象锁id和用于计数的id不使用同一个变量便能解决。
Java code
public class Demo{    public static void main(String args[])    {        MyThread mt1 = new MyThread();        new Thread(mt1, "线程1").start();        new Thread(mt1, "线程2").start();        new Thread(mt1, "线程3").start();    }    }class MyThread implements Runnable{    private static Integer id = 0;    private static Integer d = 0;    public void run()    {        for(int i = 0; i < 30; i ++)        {            synchronized(id)            {                d++;                System.out.println(Thread.currentThread().getName() + " d = " + d);            }        }    }} 

热点排行