Як синхронізовані статичні методи працюють на Java і чи можу я використовувати її для завантаження сплячих об'єктів?


179

Якщо у мене є клас утиліти зі статичними методами, які будуть викликати сплячі функції для отримання базового доступу до даних. Мені цікаво, чи виготовлення методу synchronized- це правильний підхід для забезпечення безпеки потоку.

Я хочу, щоб це запобігало доступу інформації до того самого екземпляра БД. Однак зараз я впевнений, що наступний код заважає getObjectByIdвикликати всі класи, коли він викликається певним класом.

public class Utils {
     public static synchronized Object getObjectById (Class objclass, Long id) {
           // call hibernate class
         Session session = new Configuration().configure().buildSessionFactory().openSession();
         Object obj = session.load(objclass, id);
         session.close();
         return obj;
     }

     // other static methods
}

Відповіді:


136

За допомогою синхронізованого блокування статичного методу ви синхронізуєте методи та атрибути класу (на відміну від методів і атрибутів екземпляра)

Тож ваше припущення правильне.

Мені цікаво, чи зробити метод синхронізованим - це правильний підхід для забезпечення безпеки потоку.

Не зовсім. Ви повинні дозволити вашим RDBMS робити це замість цього. Вони хороші в подібних речах.

Єдине, що ви отримаєте, синхронізувавши доступ до бази даних, - це зробити ваш додаток жахливо повільним. Крім того, у коді, який ви опублікували, ви щоразу створюєте заводський сеанс, тому ваша програма витратить більше часу на доступ до БД, ніж виконання фактичної роботи.

Уявіть такий сценарій:

Клієнт A і B намагаються вставити різну інформацію в запис X таблиці T.

Зі своїм підходом єдине, що ви отримуєте - це переконатися, що один викликається за іншим, коли це все одно станеться в БД, оскільки RDBMS не дозволить їм одночасно вставляти половину інформації від A і половину від B. . Результат буде тим самим, але лише у 5 разів (або більше) повільніше.

Напевно, було б краще поглянути на розділ "Трансакції та валюти" в документації зі сну. Більшість випадків проблеми, які ви намагаєтеся вирішити, вирішувались вже набагато краще.


1
Дуже корисна відповідь! ДЯКУЄМО! Тож Hibernate піклується про валюту за допомогою «оптимістичного блокування». Тоді взагалі немає необхідності використовувати "синхронізовані" методи для вирішення будь-якої одночасності доступу до даних ?? Використовуйте "синхронізовані" методи лише в тому випадку, якщо дані не зберігаються в базі даних ?? ..що ви їх використовуєте ??
помідор

1
1) Я думаю, що є певні засоби і для використання песимістичного блокування. 2) Ні, RDBMS може зробити цю роботу. 3) Якщо дані отримують доступ до декількох потоків одночасно. 4) синхронізація корисна, коли двом потокам доводиться обмінюватися даними. Якщо їм не потрібно, то набагато краще!
OscarRyz

7
Будь-який ресторан швидкого харчування використовує багатопотокові. Один потік приймає замовлення та використовує інший потік для його підготовки та продовжує з наступним замовником. Точка синхронізації працює лише тоді, коли вони обмінюються інформацією, щоб знати, що підготувати. Наступна така модель справді спрощує життя.
OscarRyz

5
"весь клас" не заблокований. Специфікація мови машини Java : For a class (static) method, the monitor associated with the Class object for the method's class is used. For an instance method, the monitor associated with this (the object for which the method was invoked) is used.Таким чином, якщо один потік вводить статичний метод, той самий об'єкт, який повертається Object # getClass , блокується. Інші потоки все ще можуть отримати доступ до примірників методів.
Мартін Андерссон

4
lol Я вважаю, що моє власне формулювання теж не в кінцевому рахунку правильне. Я сказав: "Таким чином, якщо один потік вводить статичний метод, той самий об'єкт, що повертається Object # getClass, блокується". Не є технічно правильним. Довга історія була короткою для всіх допитливих людей: Для кожного класу у вашій програмі існує Classоб'єкт, інстанційований одним із завантажувачів віртуальних машин. Як і всі об'єкти, і цей об’єкт Monitorпов'язаний з ним. І цей монітор - це те, що блокується.
Мартін Андерссон

233

Щоб вирішити питання більш загально ...

Майте на увазі, що використання синхронізованих методів насправді є лише скороченням (припустимо, клас SomeClass):

synchronized static void foo() {
    ...
}

те саме, що

static void foo() {
    synchronized(SomeClass.class) {
        ...
    }
}

і

synchronized void foo() {
    ...
}

те саме, що

void foo() {
    synchronized(this) {
        ...
    }
}

Ви можете використовувати будь-який об'єкт як замок. Якщо ви хочете заблокувати підмножини статичних методів, ви можете

class SomeClass {
    private static final Object LOCK_1 = new Object() {};
    private static final Object LOCK_2 = new Object() {};
    static void foo() {
        synchronized(LOCK_1) {...}
    }
    static void fee() {
        synchronized(LOCK_1) {...}
    }
    static void fie() {
        synchronized(LOCK_2) {...}
    }
    static void fo() {
        synchronized(LOCK_2) {...}
    }
}

(для нестатичних методів ви хочете, щоб блоки були нестатичними полями)


9
Ці топ-4 кодові блоки - це золото. Саме те, що я шукав. Дякую.
Райан Шіллінгтон,

Чи правильно, що якщо я використовую статичний блокування нестатичним методом, жоден два об’єкти класу SomeClass не зможуть одночасно запустити блок?
Самуїл

2
@Samuel - Майже ... Йдеться більше про теми, ніж про об'єкти. Ви вірні в тому, що окремі екземпляри SomeClass будуть використовувати один і той же замок / монітор: той, що асоціюється з об'єктом Someclass.class. Отже, якщо два різні потоки обробляли два різні екземпляри SomeClass, вони не могли працювати одночасно. Однак якщо один потік викликав метод в одному екземплярі SomeClass, а цей метод назвав метод в іншому екземплярі, блокування не відбудеться.
Скотт Стенчфілд

@ScottStanchfield Ви перерахували способи синхронізації методів, чи всі вони рівноцінні?
Bionix1441

1
@ Bionix1441 - Справа в масштабі. Кожен вище механізм забезпечує точніший контроль блокування. Спочатку використовуючи сам екземпляр, який блокує весь метод, потім сам екземпляр для блокування розділів всередині методу, а потім будь-який об’єкт об'єкта для блокування секцій.
Скотт Стенчфілд

17

Статичні методи використовують клас як об'єкт для блокування, який є Utils.class для вашого прикладу. Так що так, це нормально.


14

static synchronizedозначає тримання блокування на Classоб’єкт класу, де, як synchronizedозначає, тримання блокування на сам об'єкт класу. Це означає, що якщо ви отримуєте доступ до нестатичного методу синхронізації в потоці (виконання), ви все одно можете отримати доступ до статичного синхронізованого методу, використовуючи інший потік.

Отже, отримати доступ до двох однотипних методів (або двох статичних, або двох нестатичних методів) в будь-який момент часу більш ніж потоком неможливо.


10

Чому ви хочете встановити, що лише один потік може отримати доступ до БД будь-коли?

Завдання драйвера бази даних реалізувати будь-яке необхідне блокування, припускаючи, що a Connectionвикористовується лише один потік за один раз!

Швидше за все, ваша база даних цілком здатна обробляти багаторазовий паралельний доступ


Б'юсь об заклад, що це / було вирішенням проблеми з трансакцією. Тобто рішення не вирішує справжню проблему
matt b

1
Я не знав цього .... Я думав, що мені доведеться це вручну реалізувати. Дякуємо, що вказали на це! :)
помідор

2

Якщо це пов'язане з даними у вашій базі даних, чому б не застосувати блокування ізоляції баз даних для досягнення цього?


У мене немає жодного фону баз даних. Тепер я знаю!! Дякуємо, що вказали на це! :)
помідор

2

Щоб відповісти на ваше запитання, так, це так: ваш synchronizedметод не може бути виконаний більш ніж одним потоком одночасно.


2

Як synchronizedпрацює ключове слово Java

Коли ви додаєте synchronizedключове слово до статичного методу, метод може викликатись лише одним потоком за один раз.

У вашому випадку кожен виклик методу:

  • створити нове SessionFactory
  • створити нове Session
  • взяти сутність
  • повернути суть об'єкта абоненту

Однак це були ваші вимоги:

  • Я хочу, щоб це запобігало доступу до інформації до того самого екземпляра БД.
  • запобігання getObjectByIdвиклику для всіх класів, коли він викликається певним класом

Отже, навіть якщо getObjectByIdметод є безпечним для потоків, реалізація є неправильною.

SessionFactory Кращі практики

Це SessionFactoryбезпечно для потоків, і це дуже дорогий об'єкт, який потрібно створити, оскільки він повинен розібрати класи сутностей та створити внутрішнє представлення метамоделі сутності.

Отже, не слід створювати SessionFactoryкожен getObjectByIdвиклик методу.

Натомість вам слід створити для нього одиночний екземпляр.

private static final SessionFactory sessionFactory = new Configuration()
    .configure()
    .buildSessionFactory();

SessionЗавжди повинен бути закритий

Ви не закрили блок Sessionв finallyблоці, і це може витікати ресурси бази даних, якщо буде викинуто виняток при завантаженні сутності.

Відповідно до Session.loadметоду JavaDoc може викинути a, HibernateExceptionякщо об'єкт не можна знайти в базі даних.

Не слід використовувати цей метод, щоб визначити, чи існує екземпляр ( get()замість цього використовувати ). Використовуйте це лише для отримання екземпляра, який, на вашу думку, існує, де неіснування було б фактичною помилкою.

Ось чому вам потрібно використовувати finallyблок для закриття Session, як це:

public static synchronized Object getObjectById (Class objclass, Long id) {    
     Session session = null;
     try {
         session = sessionFactory.openSession();
         return session.load(objclass, id);
     } finally {
         if(session != null) {
             session.close(); 
         }
     }
 }

Запобігання доступу до кількох потоків

У вашому випадку ви хотіли переконатися, що лише одна нитка отримує доступ до цієї конкретної сутності.

Але synchronizedключове слово заважає getObjectByIdодночасно викликати два потоки . Якщо два потоки викликають цей метод один за одним, у вас все одно будуть два потоки, використовуючи цю сутність.

Отже, якщо ви хочете заблокувати даний об'єкт бази даних, щоб жоден інший потік не міг його змінити, тоді вам потрібно використовувати блоки блоків.

synchronizedКлючове слово працює тільки в одній віртуальній машині Java. Якщо у вас є кілька веб-вузлів, це не завадить багатопотоковому доступу через декілька JVM.

Що вам потрібно зробити, це використовувати LockModeType.PESSIMISTIC_READабоLockModeType.PESSIMISTIC_WRITE одночасно застосовувати зміни до БД, наприклад:

Session session = null;
EntityTransaction tx = null;

try {
    session = sessionFactory.openSession();

    tx = session.getTransaction();
    tx.begin();

    Post post = session.find(
        Post.class, 
        id, 
        LockModeType.LockModeType.PESSIMISTIC_READ
    );

    post.setTitle("High-Performance Java Perisstence");

    tx.commit();
} catch(Exception e) {
    LOGGER.error("Post entity could not be changed", e);
    if(tx != null) {
        tx.rollback(); 
    }
} finally {
    if(session != null) {
        session.close(); 
    }
}

Отже, ось що я зробив:

  • Я створив нову EntityTransactionі розпочав нову транзакцію з базою даних
  • Я завантажив Post об'єкт, тримаючи блокування на пов'язаному записі бази даних
  • Я змінив Post організацію та здійснив транзакцію
  • У разі Exceptionкинутості я повернув транзакцію назад

Для отримання більш детальної інформації про трансляції ACID та бази даних, також ознайомтесь із цією статтею .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.