Processes and Threads —— 同步方法 Synchronized Methods And Intrinsic Locks and Synchronization

Java同步方法与锁的深入解析

Processes and Threads 进程和线程,Oracle-CSDN博客

Processes and Threads —— 线程对象 Thread Objects-CSDN博客

Processes and Threads —— Synchronization And Thread Interference 同步-CSDN博客

https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

Synchronized Methods
The Java programming language provides two basic synchronization idioms: synchronized methods and synchronized statements. The more complex of the two, synchronized statements, are described in the next section. This section is about synchronized methods.

Java编程语言提供了两种基本的同步机制:同步方法和同步语句。其中较为复杂的同步语句将在下一节中介绍。本节主要讨论同步方法。

To make a method synchronized, simply add the synchronized keyword to its declaration:

要使一个方法同步,只需在其声明中添加 synchronized 关键字:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}


If count is an instance of SynchronizedCounter, then making these methods synchronized has two effects:

如果count是SynchronizedCounter的一个实例,那么将这些方法同步会产生两个效果:

First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

首先,对于同一个对象的两次同步方法调用不可能交错执行。当一个线程正在执行某个对象的同步方法时,所有其他对该对象调用同步方法的线程都会被阻塞(暂停执行),直到第一个线程完成对该对象的操作。


Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.
Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn't make sense, because only the thread that creates an object should have access to it while it is being constructed.

其次,当一个同步方法退出时,它会自动与同一对象上任何后续调用的同步方法建立happens-before关系。这保证了对象状态的变更对所有线程都是可见的。
需要注意的是,构造函数不能被同步——在构造函数上使用synchronized关键字会导致语法错误。同步构造函数没有意义,因为在对象构造期间,只有创建该对象的线程才应该访问它。

Warning: When constructing an object that will be shared between threads, be very careful that a reference to the object does not "leak" prematurely. For example, suppose you want to maintain a List called instances containing every instance of class. You might be tempted to add the following line to your constructor:

警告:当构建一个将在多线程之间共享的对象时,必须非常小心,确保对象的引用不会过早"泄露"。例如,假设你想要维护一个名为instances的List,其中包含该类的每个实例。你可能会忍不住在构造函数中添加以下代码行:

instances.add(this);


But then other threads can use instances to access the object before construction of the object is complete.

但其他线程可能在对象构造完成前就通过实例访问该对象。


Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object's variables are done through synchronized methods. (An important exception: final fields, which cannot be modified after the object is constructed, can be safely read through non-synchronized methods, once the object is constructed) This strategy is effective, but can present problems with liveness, as we'll see later in this lesson.

同步方法提供了一种防止线程干扰和内存一致性错误的简单策略:如果一个对象对多个线程可见,那么对该对象变量的所有读写操作都通过同步方法完成。(一个重要例外:final字段在对象构造后无法修改,一旦对象构造完成,可以通过非同步方法安全读取。)这种策略虽然有效,但可能会导致活跃性问题,我们将在本课后续部分看到这种情况。

Liveness

https://docs.oracle.com/javase/tutorial/essential/concurrency/liveness.html

A concurrent application's ability to execute in a timely manner is known as its liveness. This section describes the most common kind of liveness problem, deadlock, and goes on to briefly describe two other liveness problems, starvation and livelock.

并发应用程序及时执行的能力被称为其活性。本节将描述最常见的活性问题——死锁,并简要介绍另外两种活性问题:饥饿和活锁。

Deadlock
Deadlock describes a situation where two or more threads are blocked forever, waiting for each other. Here's an example.

死锁描述的是两个或多个线程永远被阻塞,互相等待对方的情况。下面是一个例子。

Alphonse and Gaston are friends, and great believers in courtesy. A strict rule of courtesy is that when you bow to a friend, you must remain bowed until your friend has a chance to return the bow. Unfortunately, this rule does not account for the possibility that two friends might bow to each other at the same time. This example application, Deadlock, models this possibility:

阿尔方斯和加斯顿是朋友,也是礼仪的坚定信徒。礼仪的一条严格规定是:当你向朋友鞠躬时,必须保持鞠躬姿势直到对方有机会回礼。遗憾的是,这条规则没有考虑到两个朋友可能同时向对方鞠躬的情况。这个名为"死锁"的示例应用程序正是模拟了这种可能性。


public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}


When Deadlock runs, it's extremely likely that both threads will block when they attempt to invoke bowBack. Neither block will ever end, because each thread is waiting for the other to exit bow.

当Deadlock运行时,两个线程在尝试调用bowBack时极有可能都被阻塞。这种阻塞永远不会结束,因为每个线程都在等待另一个线程退出bow方法。

Starvation and Livelock
Starvation and livelock are much less common a problem than deadlock, but are still problems that every designer of concurrent software is likely to encounter.

饥饿和活锁远比死锁问题少见,但它们仍然是每个并发软件设计者可能遇到的问题。

Starvation
Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by "greedy" threads. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.

饥饿描述了一种情况,即线程无法正常访问共享资源且无法取得进展。当"贪婪"线程长时间占用共享资源时就会发生这种情况。例如,假设某个对象提供的同步方法通常需要很长时间才能返回。如果一个线程频繁调用该方法,其他同样需要频繁同步访问该对象的线程就会经常被阻塞。

Livelock
A thread often acts in response to the action of another thread. If the other thread's action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. This is comparable to two people attempting to pass each other in a corridor: Alphonse moves to his left to let Gaston pass, while Gaston moves to his right to let Alphonse pass. Seeing that they are still blocking each other, Alphone moves to his right, while Gaston moves to his left. They're still blocking each other, so...

一个线程通常会对另一个线程的动作做出响应。如果另一个线程的动作也是对另一个线程动作的响应,那么就可能产生活锁。与死锁类似,处于活锁状态的线程无法继续执行。然而,这些线程并没有被阻塞——它们只是太忙于互相响应而无法继续工作。这就像两个人在走廊里试图互相让路:阿尔方斯向左移动想让加斯顿通过,而加斯顿向右移动想让阿尔方斯通过。看到他们仍然互相阻挡,阿尔方斯又向右移动,而加斯顿向左移动。他们仍然互相阻挡,所以......

 Intrinsic Locks and Synchronization
Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. (The API specification often refers to this entity simply as a "monitor.") Intrinsic locks play a role in both aspects of synchronization: enforcing exclusive access to an object's state and establishing happens-before relationships that are essential to visibility.

同步机制围绕着一个称为内置锁(intrinsic lock)或监视器锁(monitor lock)的内部实体构建。(API规范中通常将该实体简称为"monitor")。内置锁在同步的两个方面发挥作用:确保对对象状态的独占访问,以及建立对可见性至关重要的先行发生关系。

Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them. A thread is said to own the intrinsic lock between the time it has acquired the lock and released the lock. As long as a thread owns an intrinsic lock, no other thread can acquire the same lock. The other thread will block when it attempts to acquire the lock.

每个对象都有一个与之关联的内置锁。按照惯例,需要独占且一致地访问对象字段的线程,必须在访问这些字段之前先获取该对象的内置锁,并在访问完成后释放该内置锁。线程从获取锁到释放锁的这段时间内被称为拥有该内置锁。只要一个线程拥有内置锁,其他线程就无法获取相同的锁。当其他线程尝试获取这个锁时,将会被阻塞。

When a thread releases an intrinsic lock, a happens-before relationship is established between that action and any subsequent acquisition of the same lock.

当一个线程释放内部锁时,该操作与后续对同一锁的任何获取之间建立了 happens-before 关系。

Locks In Synchronized Methods
When a thread invokes(调用) a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.

当线程调用同步方法时,它会自动获取该方法对象的内在锁,并在方法返回时释放该锁。即使返回是由未捕获的异常引起的,锁也会被释放。

You might wonder what happens when a static synchronized method is invoked, since a static method is associated with a class, not an object. In this case, the thread acquires the intrinsic lock for the Class object associated with the class. Thus access to class's static fields is controlled by a lock that's distinct from the lock for any instance of the class.

你可能会疑惑当调用静态同步方法时会发生什么,因为静态方法与类相关联,而非对象。在这种情况下,线程会获取与该类关联的Class对象的内在锁。因此,对类静态字段的访问是由一个独立于该类任何实例锁的锁来控制的。

Synchronized Statements
Another way to create synchronized code is with synchronized statements. Unlike synchronized methods, synchronized statements must specify the object that provides the intrinsic lock:

创建同步代码的另一种方式是使用同步语句。与同步方法不同,同步语句必须指定提供内部锁的对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}


In this example, the addName method needs to synchronize changes to lastName and nameCount, but also needs to avoid synchronizing invocations of other objects' methods. (Invoking other objects' methods from synchronized code can create problems that are described in the section on Liveness.) Without synchronized statements, there would have to be a separate, unsynchronized method for the sole purpose of invoking nameList.add.

在此示例中,addName方法需要同步对lastName和nameCount的更改,但同时需要避免同步调用其他对象的方法。(从同步代码中调用其他对象的方法可能会引发“活跃性”一节中描述的问题。)若没有同步语句,就必须专门创建一个非同步方法,仅用于调用nameList.add。

Synchronized statements are also useful for improving concurrency with fine-grained synchronization. Suppose, for example, class MsLunch has two instance fields, c1 and c2, that are never used together. All updates of these fields must be synchronized, but there's no reason to prevent an update of c1 from being interleaved with an update of c2 — and doing so reduces concurrency by creating unnecessary blocking. Instead of using synchronized methods or otherwise using the lock associated with this, we create two objects solely to provide locks.

同步语句还能通过细粒度同步来提高并发性。例如,假设类MsLunch有两个从不一起使用的实例字段c1和c2。这些字段的所有更新都必须同步,但没有理由阻止c1的更新与c2的更新交错执行——这样做反而会因造成不必要的阻塞而降低并发性。我们创建了两个专门用于提供锁的对象,而不是使用同步方法或以其他方式使用与this关联的锁。

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}


Use this idiom with extreme care. You must be absolutely sure that it really is safe to interleave access of the affected fields.

使用这个成语时要格外小心。你必须绝对确定交错访问受影响的字段确实是安全的。

Reentrant Synchronization
Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a lock that it already owns. Allowing a thread to acquire the same lock more than once enables reentrant synchronization. This describes a situation where synchronized code, directly or indirectly, invokes a method that also contains synchronized code, and both sets of code use the same lock. Without reentrant synchronization, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block.

请注意,一个线程无法获取另一个线程持有的锁。但一个线程可以重复获取自己已经持有的锁。允许线程多次获取同一锁的特性称为可重入同步。这种情况描述的是:同步代码(直接或间接)调用另一个也包含同步代码的方法,且这两段代码使用同一个锁。如果没有可重入同步机制,同步代码就必须采取许多额外预防措施,以避免线程自身陷入阻塞状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值