У мене є деякі питання щодо використання та значення synchronized
ключового слова.
- Яке значення
synchronized
ключового слова? - Коли повинні бути методи
synchronized
? - Що це означає програмно та логічно?
У мене є деякі питання щодо використання та значення synchronized
ключового слова.
synchronized
ключового слова?synchronized
?Відповіді:
synchronized
Ключове слово все про різних потоків читання та запису одних і тих же змінних, об'єктів і ресурсів. Це не тривіальна тема на Java, але ось цитата від Sun:
synchronized
методи дозволяють просту стратегію запобігання помилкам потокової інтерференції та помилок узгодженості пам’яті: якщо об’єкт видимий для більш ніж одного потоку, всі зчитування чи записи змінних цього об’єкта здійснюються за допомогою синхронізованих методів.
Дуже дуже маленькою оболонкою: Коли у вас є два потоки, які читають і записують на один і той же "ресурс", скажімо, змінна назва foo
, вам потрібно забезпечити доступ цих змінних атомним способом. Без synchronized
ключового слова, ваш потік 1 може не бачити змінну нитку 2, зроблену foo
, або ще гірше, вона може бути лише наполовину змінена. Це не буде те, що ви логічно очікуєте.
Знову ж таки, це нетривіальна тема на Java. Щоб дізнатися більше, вивчіть тут теми про SO та Interwebs про:
Продовжуйте досліджувати ці теми, поки ім'я "Брайан Гец" не стане назавжди пов'язаним з терміном "паралельність" у вашому мозку.
Ну, я думаю, нам було достатньо теоретичних пояснень, тому врахуйте цей код
public class SOP {
public static void print(String s) {
System.out.println(s+"\n");
}
}
public class TestThread extends Thread {
String name;
TheDemo theDemo;
public TestThread(String name,TheDemo theDemo) {
this.theDemo = theDemo;
this.name = name;
start();
}
@Override
public void run() {
theDemo.test(name);
}
}
public class TheDemo {
public synchronized void test(String name) {
for(int i=0;i<10;i++) {
SOP.print(name + " :: "+i);
try{
Thread.sleep(500);
} catch (Exception e) {
SOP.print(e.getMessage());
}
}
}
public static void main(String[] args) {
TheDemo theDemo = new TheDemo();
new TestThread("THREAD 1",theDemo);
new TestThread("THREAD 2",theDemo);
new TestThread("THREAD 3",theDemo);
}
}
Примітка: synchronized
блокує виклик наступного потоку до методу test (), поки виконання попереднього потоку не закінчено. Нитки можуть отримати доступ до цього методу по одному. Без synchronized
усіх потоків можна отримати доступ до цього методу одночасно.
Коли потік викликає синхронізований метод "тестування" об'єкта (тут об'єкт є екземпляром класу "TheDemo"), він отримує блокування цього об'єкта, будь-який новий потік не може викликати БУДЬ-який синхронізований метод того ж об'єкта, як і попередній потік який придбав замок, він не відпускає замок.
Подібне відбувається і тоді, коли викликається будь-який статичний синхронізований метод класу. Потік набуває блокування, пов'язане з класом (у цьому випадку будь-який нестатичний синхронізований метод екземпляра цього класу може бути викликаний будь-яким потоком, тому що блокування рівня об'єкта все ще доступне). Будь-який інший потік не зможе викликати будь-який статичний синхронізований метод класу до тих пір, поки блокування рівня класу не буде відпущене потоком, який в даний час містить блокування.
Вихід із синхронізованим
THREAD 1 :: 0
THREAD 1 :: 1
THREAD 1 :: 2
THREAD 1 :: 3
THREAD 1 :: 4
THREAD 1 :: 5
THREAD 1 :: 6
THREAD 1 :: 7
THREAD 1 :: 8
THREAD 1 :: 9
THREAD 3 :: 0
THREAD 3 :: 1
THREAD 3 :: 2
THREAD 3 :: 3
THREAD 3 :: 4
THREAD 3 :: 5
THREAD 3 :: 6
THREAD 3 :: 7
THREAD 3 :: 8
THREAD 3 :: 9
THREAD 2 :: 0
THREAD 2 :: 1
THREAD 2 :: 2
THREAD 2 :: 3
THREAD 2 :: 4
THREAD 2 :: 5
THREAD 2 :: 6
THREAD 2 :: 7
THREAD 2 :: 8
THREAD 2 :: 9
Виведення без синхронізації
THREAD 1 :: 0
THREAD 2 :: 0
THREAD 3 :: 0
THREAD 1 :: 1
THREAD 2 :: 1
THREAD 3 :: 1
THREAD 1 :: 2
THREAD 2 :: 2
THREAD 3 :: 2
THREAD 1 :: 3
THREAD 2 :: 3
THREAD 3 :: 3
THREAD 1 :: 4
THREAD 2 :: 4
THREAD 3 :: 4
THREAD 1 :: 5
THREAD 2 :: 5
THREAD 3 :: 5
THREAD 1 :: 6
THREAD 2 :: 6
THREAD 3 :: 6
THREAD 1 :: 7
THREAD 2 :: 7
THREAD 3 :: 7
THREAD 1 :: 8
THREAD 2 :: 8
THREAD 3 :: 8
THREAD 1 :: 9
THREAD 2 :: 9
THREAD 3 :: 9
synchronized
, але послідовність пам'яті ігнорується.
synchronized
Ключове слово запобігає одночасний доступ до блоку коди або об'єкта з допомогою декількох потоків. Усі методи Hashtable
є synchronized
, тому лише один потік може виконувати будь-який з них одночасно.
При використанні не- synchronized
конструкції типу HashMap
, ви повинні побудувати функції безпеки потоків в вашому коді , щоб уникнути помилок узгодженості.
synchronized
означає, що в середовищі з декількома synchronized
потоками об'єкт, що має метод (и) / блок (и), не дозволяє двом потокам отримувати доступ до synchronized
методу (ив) / блоку (кодів) коду одночасно. Це означає, що один потік не може читати, поки інший поновлює його.
Натомість другий потік буде чекати, поки перша нитка завершить її виконання. Накладні витрати - швидкість, але перевагою є гарантована узгодженість даних.
Якщо ваша програма є однопоточною, synchronized
блоки не надають переваг.
synchronized
Ключове слово викликає потік , щоб отримати блокування при вході в методі, таким чином , що тільки один потік може виконати метод в той же час (для даного екземпляра об'єкта, якщо він не є статичним методом).
Це часто називають безпекою нитки класу, але я б сказав, що це евфемізм. Хоча це правда, що синхронізація захищає внутрішній стан вектора від пошкодження, але це зазвичай не дуже допомагає користувачу Vector.
Врахуйте це:
if (vector.isEmpty()){
vector.add(data);
}
Незважаючи на те, що залучені методи синхронізовані, оскільки вони блокуються та розблоковуються окремо, два, на жаль, приурочені потоки можуть створити вектор з двома елементами.
Таким чином, фактично вам також доведеться синхронізувати код свого додатка.
Оскільки синхронізація на рівні методів є а) дорогою, коли вона вам не потрібна, і b) недостатньою, коли вам потрібна синхронізація, зараз існують несинхронізовані заміни (ArrayList у випадку Vector).
Зовсім недавно було випущено пакет одночасних пакетів з низкою розумних служб, які опікуються питаннями багаторівневої нитки.
Синхронізоване ключове слово в Java має відношення до безпеки потоку, тобто коли кілька потоків читають або записують одну і ту ж змінну.
Це може статися безпосередньо (шляхом доступу до тієї самої змінної) або опосередковано (за допомогою класу, який використовує інший клас, який має доступ до тієї ж змінної).
Синхронізоване ключове слово використовується для визначення блоку коду, де безліч потоків можуть отримати доступ до однієї змінної безпечним способом.
Синтаксично synchronized
ключове слово приймає Object
як параметр (його називають об'єктом блокування ), за яким слідує a { block of code }
.
Коли виконання стикається з цим ключовим словом, поточний потік намагається "заблокувати / придбати / володіти" (взяти свій вибір) об'єкта блокування та виконати відповідний блок коду після придбання блокування.
Будь-яке записування до змінних всередині блоку синхронізованого коду гарантовано буде видимим для кожного іншого потоку, який аналогічно виконує код всередині блоку синхронізованого коду, використовуючи той самий об'єкт блокування .
Лише одна нитка одночасно може утримувати замок, протягом цього часу всі інші потоки, які намагаються придбати той самий об’єкт блокування, будуть чекати (призупиняють їх виконання). Блокування буде звільнено, коли виконання вийде з блоку синхронізованого коду.
Додавання synchronized
ключового слова в визначення методу одно всього тіла методу загортають в синхронізований блоці коду з об'єктом блокування істотами this
(для методів примірників) і ClassInQuestion.getClass()
(для методів класу) .
- Метод екземпляра - це метод, який не має static
ключового слова.
- Метод класу - це метод, який має static
ключове слово.
Без синхронізації не гарантується, в якому порядку відбувається читання і запис, можливо, залишаючи змінну зі сміттям.
(Наприклад, змінна може закінчуватися половиною бітів, записаних одним потоком, і половиною бітів, записаних іншим потоком, залишаючи змінну в стані, який не намагався записати жоден з потоків, а комбінований безлад обох.)
Недостатньо завершити операцію запису в потоці раніше (час настінного годинника) інший потік читає її, тому що апаратне забезпечення могло б кешувати значення змінної, і нитка читання побачила б кешоване значення замість того, що було записано в це.
Таким чином, у випадку Java вам потрібно дотримуватися моделі пам'яті Java, щоб упевнитись, що помилки при нанизуванні не трапляються.
Іншими словами: Використовуйте синхронізацію, атомні операції або класи, які використовують їх для вас під кришками.
Джерела
http://docs.oracle.com/javase/specs/jls/se8/html/index.html
Специфікація мови Java®, 2015-02-13
Подумайте про це як про якийсь турнікет, як ви могли б знайти на футбольному майданчику. Є паралельні пари людей, які хочуть зайти, але на турнікеті вони "синхронізовані". За один раз може пройти лише одна людина. Усі, хто хоче пройти, зроблять, але можуть зачекати, поки вони зможуть пройти.
Що таке ключове слово синхронізоване?
Нитки спілкуються головним чином шляхом спільного доступу до полів і посилань на посилання на об'єкти. Така форма спілкування є надзвичайно ефективною, але робить можливими два види помилок: втручання в потоки та помилки послідовності пам'яті . Інструментом, необхідним для запобігання цих помилок, є синхронізація.
Синхронізовані блоки або методи запобігають втручанню потоку і забезпечують відповідність даних. У будь-який момент часу лише один потік може отримати доступ до синхронізованого блоку або методу ( критичний розділ ), придбавши замок. Інші нитки будуть чекати виходу блокування для доступу до критичного розділу .
Коли методи синхронізовані?
Методи синхронізуються, коли ви додаєте synchronized
до визначення методу чи декларації. Ви також можете синхронізувати певний блок коду за допомогою методу.
Що це означає програматично та логічно?
Це означає, що лише один потік може отримати доступ до критичної секції , придбавши замок. Якщо цей потік не звільнить цей замок, усім іншим потокам (ім) доведеться чекати, щоб придбати замок. Вони не мають доступу для входу в критичну секцію із придбанням блокування.
Це неможливо зробити магією. Це відповідальність програміста за те, щоб визначити критичний розділ у додатку та захистити його відповідно. Java надає основу для захисту вашої програми, але де і які всі розділи слід захищати - відповідальність програміста.
Більше інформації на сторінці документації Java
Внутрішні блокування та синхронізація:
Синхронізація побудована навколо внутрішньої сутності, відомої як внутрішній замок або замок монітора. Внутрішні блокування відіграють роль в обох аспектах синхронізації: забезпечення виключного доступу до стану об'єкта та встановлення взаємозв'язків, що мають місце перед важливими для видимості.
Кожен об'єкт має властивий йому замок . За умовою, потік, який потребує ексклюзивного та послідовного доступу до полів об'єкта, повинен отримати власний замок об'єкта перед доступом до них, а потім випустити внутрішній замок, коли це зроблено з ними.
Кажуть, що нитка володіє внутрішнім замком між часом, коли вона придбала замок і відпустила замок. Поки нитка володіє власним замком, жодна інша нитка не може придбати такий самий замок. Інший потік блокується при спробі придбати замок.
Коли потік звільняє внутрішній замок, між цією дією та будь-яким наступним придбанням того ж блокування встановлюється взаємозв'язок, що відбувається раніше.
Синхронізація методів має два ефекти :
По-перше, неможливо переплутати два виклики синхронізованих методів на одному об’єкті.
Коли одна нитка виконує синхронізований метод для об'єкта, всі інші потоки, які викликають синхронізовані методи для одного блоку об'єктів (призупинення виконання), поки перший потік не буде виконано з об'єктом.
По-друге, при виході синхронізованого методу він автоматично встановлює зв'язок, що відбувається раніше, з будь-яким наступним викликом синхронізованого методу для того ж об'єкта.
Це гарантує, що зміни стану об'єкта є видимими для всіх потоків.
Шукайте інші альтернативи синхронізації в:
Synchronized normal method
еквівалент
Synchronized statement
(використовувати це)
class A {
public synchronized void methodA() {
// all function code
}
equivalent to
public void methodA() {
synchronized(this) {
// all function code
}
}
}
Synchronized static method
еквівалент Synchronized statement
(клас використання)
class A {
public static synchronized void methodA() {
// all function code
}
equivalent to
public void methodA() {
synchronized(A.class) {
// all function code
}
}
}
Синхронізований оператор (використовуючи змінну)
class A {
private Object lock1 = new Object();
public void methodA() {
synchronized(lock1 ) {
// all function code
}
}
}
Бо synchronized
ми маємо і те, Synchronized Methods
і Synchronized Statements
. Однак Synchronized Methods
це схоже на Synchronized Statements
те, що нам просто потрібно зрозумітиSynchronized Statements
.
=> В основному, у нас буде
synchronized(object or class) { // object/class use to provides the intrinsic lock
// code
}
Ось 2 думки, які допомагають зрозуміти synchronized
intrinsic lock
асоційований з ним.synchronized statement
, він автоматично отримує значення intrinsic lock
для цього synchronized statement's
об'єкта і звільняє його, коли метод повертається. Поки нитка володіє антенною intrinsic lock
, НІЙ інший потік не може придбати SAME lock => thread safe.=> Коли thread A
викликає synchronized(this){// code 1}
=> весь код блоку (внутрішній клас), де have synchronized(this)
і all synchronized normal method
(всередині класу) заблокований, оскільки блокування SAME . Він буде виконуватися післяthread A
розблокування ("// код 1" завершено).
Така поведінка схожа на synchronized(a variable){// code 1}
або synchronized(class)
.
САМЕ ЗАКЛЮЧЕННЯ => замок (не залежить від того, який метод? Чи які заяви?)
Я вважаю за краще, synchronized statements
тому що вона більш розширювана. Наприклад, у майбутньому вам потрібно лише синхронізувати частину методу. Наприклад, у вас є 2 синхронізовані методи, і вони не мають жодного відношення один до одного, однак, коли потік запускає метод, він блокує інший метод (це може запобігти використанню synchronized(a variable)
).
Однак застосувати синхронізований метод просто, а код виглядати просто. Для деяких класів існує лише 1 синхронізований метод або всі синхронізовані методи у класі, що відповідають один одному => ми можемо використовувати, synchronized method
щоб зробити код коротшим і легшим для розуміння
(це не стосується великої кількості synchronized
, це різниця між об'єктом і класом або нестатичною і статичною).
synchronized
або звичайний метод або synchronized(this)
або synchronized(non-static variable)
вона буде синхронізована база кожного примірника об'єкта. synchronized
або статичного методу, synchronized(class)
або synchronized(static variable)
він синхронізується на базі класуhttps://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
Сподіваюся, це допоможе
Ось пояснення з навчальних посібників Java .
Розглянемо наступний код:
public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public synchronized int value() { return c; } }
якщо
count
це примірникSynchronizedCounter
, то синхронізація цих методів має два ефекти:
- По-перше, неможливо переплутати два виклики синхронізованих методів на одному об’єкті. Коли одна нитка виконує синхронізований метод для об'єкта, всі інші потоки, які викликають синхронізовані методи для того ж блоку об'єктів (призупинення виконання), поки перший потік не буде виконано з об'єктом.
- По-друге, при виході синхронізованого методу він автоматично встановлює зв'язок, що відбувається раніше, з будь-яким наступним викликом синхронізованого методу для того ж об'єкта. Це гарантує, що зміни стану об'єкта є видимими для всіх потоків.
Наскільки я розумію, синхронізований в основному означає, що компілятор записує monitor.enter і monitor.exit навколо вашого методу. Як такий, він може бути безпечним для потоків залежно від способу його використання (я маю на увазі, що ви можете написати об'єкт синхронізованими методами, який не є безпечним для потоків, залежно від того, що робить ваш клас).
Те, що інші відповіді відсутні, є одним із важливих аспектів: бар'єри пам’яті . Синхронізація нитки в основному складається з двох частин: серіалізації та видимості. Я раджу всім google щодо "бар'єру пам'яті jvm", оскільки це нетривіальна і надзвичайно важлива тема (якщо ви змінюєте спільні дані, до яких звертаються декілька потоків). Зробивши це, я раджу переглядати класи пакунків java.util.concurrent, які допомагають уникнути використання явної синхронізації, що, в свою чергу, допомагає зберігати програми просто та ефективно, можливо, навіть запобігаючи тупиковість.
Одним із таких прикладів є ConcurrentLinkedDeque . Разом з командним шаблоном це дозволяє створювати високоефективні робочі потоки шляхом введення команд у паралельну чергу - не потрібна явна синхронізація, неможливі тупикові місця, не потрібно явного сну (), просто запитуйте чергу, зателефонувавши take ().
Коротше кажучи: "синхронізація пам'яті" відбувається неявно, коли ви запускаєте потік, потік закінчується, ви читаєте мінливу змінну, ви розблоковуєте монітор (залишаєте синхронізований блок / функцію) тощо. Ця "синхронізація" впливає (у певному сенсі "проганяє ") всі записи, зроблені перед цією конкретною дією. У випадку з вищезгаданим ConcurrentLinkedDeque , документація "говорить":
Ефекти узгодженості пам’яті: Як і в інших одночасних колекціях, дії в потоці перед розміщенням об'єкта в ConcurrentLinkedDeque відбуваються перед діями, наступними після доступу або видалення цього елемента з ConcurrentLinkedDeque в іншому потоці.
Це неявна поведінка є дещо згубним аспектом, тому що більшість програмістів на Java без особливого досвіду просто забирають багато, як дано через це. А потім раптом натрапляють на цей потік після того, як Java не робить того, що "належить" робити у виробництві, де є різне навантаження на роботу - і перевірити проблеми одночасності досить складно.
Синхронізований просто означає, що декілька потоків, якщо вони пов'язані з одним об'єктом, можуть запобігти брудному читанню та запису, якщо синхронізований блок використовується на певному об'єкті. Щоб надати більше ясності, давайте взяти приклад:
class MyRunnable implements Runnable {
int var = 10;
@Override
public void run() {
call();
}
public void call() {
synchronized (this) {
for (int i = 0; i < 4; i++) {
var++;
System.out.println("Current Thread " + Thread.currentThread().getName() + " var value "+var);
}
}
}
}
public class MutlipleThreadsRunnable {
public static void main(String[] args) {
MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
Thread t1 = new Thread(runnable1);
t1.setName("Thread -1");
Thread t2 = new Thread(runnable2);
t2.setName("Thread -2");
Thread t3 = new Thread(runnable1);
t3.setName("Thread -3");
t1.start();
t2.start();
t3.start();
}
}
Ми створили два об’єкти класу MyRunnable, якими можна поділитись за допомогою потоку 1 та потоком 3 та runnable2 лише з потоком 2. Тепер, коли t1 і t3 починається без синхронізованого використання, вихід PFB, який дозволяє припустити, що обидва потоки 1 і 3 одночасно впливають на значення var, де для потоку 2 var має власну пам'ять.
Without Synchronized keyword
Current Thread Thread -1 var value 11
Current Thread Thread -2 var value 11
Current Thread Thread -2 var value 12
Current Thread Thread -2 var value 13
Current Thread Thread -2 var value 14
Current Thread Thread -1 var value 12
Current Thread Thread -3 var value 13
Current Thread Thread -3 var value 15
Current Thread Thread -1 var value 14
Current Thread Thread -1 var value 17
Current Thread Thread -3 var value 16
Current Thread Thread -3 var value 18
Використовуючи Synchronzied, нитка 3 чекає завершення потоку 1 у всіх сценаріях. Існує два замикання блокування, один на runnable1, розділений на потік 1 і на потік 3, а інший на runnable2, розділений лише на потік 2.
Current Thread Thread -1 var value 11
Current Thread Thread -2 var value 11
Current Thread Thread -1 var value 12
Current Thread Thread -2 var value 12
Current Thread Thread -1 var value 13
Current Thread Thread -2 var value 13
Current Thread Thread -1 var value 14
Current Thread Thread -2 var value 14
Current Thread Thread -3 var value 15
Current Thread Thread -3 var value 16
Current Thread Thread -3 var value 17
Current Thread Thread -3 var value 18
синхронізований простий засіб, що два блоки не можуть одночасно отримати доступ до блоку / методу. Коли ми кажемо, що будь-який блок / метод класу синхронізований, це означає, що лише один потік може отримати доступ до них одночасно. Внутрішньо потік, який намагається отримати доступ до нього, спочатку знімає блокування на цьому об'єкті, і поки цього блокування немає, жоден інший потік не може отримати доступ до будь-якого із синхронізованих методів / блоків цього примірника класу.
Зауважте, інший потік може отримати доступ до методу того ж об'єкта, який не визначений для синхронізації. Нитка може звільнити замок, зателефонувавши
Object.wait()
synchronized
блок на Java - це монітор у багатопотоковому читанні. synchronized
блок з тим самим об'єктом / класом може бути виконаний лише одним потоком, всі інші чекають. Це може допомогти в race condition
ситуації, коли кілька потоків намагаються оновити ту саму змінну (перший крок - використання volatile
About )
Java 5
розширено synchronized
підтримкою happens-before
[Про нас]
Розблокування (синхронізований блок або вихід методу) монітора відбувається перед кожним наступним блокуванням (синхронізований блок або запис методу) того самого монітора.
Наступний крок - java.util.concurrent