【Java并发编程学习】六、死锁

/**
 * 可重入内置锁
 *     每一个Java对象均可以用作一个实现同步的锁,这些锁被称为内置锁或监视锁。线程在进入同步代码块以前会自动获取锁,而且在退出同步代码块时
 * 会自动释放锁。获取内置锁的惟一途径是进入由这个锁保护的同步代码块或方法。
 * “重入”意味着获取锁的操做的粒度是“线程”,而不适合调用。
 * 同一个线程在调用本类中其它synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地执行,由于互斥锁时可重入的。
 */
public class Deadlock extends Object {
    private String objID;

    public Deadlock(String id) {
        objID = id;
    }

    public synchronized void checkOther(Deadlock other) {
        print("entering checkOther()");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {

        }
        print("in checkOther() - about to" + "invoke 'other.action()'");
        other.action();
        print("leaving checkOther()");
    }

    public synchronized void action() {
        print("entering action()");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {

        }
        print("leaving action()");
    }

    public void print(String msg) {
        threadPrint("objID=" + objID + "-" + msg);
    }

    public static void threadPrint(String msg) {
        String threadName = Thread.currentThread().getName();
        System.out.println("[Deadlock] threadName= " + threadName + "#" + msg);
    }

    public static void main(String[] args) {
        final Deadlock obj1 = new Deadlock("obj1");
        final Deadlock obj2 = new Deadlock("obj2");

        Runnable runA = new Runnable() {
            @Override
            public void run() {
                obj1.checkOther(obj2);
            }
        };
        Thread threadA = new Thread(runA, "threadA");
        threadA.start();

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Runnable runB = new Runnable() {
            @Override
            public void run() {
                obj2.checkOther(obj1);
            }
        };
        Thread threadB = new Thread(runB, "threadB");
        threadB.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        threadPrint("about to interrupt() threadA");
        threadA.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadPrint("about to interrupt() threadB");
        threadB.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadPrint("did that break the deadlock?");
    }
}
/**
 * 重入锁使用java.util.concurrent.locks.ReentrantLock类来实现
 * 为了保证锁最终必定会被释放(可能会有一场发生),要把互斥区放在try语句块内,并在finally语句块中释放锁,尤为当有return语句时,
 * return语句必须放在try字句中,以确保unlock()不会过早发生,从而将数据暴露给第二个任务。
 *
 * 有助于提升“锁”性能的几点建议:
 *     一、减小锁持有时间;减小锁的持有时间有助于下降锁冲突的可能性,进而提高系统的并发能力。
 *     二、减少锁粒度
 *        a)线程安全的HashMap:使用Collections.synchronizedMap()方法包装HashMap
 *          public static Map m = Collections.synchronizedMap(new HashMap())
 *        b)对于ConcurrentHashMap,它内部进一步细分了若干小的HashMap,称之为段(SEGMENT)。默认状况下,一个ConcurrentHashMap被
 *        进一步细分为16个段。因为默认有16个段,所以,若是够幸运的话,ConcurrentHashMap能够同时接受16个线程同时插入
 *        (若是都插入不一样的段中),从而大大提升其吞吐量;减小锁粒度会引入一个新的问题,即:当系统须要取得全局锁时,其消耗的资源会比较多。
 *        c)所谓减小锁粒度,就是只缩小锁定对象的范围,从而减小锁冲突的可能性,进而提升系统的并发能力;
 *     三、读写分离锁来替换独占锁
 *        a)使用读写锁ReadWriteLock能够提升系统的性能。使用读写分离锁来替代独占锁是减小锁粒度的一种特殊状况。
 *        b)减小锁粒度是经过分割数据结构实现的,那么,读写锁则是对系统功能点的分割;
 *        c)在读多写少的场合,使用读写锁能够有效提高系统的并发能力;
 *     四、锁分离
 *
 *     五、锁粗化
 *        虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操做时,便会把全部的锁操做整合成对锁的一次请求,从而减小对锁的请求同步次数,
 *        这个操做叫作锁的粗化。
 *
 *  synchronized和ReetrantLock
 *  1)互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,于是这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程得到的是
 *  独占锁。独占锁意味着其它线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引发线程上下文切换,当有不少线程竞争锁的时候,
 *  会引发CPU频繁的上下文切换致使效率很低。synchronized采用的即是这种并发策略。
 *
 *  2)基于冲突检测的乐观并发策略,通俗地将就是先进性操做,若是没有其它线程争用共享数据,那操做就成功了,若是共享数据被争用,产生了冲突,
 *  那就在进行其它的补偿措施(最多见的补偿措施就是不断的重试,直到试成功为止),这种乐观的并发策略的许多实现都不须要把线程挂起,所以这种
 *  同步被称为非阻塞同步。ReetrantLock采用的即是这种并发策略。在乐观的并发策略中,须要操做和冲突检测这两个步骤具有原子性,它靠硬件指令
 *  来保证,这里用的是CAS操做(Compare and Swap)。
 *
 *  3)基本语法上,ReetrantLock与synchronized很类似,它们都具有同样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互斥锁(Lock),
 *  一个表现为原生语法层面的互斥锁(synchronized)。
 *
 *  《Java并发编程实践》一书给出了使用ReetrantLock的最佳时机:
 *    当你须要如下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操做,公平队列,或者非块结构的锁。不然,请使用synchronized。
 */
public class ReentrantLockTest implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
        Thread thread1 = new Thread(reentrantLockTest);
        Thread thread2 = new Thread(reentrantLockTest);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("[ReentrantLockTest] i=" + i);
    }
}
相关文章
相关标签/搜索