Java 线程第三版 第六章 高级同步议题 读书笔记

多线程数据同步错误比较难检测,因为通常是与事件的特定发生顺序有关。

一、同步术语

Barrier(屏障)

barrier是多个Thread的集合点:所有的Thread都应该到齐在这个barrier之后才能允许它们继续下去。

Condition variable(条件变量)

实际上不是变量,而是与某个lock有关联的变量。

Event variable(事件变量)

条件变量的另一个名称。

Critical section(临界区)

临界区是synchronized方法或者block。

Lock(锁)

已经进入synchronized方法或者block的特定Thread所授予的访问权。

Mutex(互斥)

lock的另一个名字。

Monitor(监视器)

在某些系统中,一个monitor就是一个lock,其他系统中的monitor类似于等待与通知的机制。

Reader/writer lock(读/写锁)

一种可以被多个Thread同时取得的锁,只要各Thread间都同意只从共享数据中做读的操作(或者写操作)。

Semaphore(信号量)

在各系统间使用是不一致的。不能完全等同于lock方式使用。

二、J2SE 5.0中加入的同步Class

Semaphore

Semaphore记录它可以发出的许可数目。

Semaphore与Lock的区别,Semaphore构造器需要制定许可数目,方法可以返回许可的总数与可用数量。Semaphore没有关联的条件变量可用。

Barrier

未广泛使用的原因是可以有两种替代方案:

1. 要求Thread等待条件变量,最后达到Thread通过通知所有其他Thread来释放掉barrier。

2. 通过join方法等待Thread终结,一旦所有的Thread已经被结合,就可以启动新的Thread进行程序的下一个阶段。

倒数的Latch

类似与Barrier的同步功能。区别在于释放的条件不是等待所有thread满足条件。

Exchanger

其结合barrier以及数据传递。能够让一对thread相互回合类似与barrier,这一对thread相遇时能够在分开前交换一组数据。

读/写锁

有时候需要在可能会很长时间的操作下从对象中读取信息。

三、防止死锁

import javax.swing.*;
import java.awt.event.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import javathreads.examples.ch03.*;

public class ScoreLabel extends JLabel implements CharacterListener {

    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;
    private Lock adminLock = new ReentrantLock();
    private Lock charLock = new ReentrantLock();
    private Lock scoreLock = new ReentrantLock();

    public ScoreLabel (CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;

        if (generator != null)
            generator.addCharacterListener(this);
        if (typist != null)
             typist.addCharacterListener(this);
    }

    public ScoreLabel () {
        this(null, null);
    }

    public void resetGenerator(CharacterSource newGenerator) {
        try {
            adminLock.lock();
            if (generator != null)
                generator.removeCharacterListener(this);

            generator = newGenerator;
            if (generator != null)
                generator.addCharacterListener(this);
        } finally {
            adminLock.unlock();
        }
    }

    public void resetTypist(CharacterSource newTypist) {
        try {
            adminLock.lock();
            if (typist != null)
                typist.removeCharacterListener(this);

            typist = newTypist;
            if (typist != null)
                typist.addCharacterListener(this);
        } finally {
            adminLock.unlock();
        }
    }

    public void resetScore() {
        try {
            charLock.lock();
            scoreLock.lock();
            score = 0;
            char2type = -1;
            setScore();
        } finally {
            charLock.unlock();
            scoreLock.unlock();
        }
    }

    private void setScore() {
        // This method will be explained later in chapter 7
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setText(Integer.toString(score));
            }
        });
    }

    public void newCharacter(CharacterEvent ce) {
        try {
            scoreLock.lock();
            charLock.lock();
            // Previous character not typed correctly - 1 point penalty
            if (ce.source == generator) {
                if (char2type != -1) {
                    score--;
                    setScore();
                }
                char2type = ce.character;
            }

            // If character is extraneous - 1 point penalty
            // If character does not match - 1 point penalty
            else {
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
                setScore();
            }
        } finally {
            scoreLock.unlock();
            charLock.unlock();
        }
    }
}

避免死锁最简单的方法就是当持有一个锁的时候,绝对不再调用任何需要其他锁的方法。

import javax.swing.*;
import java.awt.event.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import javathreads.examples.ch03.*;

public class ScoreLabel extends JLabel implements CharacterListener {

    private volatile int score = 0;
    private int char2type = -1;
    private CharacterSource generator = null, typist = null;
    private Lock adminLock = new ReentrantLock();
    private Lock charLock = new ReentrantLock();
    private Lock scoreLock = new ReentrantLock();

    public ScoreLabel (CharacterSource generator, CharacterSource typist) {
        this.generator = generator;
        this.typist = typist;

        if (generator != null)
            generator.addCharacterListener(this);
        if (typist != null)
             typist.addCharacterListener(this);
    }

    public ScoreLabel () {
        this(null, null);
    }

    public void resetGenerator(CharacterSource newGenerator) {
        try {
            adminLock.lock();
            if (generator != null)
                generator.removeCharacterListener(this);

            generator = newGenerator;
            if (generator != null)
                generator.addCharacterListener(this);
        } finally {
            adminLock.unlock();
        }
    }

    public void resetTypist(CharacterSource newTypist) {
        try {
            adminLock.lock();
            if (typist != null)
                typist.removeCharacterListener(this);

            typist = newTypist;
            if (typist != null)
                typist.addCharacterListener(this);
        } finally {
            adminLock.unlock();
        }
    }

    public void resetScore() {
        try {
            scoreLock.lock();
            charLock.lock();
            score = 0;
            char2type = -1;
            setScore();
        } finally {
            charLock.unlock();
            scoreLock.unlock();
        }
    }

    private void setScore() {
        // This method will be explained later in chapter 7
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                setText(Integer.toString(score));
            }
        });
    }

    public void newCharacter(CharacterEvent ce) {
        try {
            scoreLock.lock();
            charLock.lock();
            // Previous character not typed correctly - 1 point penalty
            if (ce.source == generator) {
                if (char2type != -1) {
                    score--;
                    setScore();
                }
                char2type = ce.character;
            }

            // If character is extraneous - 1 point penalty
            // If character does not match - 1 point penalty
            else {
                if (char2type != ce.character) {
                    score--;
                } else {
                    score++;
                    char2type = -1;
                }
                setScore();
            }
        } finally {
            scoreLock.unlock();
            charLock.unlock();
        }
    }
}

死锁与自动释放的Lock

Lock Interface锁不会自动释放,即使异常退出程序。而synchronized关键字异常情况下依然会释放。

一种避免这种情况发生的办法就是把unLock放置到finally子句中。

synchronized在异常情况下都能释放锁并不一定在所有场景下都是优点,有可能当前方法执行一部分异常退出,其他线程再执行有可能获取到的并不是正确数据。

使用时限来防止死锁

通过在lock上设定期限,使用tryLock()在算法中提供替代方案。

四、死锁检测

死锁的问题在于它会导致程序无止境地等待。

支持检测死锁的Lock类,用于演示分析死锁过程:

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

//
// This is a very very slow implementation of a ReentrantLock class and is not for
//   everyday usage. The purpose of this class is to test for deadlocks. The lock()
//   method now throws a DeadlockDetectedException, if a deadlock occurs.
//
public class DeadlockDetectingLock extends ReentrantLock {
    // List of deadlock detecting locks.
    // This array is not thread safe, and must be externally synchronized
    //    by the class lock. Hence, it should only be called by static
    //    methods.
    private static List deadlockLocksRegistry = new ArrayList();

    private static synchronized void registerLock(DeadlockDetectingLock ddl) {
        if (!deadlockLocksRegistry.contains(ddl))
             deadlockLocksRegistry.add(ddl);
    }

    private static synchronized void unregisterLock(DeadlockDetectingLock ddl) {
        if (deadlockLocksRegistry.contains(ddl))
             deadlockLocksRegistry.remove(ddl);
    }

    // List of threads hard waiting for this lock.
    // This array is not thread safe, and must be externally synchronized
    //    by the class lock. Hence, it should only be called by static
    //    methods.
    private List hardwaitingThreads = new ArrayList();

    private static synchronized void markAsHardwait(List l, Thread t) {
        if (!l.contains(t)) l.add(t);
    }

    private static synchronized void freeIfHardwait(List l, Thread t) {
        if (l.contains(t)) l.remove(t);
    }

    //
    // Deadlock checking methods
    //
    // Given a thread, return all locks that are already owned
    // Must own class lock prior to calling this method
    private static Iterator getAllLocksOwned(Thread t) {
        DeadlockDetectingLock current;
        ArrayList results = new ArrayList();

        Iterator itr = deadlockLocksRegistry.iterator();
        while (itr.hasNext()) {
            current = (DeadlockDetectingLock) itr.next();
            if (current.getOwner() == t) results.add(current);
        }
        return results.iterator();
    }

    // Given a lock, return all threads that are hard waiting for the lock
    // Must own class lock prior to calling this method
    private static Iterator getAllThreadsHardwaiting(DeadlockDetectingLock l) {
        return l.hardwaitingThreads.iterator();
    }

    // Check to see if a thread can perform a hard wait on a lock
    private static synchronized
             boolean canThreadWaitOnLock(Thread t, DeadlockDetectingLock l) {
        Iterator locksOwned = getAllLocksOwned(t);
        while (locksOwned.hasNext()) {
            DeadlockDetectingLock current = (DeadlockDetectingLock) locksOwned.next();

            // Thread can't wait if lock is already owned. This is the end condition
            //      for the recursive algorithm -- as the initial condition should be
            //      already tested for.
            if (current == l) return false;

            Iterator waitingThreads = getAllThreadsHardwaiting(current);
            while (waitingThreads.hasNext()) {
                Thread otherthread = (Thread) waitingThreads.next();

                // In order for the thread to safely wait on the lock, it can't
                //   own any locks that have waiting threads that already owns
                //   lock. etc. etc. etc. recursively etc.
                if (!canThreadWaitOnLock(otherthread, l)) {
                    return false;
                }
            }
        }
        return true;
    }

    //
    // Core Constructors
    //
    public DeadlockDetectingLock() {
        this(false, false);
    }

    public DeadlockDetectingLock(boolean fair) {
        this(fair, false);
    }

    private boolean debugging;
    public DeadlockDetectingLock(boolean fair, boolean debug) {
        super(fair);
        debugging = debug;
        registerLock(this);
    }

    //
    // Core Methods
    //
    public void lock() {
        // Note: Owner can't change if current thread is owner. It is
        //       not guaranteed otherwise. Other owners can change due to
        //       condition variables.
        if (isHeldByCurrentThread()) {
            if (debugging) System.out.println("Already Own Lock");
            super.lock();
            freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            return;
        }

        // Note: The wait list must be marked before it is tested because
        //       there is a race condition between lock() method calls.
        markAsHardwait(hardwaitingThreads, Thread.currentThread());
        if (canThreadWaitOnLock(Thread.currentThread(), this)) {
            if (debugging) System.out.println("Waiting For Lock");
            super.lock();
            freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            if (debugging) System.out.println("Got New Lock");
        } else {
            throw new DeadlockDetectedException("DEADLOCK");
        }
    }

    //
    // Note: It is debatable whether this is a hard or soft wait. Even if
    //       interruption is common, we don't know if the interrupting thread
    //       is also involved in the deadlock. As a compromise, we'll just
    //       not allow interrupts. This method is disabled.
    public void lockInterruptibly() throws InterruptedException {
        lock();
    }

    //
    // Note: It is not necessary to override the tryLock() methods. These
    //     methods perform a soft wait -- there is a limit to the wait. It
    //     not possible to deadlock when locks are not waiting indefinitely.
    //

    // Note 1: Deadlocks are possible with any hard wait -- this includes
    //      the reacquitition of the lock upon return from an await() method.
    //      As such, condition variables will mark for the future hard
    //      wait, prior to releasing the lock.
    // Note 2: There is no need to check for deadlock on this end because
    //      a deadlock can be created whether the condition variable owns the
    //      lock or is reacquiring it. Since we are marking *before* giving
    //      up ownership, the deadlock will be detected on the lock() side
    //      first. It is not possible to create a new deadlock just by releasing
    //      locks.
    public class DeadlockDetectingCondition implements Condition {
	Condition embedded;
        protected DeadlockDetectingCondition(ReentrantLock lock, Condition embedded) {
	    this.embedded = embedded;
        }

        // Note: The algorithm can detect a deadlock condition if the thead is
        //    either waiting for or already owns the lock, or both. This is why
        //    we have to mark for waiting *before* giving up the lock.
        public void await() throws InterruptedException {
            try {
                markAsHardwait(hardwaitingThreads, Thread.currentThread());
                embedded.await();
            } finally {
                freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            }
        }

        public void awaitUninterruptibly() {
            markAsHardwait(hardwaitingThreads, Thread.currentThread());
            embedded.awaitUninterruptibly();
            freeIfHardwait(hardwaitingThreads, Thread.currentThread());
        }

        public long awaitNanos(long nanosTimeout) throws InterruptedException {
            try {
                markAsHardwait(hardwaitingThreads, Thread.currentThread());
                return embedded.awaitNanos(nanosTimeout);
            } finally {
                freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            }
        }

        public boolean await(long time, TimeUnit unit) throws InterruptedException {
            try {
                markAsHardwait(hardwaitingThreads, Thread.currentThread());
                return embedded.await(time, unit);
            } finally {
                freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            }
        }

        public boolean awaitUntil(Date deadline) throws InterruptedException {
            try {
                markAsHardwait(hardwaitingThreads, Thread.currentThread());
                return embedded.awaitUntil(deadline);
            } finally {
                freeIfHardwait(hardwaitingThreads, Thread.currentThread());
            }
        }

	public void signal() {
	    embedded.signal();
	}

	public void signalAll() {
	    embedded.signalAll();
	}
    }

    // Return a condition variable that support detection of deadlocks
    public Condition newCondition() {
        return new DeadlockDetectingCondition(this, super.newCondition());
    }

    //
    // Testing routines here
    //
    // These are very simple tests -- more tests will have to be written
    private static Lock a = new DeadlockDetectingLock(false, true);
    private static Lock b = new DeadlockDetectingLock(false, true);
    private static Lock c = new DeadlockDetectingLock(false, true);
    private static Condition wa = a.newCondition();
    private static Condition wb = b.newCondition();
    private static Condition wc = c.newCondition();

    private static void delaySeconds(int seconds) {
        try {
             Thread.sleep(seconds * 1000);
        } catch (InterruptedException ex) {
        }
    }

    private static void awaitSeconds(Condition c, int seconds) {
        try {
             c.await(seconds, TimeUnit.SECONDS);
        } catch (InterruptedException ex) {
        }
    }

    private static void testOne() {
         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread one grab a");
                 a.lock();
                 delaySeconds(2);
                 System.out.println("thread one grab b");
                 b.lock();
                 delaySeconds(2);
                 a.unlock(); b.unlock();
             }
         }).start();

         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread two grab b");
                 b.lock();
                 delaySeconds(2);
                 System.out.println("thread two grab a");
                 a.lock();
                 delaySeconds(2);
                 a.unlock(); b.unlock();
             }
         }).start();
    }

    private static void testTwo() {
         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread one grab a");
                 a.lock();
                 delaySeconds(2);
                 System.out.println("thread one grab b");
                 b.lock();
                 delaySeconds(10);
                 a.unlock(); b.unlock();
             }
         }).start();

         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread two grab b");
                 b.lock();
                 delaySeconds(2);
                 System.out.println("thread two grab c");
                 c.lock();
                 delaySeconds(10);
                 b.unlock(); c.unlock();
             }
         }).start();

         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread three grab c");
                 c.lock();
                 delaySeconds(4);
                 System.out.println("thread three grab a");
                 a.lock();
                 delaySeconds(10);
                 c.unlock(); a.unlock();
             }
         }).start();
    }

    private static void testThree() {
         new Thread(new Runnable() {
             public void run() {
                 System.out.println("thread one grab b");
                 b.lock();
                 System.out.println("thread one grab a");
                 a.lock();
                 delaySeconds(2);
                 System.out.println("thread one waits on b");
                 awaitSeconds(wb, 10);
                 a.unlock(); b.unlock();
             }
         }).start();

         new Thread(new Runnable() {
             public void run() {
                 delaySeconds(1);
                 System.out.println("thread two grab b");
                 b.lock();
                 System.out.println("thread two grab a");
                 a.lock();
                 delaySeconds(10);
                 b.unlock(); c.unlock();
             }
         }).start();

    }

    public static void main(String args[]) {
        int test = 1;
	if (args.length > 0)
	    test = Integer.parseInt(args[0]);
	switch(test) {
	    case 1:
                testOne();    // 2 threads deadlocking on grabbing 2 locks
		break;
	    case 2:
        	testTwo();    // 3 threads deadlocking on grabbing 2 out of 3 locks
		break;
	    case 3:
                testThree();  // 2 threads deadlocking on 2 locks with CV wait
		break;
	    default:
	        System.err.println("usage: java DeadlockDetectingLock [ test# ]");
	}
        delaySeconds(60);
        System.out.println("--- End Program ---");
        System.exit(0);
    }
}

此实现继承Lock Interface,所以可以用在任何需要Lock对象的地方。

在所有需要检测死锁的地方都已此类取代,甚至包括synchronized关键字锁提供的锁。

在检测到死锁时会立即抛出DeadlockDetectedException。 但是这个类未考虑性能问题,所以尽量不要在真实环境中使用。

两个集合

lock通过使用registerLock、unregisterLock方法来进行添加与删除注册监听。

getAllLocksOwned、getAllThreadsHardwaiting用来取得之前所提过的两种等待子树。

canThreadWaitOnLock用来遍历等待树,检查某特定lock是否已经出现在树上。检测潜在死锁的主要方法。

覆写lock方法

如果当前thread已经持有此lock,没有理由检查死锁。仅需要调用super.lock()即可。

通过canThreadWaitOnLock判断是否死锁。如果死锁抛出异常,反之此Thread就会被添加到lock的hard wait列表中,并调用super.lock()方法。

五、Lock饥饿

每当有多个Thread争夺稀少的资源时,就会有饥饿的危机Thread会存在永远拿不到资源的状况。

例如:某个Thread获得锁之后永远不释放。

public ReentrantLock(boolean fair)
     创建一个具有给定公平策略的 ReentrantLock。

这是一种非常接近先到先服务原则来授出Lock,不管Thread有多少或者是怎么写的,都不可能有任何Thread会因为Lock而挨饿。

Java 线程第三版 第六章 高级同步议题 读书笔记,布布扣,bubuko.com

时间: 2024-12-25 18:08:18

Java 线程第三版 第六章 高级同步议题 读书笔记的相关文章

Java 线程第三版 第八章 Thread与Collection Class 读书笔记

JDK1.2引入最有争议性的改变是将集合类默认为不是Thread安全性的. 一.Collection Class的概述 1. 具有Threadsafe 的Collection Class: java.util.Vector(List) 列表集合,通过索引操作. java.util.Stack(List) 继承自Vector,提供LIFO的顺序操作push进入,pop出元素. java.util.Hashtable(Map) 一个简单.无序的key与value的映射. java.util.conc

Java 线程第三版 第四章 Thread Notification 读书笔记

一.等待与通知 public final void wait() throws InterruptedException 等待条件的发生. public final void wait(long timeout) throws InterruptedException 等待条件的发生.如果通知没有在timeout指定的时间内发生,它还是会返回. public final void wait(long timeout, int nanos) throws InterruptedException

Java 线程第三版 第五章 极简同步技巧 读书笔记

一.能避免同步吗? 取得锁会因为以下原因导致成本很高: 取得由竞争的锁需要在虚拟机的层面上运行更多的程序代码. 要取得有竞争锁的线程总是必须等到锁被释放后. 1. 寄存器的效应 计算机有一定数量的主寄存器用来存储与程序有关的数据. 从逻辑上的观点来看,每个Thread都有自己的一组寄存器.当操作系统将某个Thread分配给CPU时,它会把该Thread特有的信息加载到CPU的寄存器中.在分配不同的Thread给CPU之前,它会将寄存器的信息存下来.所以Thread间绝不会共享保存在寄存器的数据.

Java 线程第三版 第一章Thread导论、 第二章Thread的创建与管理读书笔记

第一章 Thread导论 为何要用Thread ? 非阻塞I/O I/O多路技术 轮询(polling) 信号 警告(Alarm)和定时器(Timer) 独立的任务(Task) 并行算法 第二章 Thread的创建与管理 一.什么是Thread ? Thread是所在主机执行的应用程序任务(task). 只有一个线程的例子: public class Factorial { public static void main(String[] args) { int n = 5; System.ou

Java 线程第三版 第三章数据同步 读书笔记

多线程间共享数据问题 一.Synchronized关键字 atomic一词与"原子"无关,它曾经被认为是物质的最小的单元,不能再被拆解成更小的部分. 当一个方法被声明成synchronized,要执行此方法的thread必须先取得一个token,我们将它称为锁.一旦该方法取得(或者说是获得)锁,它将运行此方法然后释放掉(或者返回)此锁.不管方法时怎样返回的(包括通过异常)该锁会被释放. 二.Volatile关键字 如果变量被标示为volatile,每次使用该变量时都必须从主寄存器中读出

Java 线程第三版 第九章 Thread调度 读书笔记

一.Thread调度的概述 import java.util.*; import java.text.*; public class Task implements Runnable { long n; String id; private long fib(long n) { if (n == 0) return 0L; if (n == 1) return 1L; return fib(n - 1) + fib(n - 2); } public Task(long n, String id)

王爽《汇编语言》第三版 第六章 包含多个段的程序

6.1 在代码段中使用数据 "dw"的含义是定义字型数据.dw即define word. 由于它们在代码段中,程序在运行的时候CS中存放代码段的段地址,所以我们可以从CS中得到它们的段地址. 因为用dw定义的数据处于代码段的最开始,所以偏移地址为0,这8 个数据就在代码段的偏移0.2.4.6.8.A.C.E处. end 除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方. 6.2 在代码段中使用栈 6.3 将数据.代码.栈放入不同的段 我们用和定义代码段一样的方法来定义多个

《Java并发编程实战》第四章 对象的组合 读书笔记

一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素: . 找出构成对象状态的全部变量. . 找出约束状态变量的不变性条件. . 建立对象状态的并发訪问管理策略. 分析对象的状态,首先从对象的域開始. 变量按作用域划分: . 全局变量 . 局部变量 . 方法行參 . 异常处理參数 1. 收集同步需求 假设不了解对象的不变性条件与后验条件,那么就不能确保线程安全性.要满足在状态变量的有效值或状态转换上的各种约束条件.就须要借助原子性和封装性. 说的更简略些是Java线程安全都是

读Javascript高级程序设计第三版第六章面向对象设计--创建对象

虽然Object构造函数或者对象字面量都可以用来创建单个对象,但是缺点非常明显:使用同一接口创建很多对象,会产生大量重复代码. 工厂模式  1 function CreatePerson(name,age,job){ 2         var o=new Object(); 3         o.name=name; 4         o.age=age; 5         o.job=job; 6         o.sayName=function(){ 7