Java线程同步

    |     2015年4月16日   |   多线程编程   |     0 条评论   |    1715

两个或两个以上线程要共享资源时,必须保证某一刻只有一条线程占用。这个过程叫同步(synchronization)。Java 在语言层面提供了支持。

关键概念是管程(也叫信号量 semaphore):一个互斥独占锁定,或称互斥体(mutex)。同一时刻只有一条线程能获得管程;其他试图进入已锁定管程的线程必须挂起等待。拥有管程的线程可以再次进入同一个管程。

C / C++ 往往要调操作系统原语,用起来别扭。Java 用 synchronized 把大部分复杂性收掉。同步代码有两种写法:同步方法和同步语句块。

写法 何时用
synchronized 方法 类是你写的,整段方法都要互斥
synchronized (obj) { … } 第三方类没有同步方法,又拿不到源码

一、使用同步方法

每个对象都有隐式管程。进入它就是调用被 synchronized 修饰的方法。一条线程在同步方法里时,其他试图调用该实例同一(或其他)同步方法的线程必须等。从同步方法返回即退出管程。

先看一个该同步却没同步的例子。Callme.call 在方括号里打印字符串,中间 sleep 1 秒,故意把时间片让出去。三个 Caller 共用同一个 Callme 实例:

class Callme {
    void call(String msg) {
        System.out.print("[" + msg);
        try { Thread.sleep(1000); } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
        System.out.println("]");
    }
}
class Caller implements Runnable {
    String msg;
    Callme target;
    Thread t;
    public Caller(Callme targ, String s) {
        target = targ;
        msg = s;
        t = new Thread(this);
        t.start();
    }
    public void run() { target.call(msg); }
}
class Synch {
    public static void main(String args[]) {
        Callme target = new Callme();
        Caller ob1 = new Caller(target, "Hello");
        Caller ob2 = new Caller(target, "Synchronized");
        Caller ob3 = new Caller(target, "World");
        try {
            ob1.t.join();
            ob2.t.join();
            ob3.t.join();
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
    }
}

没有同步时输出会搅在一起,例如 Hello[Synchronized[World] 再跟几个孤立的 ]。这是竞争:三条线程抢着完成同一方法。sleep 只是让现象稳定复现;真实竞争更复杂、更不可预知。

在 call 前加上 synchronized 后,某一时刻只有一条线程能进 call,输出变成:

[Hello]
[Synchronized]
[World]

多线程下凡是操纵对象内部状态的方法,都要用 synchronized 防竞争。一条线程进入某实例的同步方法后,其他线程不能再进该实例的同步方法;但该实例上未同步的方法仍可被调用。

二、同步语句块

类不是你写的、方法也没加 synchronized 时,把调用放进同步块即可:

synchronized (object) {
    // statements to be synchronized
}

object 是被同步对象的引用。只有一条语句时花括号可省略。同步块保证:只有当前线程成功进入 object 的管程后,才能调它的成员方法。把 call 保持非同步,在 Caller.run 里锁 target,效果与给 call 加 synchronized 相同:

public void run() {
    synchronized (target) {
        target.call(msg);
    }
}

一句话总结:共享对象用管程互斥;能改源码就给方法加 synchronized,改不了就把调用包进 synchronized(obj);未同步方法挡不住竞争。

转载请注明来源:Java线程同步
本文链接地址:https://ai.zhousir.top/?p=235

上一篇:

下一篇:

回复 取消