라이브 록이 무엇인지 이해하지만 코드 기반의 좋은 예가 누구인지 궁금합니다. 그리고 코드 기반으로, "두 사람이 복도에서 서로를 지나치려고한다" 는 의미 는 아닙니다 . 다시 읽으면 점심을 잃을 것입니다.
라이브 록이 무엇인지 이해하지만 코드 기반의 좋은 예가 누구인지 궁금합니다. 그리고 코드 기반으로, "두 사람이 복도에서 서로를 지나치려고한다" 는 의미 는 아닙니다 . 다시 읽으면 점심을 잃을 것입니다.
답변:
여기 남편과 아내가 수프를 먹으려 고하지만 그 사이에 숟가락이 하나있는 라이브 락의 매우 간단한 자바 예제가 있습니다. 각 배우자는 너무 공손하며, 다른 배우자가 아직 먹지 않은 경우 숟가락을 통과합니다.
public class Livelock {
static class Spoon {
private Diner owner;
public Spoon(Diner d) { owner = d; }
public Diner getOwner() { return owner; }
public synchronized void setOwner(Diner d) { owner = d; }
public synchronized void use() {
System.out.printf("%s has eaten!", owner.name);
}
}
static class Diner {
private String name;
private boolean isHungry;
public Diner(String n) { name = n; isHungry = true; }
public String getName() { return name; }
public boolean isHungry() { return isHungry; }
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// Don't have the spoon, so wait patiently for spouse.
if (spoon.owner != this) {
try { Thread.sleep(1); }
catch(InterruptedException e) { continue; }
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.isHungry()) {
System.out.printf(
"%s: You eat first my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
continue;
}
// Spouse wasn't hungry, so finally eat
spoon.use();
isHungry = false;
System.out.printf(
"%s: I am stuffed, my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner("Bob");
final Diner wife = new Diner("Alice");
final Spoon s = new Spoon(husband);
new Thread(new Runnable() {
public void run() { husband.eatWith(s, wife); }
}).start();
new Thread(new Runnable() {
public void run() { wife.eatWith(s, husband); }
}).start();
}
}
getOwner
방법뿐만 아니라 동기화 할 수 있나요? 유효 Java에서 " 읽기 및 쓰기 " 둘 다를 제외하고 " 동기화는 효과가 없습니다 .
Thread.join()
보다는 Thread.sleep()
그가 식사를 할 수있는 배우자 대기하고 싶어하기 때문에?
getOwner
메소드는 동기화되어 있어도 setOwner
스레드를 사용하는 getOwner
(또는 필드에 owner
직접 액세스하는 ) 스레드가 다른 스레드가 수행 한 변경 사항을 보도록 보장하지 않으므로 동기화 되어야합니다 setOwner
. 이 vid는 이것을 매우 신중하게 설명합니다. youtube.com/watch?v=WTVooKLLVT8
synchronized
키워드 를 사용할 필요가 없습니다 setOwner
.
Flippant 의견은 제쳐두고, 교착 상태 상황을 감지하고 처리하려고 시도하는 코드가 있습니다. 두 스레드가 교착 상태를 감지하고 서로 "비켜"려고하면주의를 기울이지 않으면 서 항상 "비켜서"루프에 멈춰서 앞으로 나아갈 수 없게됩니다.
"제쳐두고"라는 말은 그들이 자물쇠를 풀고 다른 사람이 자물쇠를 얻도록 시도한다는 것을 의미합니다. 이 작업을 수행하는 두 개의 스레드가있는 상황 (의사 코드)을 상상할 수 있습니다.
// thread 1
getLocks12(lock1, lock2)
{
lock1.lock();
while (lock2.locked())
{
// attempt to step aside for the other thread
lock1.unlock();
wait();
lock1.lock();
}
lock2.lock();
}
// thread 2
getLocks21(lock1, lock2)
{
lock2.lock();
while (lock1.locked())
{
// attempt to step aside for the other thread
lock2.unlock();
wait();
lock2.lock();
}
lock1.lock();
}
경쟁 조건을 제쳐두고, 우리가 여기있는 것은 두 스레드가 동시에 들어가면 내부 루프에서 계속 진행되지 않는 상황입니다. 분명히 이것은 간단한 예입니다. 이 문제를 해결하는 방법은 스레드가 기다리는 시간에 임의의 종류를 무작위로 넣는 것입니다.
올바른 수정은 항상 잠금 계층 구조를 존중하는 것 입니다. 자물쇠를 얻는 순서를 고르고 그에 충실하십시오. 예를 들어, 두 스레드가 항상 lock2 전에 lock1을 획득하면 교착 상태가 발생할 가능성이 없습니다.
허용 된 답변으로 표시된 답변이 없으므로 라이브 잠금 예제를 만들려고 시도했습니다.
오리지널 프로그램 은 2012 년 4 월에 다양한 멀티 스레딩 개념을 배우기 위해 작성되었습니다. 이번에는 교착 상태, 경쟁 조건, 라이브 록 등을 생성하도록 수정했습니다.
먼저 문제 진술을 이해합시다.
쿠키 메이커 문제
ChocoPowederContainer , WheatPowderContainer 성분 용기가 있습니다 . CookieMaker 는 성분 용기에서 일정량의 분말을 취해 쿠키 를 굽습니다 . 쿠키 메이커가 컨테이너를 비우면 다른 컨테이너를 검사하여 시간을 절약합니다. 필러 가 필요한 컨테이너를 채울 때까지 기다립니다 . 있다 필러 일정한 간격에 컨테이너를 확인하고 용기를 필요로하는 경우 일부 수량을 채 웁니다.
github 에서 전체 코드를 확인하십시오 .
간단하게 구현을 설명하겠습니다.
코드를 살펴 보자.
CookieMaker.java
private Integer getMaterial(final Ingredient ingredient) throws Exception{
:
container.lock();
while (!container.getIngredient(quantity)) {
container.empty.await(1000, TimeUnit.MILLISECONDS);
//Thread.sleep(500); //For deadlock
}
container.unlock();
:
}
IngredientContainer.java
public boolean getIngredient(int n) throws Exception {
:
lock();
if (quantityHeld >= n) {
TimeUnit.SECONDS.sleep(2);
quantityHeld -= n;
unlock();
return true;
}
unlock();
return false;
}
필러 가 컨테이너를 채울 때까지 모든 것이 잘 작동합니다 . 그러나 필러를 시작하지 않거나 필러가 예기치 않은 휴가를 가면 하위 스레드의 상태가 계속 변경되어 다른 제조업체가 컨테이너를 검사하고 확인할 수 있습니다.
스레드 상태 및 교착 상태를 감시 하는 데몬 ThreadTracer 데몬도 만들었습니다 . 이것은 콘솔의 출력입니다.
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
WheatPowder Container has 0 only.
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
하위 스레드와 상태가 변경되고 대기 중임을 알 수 있습니다.
실제 (정확한 코드는 없지만) 예제는 SQL 서버 교착 상태를 해결하기 위해 두 개의 경쟁 프로세스가 실시간 잠금을 수행하는 것입니다. 각 프로세스는 동일한 재시도 알고리즘을 사용하여 재 시도합니다. 타이밍이 운이 좋기는하지만 EMS 주제에 추가 된 메시지 (예 : 단일 객체 그래프의 업데이트를 두 번 이상 저장)에 응답하여 유사한 성능 특성을 가진 별도의 시스템에서 발생하며 제어 할 수없는 것을 보았습니다. 잠금 순서.
이 경우 좋은 해결책 은 경쟁하는 소비자를 보유하는 것입니다 (관련되지 않은 객체에서 작업을 분할하여 가능한 한 체인에서 중복 처리를 방지하십시오).
덜 바람직한 (ok, dirty-hack) 솔루션은 타이밍 불운 (처리의 힘 차이의 종류)을 미리 깨뜨 리거나 다른 알고리즘이나 임의의 요소를 사용하여 교착 상태 후에 중단하는 것입니다. 잠금 처리 순서가 각 프로세스에 대해 "고정적"일 수 있기 때문에 여전히 문제가있을 수 있으며, 대기 재 시도에서 고려되지 않는 특정 시간이 소요됩니다.
또 다른 솔루션 (적어도 SQL Server의 경우)은 다른 격리 수준 (예 : 스냅 샷)을 시도하는 것입니다.
복도를 지나가는 두 사람의 예를 작성했습니다. 두 실은 방향이 동일하다는 것을 깨닫 자마자 서로를 피할 것입니다.
public class LiveLock {
public static void main(String[] args) throws InterruptedException {
Object left = new Object();
Object right = new Object();
Pedestrian one = new Pedestrian(left, right, 0); //one's left is one's left
Pedestrian two = new Pedestrian(right, left, 1); //one's left is two's right, so have to swap order
one.setOther(two);
two.setOther(one);
one.start();
two.start();
}
}
class Pedestrian extends Thread {
private Object l;
private Object r;
private Pedestrian other;
private Object current;
Pedestrian (Object left, Object right, int firstDirection) {
l = left;
r = right;
if (firstDirection==0) {
current = l;
}
else {
current = r;
}
}
void setOther(Pedestrian otherP) {
other = otherP;
}
Object getDirection() {
return current;
}
Object getOppositeDirection() {
if (current.equals(l)) {
return r;
}
else {
return l;
}
}
void switchDirection() throws InterruptedException {
Thread.sleep(100);
current = getOppositeDirection();
System.out.println(Thread.currentThread().getName() + " is stepping aside.");
}
public void run() {
while (getDirection().equals(other.getDirection())) {
try {
switchDirection();
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
}
jelbourn 코드의 C # 버전 :
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace LiveLockExample
{
static class Program
{
public static void Main(string[] args)
{
var husband = new Diner("Bob");
var wife = new Diner("Alice");
var s = new Spoon(husband);
Task.WaitAll(
Task.Run(() => husband.EatWith(s, wife)),
Task.Run(() => wife.EatWith(s, husband))
);
}
public class Spoon
{
public Spoon(Diner diner)
{
Owner = diner;
}
public Diner Owner { get; private set; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void SetOwner(Diner d) { Owner = d; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void Use()
{
Console.WriteLine("{0} has eaten!", Owner.Name);
}
}
public class Diner
{
public Diner(string n)
{
Name = n;
IsHungry = true;
}
public string Name { get; private set; }
private bool IsHungry { get; set; }
public void EatWith(Spoon spoon, Diner spouse)
{
while (IsHungry)
{
// Don't have the spoon, so wait patiently for spouse.
if (spoon.Owner != this)
{
try
{
Thread.Sleep(1);
}
catch (ThreadInterruptedException e)
{
}
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.IsHungry)
{
Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
continue;
}
// Spouse wasn't hungry, so finally eat
spoon.Use();
IsHungry = false;
Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
}
}
}
}
}
여기서 하나의 예는 시간이 초과 된 tryLock을 사용하여 둘 이상의 잠금을 확보 할 수 있으며 잠금을 모두 얻을 수없는 경우에는 물러서서 다시 시도하십시오.
boolean tryLockAll(Collection<Lock> locks) {
boolean grabbedAllLocks = false;
for(int i=0; i<locks.size(); i++) {
Lock lock = locks.get(i);
if(!lock.tryLock(5, TimeUnit.SECONDS)) {
grabbedAllLocks = false;
// undo the locks I already took in reverse order
for(int j=i-1; j >= 0; j--) {
lock.unlock();
}
}
}
}
많은 스레드가 충돌하여 일련의 잠금을 얻기 위해 대기하고 있기 때문에 그러한 코드가 문제가 될 것이라고 생각할 수 있습니다. 그러나 이것이 간단한 예로써 나에게 매우 매력적이라고 확신하지 못합니다.
tryLockAll()
잠금과 함께 사용 하는 경우 locks
라이브 록이 없습니다.
jelbourn 코드의 Python 버전 :
import threading
import time
lock = threading.Lock()
class Spoon:
def __init__(self, diner):
self.owner = diner
def setOwner(self, diner):
with lock:
self.owner = diner
def use(self):
with lock:
"{0} has eaten".format(self.owner)
class Diner:
def __init__(self, name):
self.name = name
self.hungry = True
def eatsWith(self, spoon, spouse):
while(self.hungry):
if self != spoon.owner:
time.sleep(1) # blocks thread, not process
continue
if spouse.hungry:
print "{0}: you eat first, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
continue
# Spouse was not hungry, eat
spoon.use()
print "{0}: I'm stuffed, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
def main():
husband = Diner("Bob")
wife = Diner("Alice")
spoon = Spoon(husband)
t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
t0.start()
t1.start()
t0.join()
t1.join()
if __name__ == "__main__":
main()
@jelbourn의 답변을 수정합니다. 그들 중 한 사람이 다른 사람이 배가 고프다는 것을 알게되면 숟가락을 풀고 다른 알림을 기다려야 라이브 록이 발생합니다.
public class LiveLock {
static class Spoon {
Diner owner;
public String getOwnerName() {
return owner.getName();
}
public void setOwner(Diner diner) {
this.owner = diner;
}
public Spoon(Diner diner) {
this.owner = diner;
}
public void use() {
System.out.println(owner.getName() + " use this spoon and finish eat.");
}
}
static class Diner {
public Diner(boolean isHungry, String name) {
this.isHungry = isHungry;
this.name = name;
}
private boolean isHungry;
private String name;
public String getName() {
return name;
}
public void eatWith(Diner spouse, Spoon sharedSpoon) {
try {
synchronized (sharedSpoon) {
while (isHungry) {
while (!sharedSpoon.getOwnerName().equals(name)) {
sharedSpoon.wait();
//System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
}
if (spouse.isHungry) {
System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
sharedSpoon.setOwner(spouse);
sharedSpoon.notifyAll();
} else {
sharedSpoon.use();
sharedSpoon.setOwner(spouse);
isHungry = false;
}
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
System.out.println(name + " is interrupted.");
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner(true, "husband");
final Diner wife = new Diner(true, "wife");
final Spoon sharedSpoon = new Spoon(wife);
Thread h = new Thread() {
@Override
public void run() {
husband.eatWith(wife, sharedSpoon);
}
};
h.start();
Thread w = new Thread() {
@Override
public void run() {
wife.eatWith(husband, sharedSpoon);
}
};
w.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
h.interrupt();
w.interrupt();
try {
h.join();
w.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package concurrently.deadlock;
import static java.lang.System.out;
/* This is an example of livelock */
public class Dinner {
public static void main(String[] args) {
Spoon spoon = new Spoon();
Dish dish = new Dish();
new Thread(new Husband(spoon, dish)).start();
new Thread(new Wife(spoon, dish)).start();
}
}
class Spoon {
boolean isLocked;
}
class Dish {
boolean isLocked;
}
class Husband implements Runnable {
Spoon spoon;
Dish dish;
Husband(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (spoon) {
spoon.isLocked = true;
out.println("husband get spoon");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (dish.isLocked == true) {
spoon.isLocked = false; // give away spoon
out.println("husband pass away spoon");
continue;
}
synchronized (dish) {
dish.isLocked = true;
out.println("Husband is eating!");
}
dish.isLocked = false;
}
spoon.isLocked = false;
}
}
}
class Wife implements Runnable {
Spoon spoon;
Dish dish;
Wife(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (dish) {
dish.isLocked = true;
out.println("wife get dish");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (spoon.isLocked == true) {
dish.isLocked = false; // give away dish
out.println("wife pass away dish");
continue;
}
synchronized (spoon) {
spoon.isLocked = true;
out.println("Wife is eating!");
}
spoon.isLocked = false;
}
dish.isLocked = false;
}
}
}