通过synchronized仍然允许修改对象的属性的内部锁定。

时间:2014-06-17 18:23:22

标签: java multithreading

在下面的例子中,我们有3个线程。 StartConsole线程首先运行(因为其他2个线程暂时休眠)。 StartConsole控制myObject的intrinsick锁。但是它立即放弃了使用wait()调用的内部锁。 wait()调用允许其他线程使用该对象。这意味着通过Thread.sleep等待第二个的tcpResponder可以增加myObject的值。接下来发生的事情是udpResponder在2秒后开始执行并且它通过synchronized控制本机锁,直到用户键入输入,然后它调用notify()并将控制权移交给startConsole,然后打印出“从单元接收的ack”。问题:当udpResponder控制其内部锁时,为什么startService可以继续修改myObject?我测试了这个例子,myObject的list属性的值一直在增加。

import java.util.Random;
import java.util.Scanner;

public class WaitNotifyThreads {
    MyObject myObject = new MyObject();

    // notifier
    public void udpResponder() throws InterruptedException{
        Scanner scanner = new Scanner(System.in);
        Thread.sleep(2000);

        synchronized (myObject) {
            System.out.println("Send ACK to socket server");
            scanner.nextLine();
            System.out.println("Return key pressed.");
            myObject.notify();
        }
        scanner.close();
    }

    public void startConsole() throws InterruptedException{

        synchronized (myObject) {
            myObject.wait();
        }       
        System.out.println("Received ack from unit.");
    }

    public void tcpResponder() throws InterruptedException{
        // Test if we can modify myObject
        // while startConsole has invoked wait() on it.
        Thread.sleep(1000);

        Random random = new Random();

            while(true){
                Thread.sleep(1000);
                myObject.add(random.nextInt(1000));
                System.out.println("object size: " + myObject.list().size());
            }       
    }

    public void startService() {
        Thread udpResponder = new Thread(new Runnable(){
            public void run() {
                try {
                    udpResponder();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }           
        });

        Thread consoleResponder = new Thread(new Runnable(){
            public void run() {
                try {
                    startConsole();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }           
        });

        Thread tcpResponder = new Thread(new Runnable(){
            public void run() {
                try {
                    tcpResponder();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }           
        });

        udpResponder.start();
        consoleResponder.start();
        tcpResponder.start();
    }

    public static void main(String[] args) {
        WaitNotifyThreads program = new WaitNotifyThreads();
        program.startService();
    }

}

myObject类:

public class MyObject {
    ArrayList<Integer> list = new ArrayList<Integer>();

    public MyObject(){

    }

    public void add(int val){
        list.add(val);
    }

    public ArrayList<Integer> list(){
        return list;
    }
}

输出:

object size: 1
Send ACK to socket server
object size: 2
object size: 3
object size: 4
object size: 5
object size: 6
object size: 7
object size: 8

object size: 9
Return key pressed.
Received ack from unit.
object size: 10
object size: 11
object size: 12

打印发送ACK到套接字服务器之后,它应该不再允许它修改myObject,直到输入密钥,因为udpResponder控制了myObject内部锁。

1 个答案:

答案 0 :(得分:1)

一些评论:

(1)synchronized(foo) {...} 阻止其他线程修改foo。它只能阻止其他线程在foo上进行同步。如果要确保线程B,C和D在线程A进行更改时无法访问某些(集合)状态变量,那么您编码更新或使用该变量的每个位置都必须是在同一个对象上同步。它们所有同步的对象并不重要。它甚至可能是一个不属于共享状态的对象,但它必须是每个地方的相同的对象。

这几乎总是意味着锁对象的范围与它保护的数据相同(例如,如果数据是静态的,那么锁将是静态的。如果数据是某个类的实例变量,那么lock将是同一个类的实例变量,...)

(2)即使foo.wait()未被调用,也允许foo.notify()返回。始终在循环中调用wait():

// Thread A
synchronized (foo) {
    while (! conditionImWaitingFor()) {
        foo.wait();
    }
    doSomethingAboutIt();
}

(3)如果某个其他线程尚未在foo.notify()中等待,则foo.wait() 。因此,如果线程A只调用一次foo.notify(),然后然后线程B调用foo.wait(),则线程B将永远等待。总是像这样调用notify():

// Thread B
synchronized (foo) {
    createTheConditionHesWaitingFor();
    foo.notify();
}

注意(2)和(3)如何一起工作。如果线程B先运行,那么它将创建线程A等待的条件。它将调用foo.notify(),它将无效,但是当线程A运行时,它将发现条件为真,并且它不会等待()。如果线程A先运行,它将等待,但线程B保证通知它并将其唤醒。