Синхронизированный метод Java на объекте или методе?
Если у меня есть 2 синхронизированных метода в одном классе, но каждый из них обращается к различным переменным, может ли 2 потока одновременно обращаться к этим двум методам? Находит ли замок объект, или он становится таким же конкретным, как переменные внутри синхронизированного метода?
Пример:
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
}
Могут ли 2 потока одновременно обращаться к одному экземпляру класса X, выполняющему x.addA(
) и x.addB()
?
Ответы
Ответ 1
Если вы объявляете метод как синхронизированный (как вы делаете, вводя public synchronized void addA()
), вы синхронизируете весь объект, поэтому два потока, обращающиеся к другой переменной из этого же объекта, в любом случае будут блокировать друг друга.
Если вы хотите синхронизировать только одну переменную за раз, чтобы два потока не блокировали друг друга при обращении к различным переменным, вы должны синхронизировать их по отдельности в блоках synchronized()
. Если бы a
и b
были ссылками на объекты, вы бы использовали:
public void addA() {
synchronized( a ) {
a++;
}
}
public void addB() {
synchronized( b ) {
b++;
}
}
Но так как они примитивы, вы не можете этого сделать.
Я бы предложил вам вместо этого использовать AtomicInteger:
import java.util.concurrent.atomic.AtomicInteger;
class X {
AtomicInteger a;
AtomicInteger b;
public void addA(){
a.incrementAndGet();
}
public void addB(){
b.incrementAndGet();
}
}
Ответ 2
Синхронизированный по объявлению метода синтаксический сахар для этого:
public void addA() {
synchronized (this) {
a++;
}
}
Для статического метода для этого используется синтаксический сахар:
ClassA {
public static void addA() {
synchronized(ClassA.class) {
a++;
}
}
Я думаю, что если разработчики Java знали тогда, что теперь понимается в синхронизации, они не добавили бы синтаксический сахар, поскольку это чаще всего приводит к плохим реализациям concurrency.
Ответ 3
Из "Учебников Java ™" по синхронизированным методам:
Во-первых, невозможно выполнить два вызова синхронизированных методов для одного и того же объекта. Когда один поток выполняет синхронизированный метод для объекта, все другие потоки, которые вызывают синхронизированные методы для того же блока объекта (приостанавливают выполнение), пока первый поток не завершится с объектом.
Из "Учебников Java ™" по синхронизированным блокам:
Синхронизированные операторы также полезны для улучшения параллелизма с детальной синхронизацией. Предположим, например, что класс MsLunch имеет два поля экземпляра, c1 и c2, которые никогда не используются вместе. Все обновления этих полей должны быть синхронизированы, но нет причин препятствовать чередованию обновления c1 с обновлением c2 - и это уменьшает параллелизм, создавая ненужную блокировку. Вместо использования синхронизированных методов или иного использования блокировки, связанной с этим, мы создаем два объекта исключительно для обеспечения блокировки.
(Акцент мой)
Предположим, у вас есть 2 не перемежающиеся переменные. Таким образом, вы хотите получить доступ к каждому из разных потоков одновременно. Вы должны определить блокировку не для самого класса объекта, а для класса Object, как показано ниже (пример из второй ссылки Oracle):
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++;
}
}
}
Ответ 4
Доступ к блокировке осуществляется на объекте, а не на методе. Какие переменные доступны в рамках метода, не имеет значения.
Добавление "синхронизированного" к методу означает, что поток, выполняющий код, должен получить блокировку объекта перед продолжением. Добавление "статической синхронизации" означает, что поток, выполняющий код, должен получить блокировку объекта класса перед продолжением. В качестве альтернативы вы можете обернуть код в блоке следующим образом:
public void addA() {
synchronized(this) {
a++;
}
}
чтобы вы могли указать объект, чья блокировка должна быть получена.
Если вы хотите избежать блокировки на содержащем объекте, вы можете выбрать между:
Ответ 5
Из документации oracle ссылка
Синхронизация синхронных методов имеет два эффекта:
Во-первых, невозможно, чтобы две вызовы синхронизированных методов на одном объекте чередовали. Когда один поток выполняет синхронизированный метод для объекта, все другие потоки, которые вызывают синхронизированные методы для одного и того же объекта (приостанавливать выполнение) до тех пор, пока первый поток не будет выполнен с объектом.
Во-вторых, когда синхронизированный метод завершается, он автоматически устанавливает связь между событиями и последующим вызовом синхронизированного метода для одного и того же объекта. Это гарантирует, что изменения состояния объекта будут видны для всех потоков
Взгляните на эту документацию страница, чтобы понять внутренние блокировки и поведение блокировки.
Это ответит на ваш вопрос: на том же объекте x вы не можете вызывать x.addA() и x.addB() в то же время, когда выполняется одно из выполнения синхронизированных методов.
Ответ 6
Вы можете сделать что-то вроде следующего. В этом случае вы используете блокировку a и b для синхронизации вместо блокировки на "this". Мы не можем использовать int, потому что примитивные значения не имеют блокировок, поэтому мы используем Integer.
class x{
private Integer a;
private Integer b;
public void addA(){
synchronized(a) {
a++;
}
}
public synchronized void addB(){
synchronized(b) {
b++;
}
}
}
Ответ 7
Если у вас есть какие-то методы, которые не синхронизированы и к которым относятся и изменяют переменные экземпляра. В вашем примере:
private int a;
private int b;
любое число потоков может обращаться к этим несинхронизированным методам одновременно, когда другой поток находится в синхронизированном методе одного и того же объекта и может вносить изменения в переменные экземпляра.
Например, например: -
public void changeState() {
a++;
b++;
}
Вам нужно избегать сценария, при котором несинхронизированные методы обращаются к переменным экземпляра и изменяют его, иначе нет смысла использовать синхронизированные методы.
В приведенном ниже сценарии: -
class X {
private int a;
private int b;
public synchronized void addA(){
a++;
}
public synchronized void addB(){
b++;
}
public void changeState() {
a++;
b++;
}
}
Только один из потоков может быть либо в addA, либо в методе addB, но в то же время любое число потоков может вводить метод changeState. Ни один из двух потоков не может вводить addA и addB в одно и то же время (из-за блокировки уровня объекта), но в то же время любое количество потоков может входить в changeState.
Ответ 8
Этот пример (хотя и не очень красивый) может обеспечить более глубокое понимание механизма блокировки. Если incrementA синхронизирован, а incrementB не синхронизирован, то incrementB будет выполняться ASAP, но если incrementB также синхронизирован, тогда он должен " подождите ', чтобы incrementA закончил, прежде чем incrementB может выполнить свою работу.
Оба метода вызываются в один экземпляр - объект, в этом примере это: задание и "конкурирующие" потоки - это aThread и main.
Попробуйте " синхронизированный" в incrementB и без него, и вы увидите разные результаты. Если incrementB является " синхронизированным", то он должен ждать incrementA ( ) заканчивать. Выполняйте несколько раз каждый вариант.
class LockTest implements Runnable {
int a = 0;
int b = 0;
public synchronized void incrementA() {
for (int i = 0; i < 100; i++) {
this.a++;
System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
}
}
// Try with 'synchronized' and without it and you will see different results
// if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish
// public void incrementB() {
public synchronized void incrementB() {
this.b++;
System.out.println("*************** incrementB ********************");
System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
System.out.println("*************** incrementB ********************");
}
@Override
public void run() {
incrementA();
System.out.println("************ incrementA completed *************");
}
}
class LockTestMain {
public static void main(String[] args) throws InterruptedException {
LockTest job = new LockTest();
Thread aThread = new Thread(job);
aThread.setName("aThread");
aThread.start();
Thread.sleep(1);
System.out.println("*************** 'main' calling metod: incrementB **********************");
job.incrementB();
}
}
Ответ 9
Да, он заблокирует другой метод, потому что синхронизированный метод применяется к объекту класса WHOLE, как указано... но в любом случае он заблокирует выполнение другого потока ТОЛЬКО при выполнении суммы в любом методе addA или addB, который входит, потому что когда он заканчивается... один поток будет БЕСПЛАТНО, а другой поток получит доступ к другому методу и т.д. отлично работает.
Я имею в виду, что "синхронизированный" выполняется именно для того, чтобы блокировать другой поток от доступа к другому при выполнении определенного кода. ТАК НАКОНЕЦ ДЕЙСТВИТЕЛЬНО ЭТОТ КОДИРОВАНО РАБОТАЕТ. [/P >
Как последнее замечание, если есть переменные 'a' и 'b', а не только уникальная переменная 'a' или какое-либо другое имя, нет необходимости синхронизировать эти методы, потому что это совершенно безопасно, (Другая ячейка памяти).
class X {
private int a;
private int b;
public void addA(){
a++;
}
public void addB(){
b++;
}}
Будет работать также
Ответ 10
Это может не сработать, так как бокс и автобоксинг от Integer к int и наоборот зависят от JVM, и существует высокая вероятность того, что два разных номера могут быть хэшированы по одному и тому же адресу, если они находятся между -128 и 127.
Ответ 11
В синхронизации Java, если поток хочет войти в метод синхронизации, он получит блокировку для всех синхронизированных методов этого объекта, а не только для одного синхронизированного метода, который использует поток. Таким образом, поток, выполняющий addA(), получит блокировку для addA() и addB(), поскольку оба синхронизированы. Так что другие потоки с тем же объектом не могут выполнять addB().