java 并发 synchronized问题

上码:

 

package com.hxsmart.thread.synchronize;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class CleanRoom {
    private Integer student=0;
    private Integer machine=0;
    
    public String getDateStr(){
        return "  Thread"+Thread.currentThread().getName()+"  *time: "+new SimpleDateFormat("mm:ss").format(new Date());
    }
    public void stuClean(){
        System.out.println("enter stuClean");
        synchronized (student) {
            for(int i=0;i<5;i++){
                System.out.println("stu"+getDateStr());
                student++;
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public void macClean(){
        System.out.println("enter macClean");
        synchronized (machine) {
            for(int i=0;i<5;i++){
                System.out.println("mac"+getDateStr());
                machine++;
//              try {
//                  TimeUnit.SECONDS.sleep(3);
//              } catch (InterruptedException e) {
//                  e.printStackTrace();
//              }
            }
        }
    }
    public static void main(String[] args) {
        CleanRoom c=new CleanRoom();
        
        StuThread t1=new StuThread(c);
        MacThread t2=new MacThread(c);
        
        t1.start();
        t2.start();
    }
    public Integer getStudent() {
        return student;
    }
    public void setStudent(Integer student) {
        this.student = student;
    }
    public Integer getMachine() {
        return machine;
    }
    public void setMachine(Integer machine) {
        this.machine = machine;
    }
    
}

class StuThread extends Thread{
    CleanRoom clean;
    public StuThread() {
        super();
    }
    public StuThread(CleanRoom clean) {
        super();
        this.clean = clean;
    }
    @Override
    public void run() {
        //for(int i=0;i<5;i++)
        clean.stuClean();
        System.out.println("stu: "+clean.getStudent()+"**  mac:**"+clean.getMachine());
    }
    public CleanRoom getClean() {
        return clean;
    }
    public void setClean(CleanRoom clean) {
        this.clean = clean;
    }
    
}
class MacThread extends Thread{
    CleanRoom clean;
    public MacThread() {
        super();
    }
    public MacThread(CleanRoom clean) {
        super();
        this.clean = clean;
    }
    @Override
    public void run() {
        //for(int i=0;i<5;i++)
        clean.macClean();
        System.out.println("stu: "+clean.getStudent()+"**  mac:**"+clean.getMachine());
    }
    public CleanRoom getClean() {
        return clean;
    }
    public void setClean(CleanRoom clean) {
        this.clean = clean;
    }
}

 输出:

 

 

enter stuClean
enter macClean
stu  ThreadThread-0  *time: 12:38
stu  ThreadThread-0  *time: 12:41
stu  ThreadThread-0  *time: 12:44
stu  ThreadThread-0  *time: 12:47
stu  ThreadThread-0  *time: 12:50
stu: 5**  mac:**0
mac  ThreadThread-1  *time: 12:53
mac  ThreadThread-1  *time: 12:53
mac  ThreadThread-1  *time: 12:53
mac  ThreadThread-1  *time: 12:53
mac  ThreadThread-1  *time: 12:53
stu: 5**  mac:**5

 问题:

 

t1和t2线程,分别调用的是CleanRoom类同一实例c的不同方法,并且这两个方法没有把这个实例C锁上,两个方法所锁定的资源分别是c实例的两个不同的属性,按道理来说,这两个线程应该是并发执行的。

但是事实不如此,t2线程在t1线程没有执行完成前,始终处于等待状态。

 

求解!

当stuClean方法中调用TimeUnit.SECONDS.sleep(3)后,macClean()方法应该有机会得到执行啊?

3个回答

哈哈,神秘现象不神秘。Integer类会缓存一部分对象,从-128到127的数在系统中都只有一个Integer对象(假如你用自动装箱和valueof方法得到对象,其实这两者是一回事)。因此你锁住的两个对象其实都是同一个Integer(0),当自增之后,那两个对象的引用指向了别的对象,比如Integer(1)。最终执行的结果就是student和machine都指向了Integer(5)。你把两个=0改成=new Integer(0)就OK啦。

hhsh123hhsh
阁人 理解了,其实基本类型的话,只创建一个对象,如果是包装类型或普通类,则每次new,都会新建对象
7 年多之前 回复
hhsh123hhsh
阁人 这是不是享元模式的呀
7 年多之前 回复
teasp
teasp 再解释清楚点,Integer实际上和String类似,是不可变的。底层的那个整形值是不能改变的,自增操作之后都不再是同一个对象了。你把Integer改成String也能看到类似的情况。
7 年多之前 回复
teasp
teasp 两个线程一开始就锁住了Integer(0),一直没释放,那两个变量的引用修改了也影响不到锁呀。 自增是改变引用,这个和原始类型增加值是不一样的。
7 年多之前 回复
binggouxsm
binggouxsm 如果student和machine都指向了Integer(5),这点会有矛盾,stuClean执行完了后,指向了Integer(5),如果machine也同样指向Integer(5)的话,那macClean中machine++应该是从5开始一直加到10,但是执行结果还是machine还是Integer(5),所以这点说不通啊!
7 年多之前 回复
blusewang
blusewang 学习了一下java的自动装箱机制,参考[http://denverj.iteye.com/blog/745422 但是还有一个问题: 最初,这两个对象都是同一个,所以锁住的是同一个资源。所以两个线程只有一个进入。 当一个线程执行了++操作后,值发送变化,栈中的位置也发送变化了,这个时候另一个应该可以执行啊。 这个中间的过程有点模糊,不过大致明白怎么规避此类问题了。
7 年多之前 回复

你把private Integer student=0改成private Integer student=new Integer(0);同样private Integer machine=0;改成private Integer machine=new Integer(0);再试试看,应该就不会锁住了。

真的太神奇了。
难道是因为private Integer student=0和private Integer machine=0;实际上指向的是一个对象,所以锁被加在一个对象上,所以出现了等待的情况?
但是如果指向同一对象的话,machine不应该还是从0开始计数啊。

求哪位大神解释!

blusewang
blusewang 换成new Integer(0);后就解决了。 不知这样理解是否恰当:synchronized锁的是对象,我们通过new构造对象时,对象存储在堆中。 而基本类型直接赋值时,存储在栈中。而在栈中,这两个对象是相同的。 int a=1; int b=1; 这个时候a==b; a++; 这个时候a指向2,b还是1 所以a!=b
7 年多之前 回复
pbzhang9527
pbzhang9527 sleep不释放锁
7 年多之前 回复

sleep执行的时候是没有释放锁的,他还手里面握着锁头的,其他线程竞争不走CPU资源的,他还占据着呢,
比如wait的话,在wait(3000),的时候这个时候系统的其他线是可以竞争CPU资源的。

本人的一点点见解

blusewang
blusewang sleep是没有释放锁,但是问题的根源是分别锁住了两个对象,结果依然无法并发。
7 年多之前 回复
Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐