Как синхронизированные статические методы работают в Java и можно ли использовать их для загрузки объектов Hibernate?

180

Если у меня есть класс util со статическими методами, которые будут вызывать функции Hibernate для выполнения базового доступа к данным. Мне интересно, если создание метода 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
}
помидор
источник

Ответы:

137

Используя синхронизированную блокировку статического метода, вы синхронизируете методы и атрибуты класса (в отличие от методов и атрибутов экземпляра).

Так что ваше предположение верно.

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

На самом деле, нет. Вы должны позволить вашей СУБД делать это вместо этого. Они хороши в таких вещах.

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

Представьте себе следующий сценарий:

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

При вашем подходе единственное, что вы получаете - это убедиться, что один вызывается после другого, когда это все равно произойдет в БД, потому что СУБД не позволит им одновременно вставлять половину информации из A и половину из B , Результат будет таким же, но только в 5 (или более) раз медленнее.

Возможно, было бы лучше взглянуть на главу «Транзакции и параллелизм» в документации Hibernate. В большинстве случаев проблемы, которые вы пытаетесь решить, уже решены и гораздо лучшим способом.

OscarRyz
источник
1
Очень полезный ответ! СПАСИБО! Таким образом, Hibernate заботится о мошенничестве путем «оптимистической блокировки». Тогда нет необходимости использовать "синхронизированные" методы вообще для разрешения любого параллелизма доступа к данным ?? Использовать «синхронизированные» методы только в том случае, если данные не хранятся в базе данных. .. когда ты их используешь ??
помидор
1
1) Я думаю, что есть некоторые способы использовать пессимистическую блокировку. 2) Нет, СУБД может делать эту работу. 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
@ Самуэль - Почти ... Это больше о потоках, чем экземплярах объектов. Вы правы в том, что отдельные экземпляры SomeClass будут использовать одну и ту же блокировку / монитор: тот, который связан с объектом Someclass.class. Таким образом, если два разных потока обрабатывали два разных экземпляра SomeClass, они оба не могли работать одновременно. Однако если один поток вызвал метод в одном экземпляре SomeClass, а этот метод вызвал метод в другом экземпляре, блокировка не произойдет.
Скотт Стэнчфилд
@ScottStanchfield Вы перечислили способы синхронизации методов, все ли они эквивалентны?
Bionix1441,
1
@ Bionix1441 - Все дело в области видимости. Каждый механизм выше дает вам более точное управление блокировкой. Сначала с помощью самого экземпляра блокируется весь метод, затем сам экземпляр для блокировки разделов внутри метода, затем любой экземпляр объекта для блокировки разделов.
Скотт Стэнчфилд
17

Статические методы используют класс в качестве объекта для блокировки, который для вашего примера является Utils.class. Так что да, все в порядке.

starblue
источник
14

static synchronizedозначает удержание блокировки на Classобъекте класса, где as synchronizedозначает удержание блокировки на самом объекте этого класса. Это означает, что если вы обращаетесь к нестатическому синхронизированному методу в потоке (выполнения), вы все равно можете получить доступ к статическому синхронизированному методу, используя другой поток.

Таким образом, доступ к двум одинаковым методам (двум статическим или двум нестатическим методам) в любой момент времени более чем одним потоком невозможен.

прасад
источник
10

Почему вы хотите обеспечить, чтобы только один поток мог одновременно обращаться к БД?

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

Скорее всего, ваша база данных вполне способна обрабатывать множественный параллельный доступ

oxbow_lakes
источник
Могу поспорить, что это / было обходной путь для некоторой транзакционной проблемы. То есть решение не решает истинную проблему
Мэтт Б
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 может выдать , 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 и транзакциях базы данных, проверить эту статью , а также.

Влад Михалча
источник