Java线程同步 - Thread.sleep() 方法无法按预期工作

4

我听说,sleep()会锁定当前的同步方法/块。但是在这里,当我在线程1上调用sleep()时,线程2可以访问相同的块?有人能解释一下吗?

Main.java

public class Main {     
    public static void main(String args[])
    {
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();
        System.out.println("going to start t1");
        t1.start();
        System.out.println("going to start t2");
        t2.start();

    }

}

=====================================================================

Thread1.java

public class Thread1 extends Thread{

    public void run() { 
        Syncc s1 = new Syncc();
        s1.me("T1:");
    }   

}

=====================================================================

Thread2.java

public class Thread2 extends Thread{

    public void run() { 
        Syncc s2 = new Syncc();
        s2.me("T2:");
    }   
}

=====================================================================

Syncc.java

public class Syncc{

    public void me(String s){
        synchronized(this){
        for(int i=0; i<=5; i++)
        {
            System.out.println(s+" "+" "+i);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {              
                e.printStackTrace();
            }
        }
    }
}
}

输出:

going to start t1
going to start t2
T2:  0
T1:  0
T2:  1
T1:  1
T1:  2
T2:  2
T1:  3
T2:  3
T1:  4
T2:  4
T2:  5
T1:  5

根据sleep()方法,它不应该解锁当前同步块,对吗?如果是这样的话,输出应该是:
开始运行 t1 开始运行 t2
T1:  0
T1:  1
T1:  2
T1:  3
T1:  4
T1:  5
T2:  0
T2:  1
T2:  2
T2:  3
T2:  4
T2:  5

我理解您的意思是:在线程1执行完成后才应该启动线程2,是吗?有什么问题吗?

Thread1和Thread2正在使用Syncc的不同实例,因此它们可以访问2个不同的Syncc实例的me()方法,所以同步块无法起作用。 - Alexey A.
3个回答

9
这是因为你在这里使用了两个不同的 Syncc 实例。每个线程都有自己的 Syncc 副本。
尝试只使用一个实例。你也可以在静态上下文中进行同步并尝试。
为了模拟,请修改 Thread1Thread2 以接受一个 Syncc 实例。
public class Thread1 extends Thread {
    private Syncc syncc;

    public Thread1(Syncc syncc) {
        this.syncc = syncc;
    }

    public void run() { 
        this.syncc.me("T1:");
    }   
}

您可以按如下步骤启动它们:
public static void main(String args[]) {
    Syncc syncc = new Syncc();

    Thread1 t1 = new Thread1(syncc);
    Thread2 t2 = new Thread2(syncc);

    System.out.println("going to start t1");
    t1.start();
    System.out.println("going to start t2");
    t2.start();
}

1
明白了,非常感谢你 Adarshr。 现在我可以开始了,再次感谢。 - Siva
Adarshr,你能告诉我怎么调用notify()吗?我的意思是我需要用thread1打印1-3,然后thread1必须执行wait()。现在thread2应该打印所有5个数字,然后再次需要thread1打印剩余的...实际上,我对线程很不熟悉,我在网上看到了很多例子,但是感到很困惑,但是你的解释真的很好,我理解了这个概念,请帮帮我,谢谢。 - Siva

2

睡眠、让出和加入规则

  • 睡眠用于延迟执行一段时间,当线程进入睡眠状态时不会释放锁。

  • 一个正在睡眠的线程保证至少会睡眠指定sleep()方法参数中的时间(除非被中断),但是没有保证新唤醒的线程何时实际返回运行状态。

  • sleep()方法是一个静态方法,它使当前执行线程进入睡眠状态。一个线程不能让另一个线程进入睡眠状态。

  • setPriority()方法用于Thread对象,将线程优先级设置为1(低)到10(高),虽然优先级不是保证的,并且并非所有JVM都识别10个不同的优先级水平,有些水平可能被视为等效的。

  • 如果没有显式设置,线程的优先级将与创建它的线程的优先级相同。

  • yield()方法可能会导致正在运行的线程退出,如果存在具有相同优先级的可运行线程,则此时会选择其他线程运行。但是无法保证这种情况发生,并且当线程退出后,也无法保证会选择其他线程运行。线程可能会让出后立即重新进入运行状态。

  • 最接近保证的情况是,在任何时候,当一个线程正在运行时,它通常不会比处于可运行状态的任何线程优先级低。如果低优先级线程正在运行时,高优先级线程进入可运行状态,则JVM通常会抢占正在运行的低优先级线程,并将高优先级线程放入其中。

  • 当一个线程调用另一个线程的join()方法时,当前运行的线程将等待它加入的线程完成。可以将join()方法视为“嘿,线程,我想加入你的末尾,请在完成时通知我,以便我可以进入可运行状态。”

http://www.amazon.com/SCJP-Certified-Programmer-Java-310-065/dp/0071591060


0
你创建了两个同步对象,每个对象对应一个线程。每个对象都有自己的me函数副本。因此,当你启动每个线程时,在run方法中,线程调用自己的me函数副本。由于这两个线程仅作用于它们各自的副本,所以这就像单线程场景一样工作。如果你想测试多线程场景,那么将me方法设置为静态(类级别方法),并应用类级别锁。

Thread1.java

public class Thread1 extends Thread{

public void run() { 
    Syncc.me("T1:");
}   

}

Thread2.java

public class Thread2 extends Thread{

public void run() { 
    Syncc.me("T2:");
}   

}

Syncc.java

public class Syncc{

public static void me(String s){
    synchronized(Syncc.class){
    for(int i=0; i<=5; i++)
    {
        System.out.println(s+" "+" "+i);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {              
            e.printStackTrace();
        }
    }
}

} }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接