简体   繁体   中英

Multithreading: synchronize by locking

Concurrent collection:

ConcurrentMap<LocalDate, A> ex = new ConcurrentHashMap<>();

class A {
   AtomicLong C;
   AtomicLong D
}

How can I synchronize by locking "C" and "D"? That is, I need to change "C" and "B" at the same time with a guarantee that while I change the other one, the first one does not change from external actions. Thank you.

What you're solving for

You are describing that you want to:

  • allow a caller to modify something on an object
  • prevent any other callers (other threads) from modifying things at the same time

Solution description

This solution uses synchronized , though there are number of other mechanisms available in Java that would support this (several of which are covered in the Lock Objects section of the Java Tutorials).

The way "synchronized" works is that you designate some code using the "synchronized" keyword, along with an object to synchronize on. When your code runs, the JVM will guarantee that, for all code which is synchronized – on the same object – only one thread can proceed at a time.


You can make a synchronized code block, like below. Note: this defines an Object named "lock", but it's just a name chosen for clarity when reading the code – you could name it anything you like.

Object lock;
synchronized (lock) {
    ... // all things here run only when "lock" is available
}

You can also designate an entire method as being synchronized, like this:

public void synchronized print() {
    System.out.println("hello");
}

This second example behaves like the first - it also locks on an object – but it's not clear at a glance what the object is; that is, how does the JVM know which object to sychronize on? This approach works if the method itself is called on an object instance, and in that case the lock becomes this . I'll show an example below.

There's good info in the Java Tutorials about synchronized methods .


Solution #1: synchronized block using Object lock

Here are a few notes about a class AllowOneEditAtATime :

  • it has two private members: one and two
  • because they're private, they cannot be changed directly – so it would not be allowed to do something like this:
     AllowOneEditAtATime a = new AllowOneEditAtATime(); a.one = new AtomicLong(1); // cannot change "one" directly because it is private
  • defines private Object lock – this is meant to act as the thing that two different synchronized code blocks will lock on. It's totally fine to have different blocks of code each synchronize on the same object. This is the main technique you're after.
  • uses a synchronized block inside setOne() , synchronizing on "lock"
  • uses another synchronized block inside the other method – setTwo() – also synchronizing on "lock"
  • because both setOne() and setTwo() are synchronized on the same object, one of them will be allowed to run at a time
class AllowOneEditAtATime1 {

    private Object lock;
    private AtomicLong one;
    private AtomicLong two;

    public void setOne(AtomicLong newOne) {
        synchronized (lock) {
            one = newOne;
        }
    }

    public void setTwo(AtomicLong newTwo) {
        synchronized (lock) {
            two = newTwo;
        }
    }
}

Solution #2: synchronized block, using this

Solution #1 works fine, but it isn't necessary (in this case) to create an entire object just for locking. Instead, you can rely on the fact that this code runs only after someone called new AllowOneEditAtATime() , which means there's always an object instance, which means inside the code you can use this . The this keyword refers to the object instance itself, the actual instance of AllowOneEditAtATime .

So here's a variation using this (no more Object lock ):

class AllowOneEditAtATime2 {

    private AtomicLong one;
    private AtomicLong two;

    public void setOne(AtomicLong newOne) {
        synchronized (this) {
            one = newOne;
        }
    }

    public void setTwo(AtomicLong newTwo) {
        synchronized (this) {
            two = newTwo;
        }
    }
}

Solution #3: synchronized methods, implicit lock

Solution #2 works fine, but since we're using this as the lock, and since the code paths fit with doing this, we can use synchronized methods instead of synchronized code blocks.

That means we can replace this:

public void setTwo(AtomicLong newTwo) {
    synchronized (this) {
        two = newTwo;
    }
}

with this:

public synchronized void setOne(AtomicLong newOne) {
    one = newOne;
}

Under the covers, the entire setOne() method is synchronized on this automatically, so it isn't necessary to include synchronized (this) {.. } at all. In Solution #2, both methods were doing that, so both can be replaced. By synchronizing both methods, they will both be synchronized on the object instance ( this ), which is similar to Solution #2, but with less code.

class AllowOneEditAtATime3 {

    private AtomicLong one;
    private AtomicLong two;

    public synchronized void setOne(AtomicLong newOne) {
        one = newOne;
    }

    public synchronized void setTwo(AtomicLong newTwo) {
        two = newTwo;
    }
}

Any of the above would work, as would other synchronization mechanisms. As with all things, there are multiple ways you could solve the problem.

For additional reading, the Concurrency lesson (in Java Tutorials) has good info and might be worth your time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM