Java并发编程 - 锁Lock取款机场景实例

转自:http://www.verejava.com/?id=17236703718463

锁Lock取款机场景实例

Lock是一个接口,ReentrantLock是它的实现类,下面通过“取款机案例”来剖析它的4个常用方法。

1.爸爸妈妈同时在ATM上登录取款(不加任何锁)

public class Bank {
    private static double money = 10000;

    public void login(Thread currentUserThread) {
        System.out.println(Thread.currentThread().getName() + "  登录进入银行" + "  当前银行余额  : " + money);

    }

    public void logout() {
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额  : " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款  : " + withdrawMoney + "  当前银行余额  : " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动妈妈线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                try {
                    //妈妈登录
                    bank.login(Thread.currentThread());
                    //过5秒取 10000
                    Thread.sleep(5000);
                    bank.withdraw(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        motherThread.start();
    }
}

结果:

妈妈  登录进入银行  当前银行余额  : 10000.0
爸爸  登录进入银行  当前银行余额  : 10000.0
爸爸  取款  : 10000.0  当前银行余额  : 0.0
妈妈 当前银行余额  : 0.0 余额不够

妈妈登录显示银行余额为10000,但是当她取钱时却显示“余额不足”,产生了数据不一致。

2.同一时刻,爸爸或妈妈线程只能有一个能够登录银行取款(获取lock()),另外一个线程需要等待, 直到unlock()释放锁

public class Bank {
    private static double money = 10000;
    private Lock lock = new ReentrantLock();

    public void login(Thread currentUserThread) {
        lock.lock();//登录加锁
        System.out.println(Thread.currentThread().getName() + "  登录进入银行" + "  当前银行余额  : " + money);

    }

    public void logout() {
        lock.unlock();//退出释放锁
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额  : " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款  : " + withdrawMoney + "  当前银行余额  : " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);
                    //爸爸退出
                    bank.logout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动妈妈线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                try {
                    //妈妈登录
                    bank.login(Thread.currentThread());
                    //过5秒取 10000
                    Thread.sleep(5000);
                    bank.withdraw(10000);
                    //妈妈退出
                    bank.logout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        motherThread.start();
    }
}

结果:

爸爸  登录进入银行  当前银行余额  : 10000.0
爸爸  取款  : 10000.0  当前银行余额  : 0.0
爸爸  退出银行
妈妈  登录进入银行  当前银行余额  : 0.0
妈妈 当前银行余额  : 0.0 余额不够
妈妈  退出银行

lock() 和unlock()成对出现,在login(Thread currentUserThread) 登录方法中调用 lock() ,在 logout()退出方法中调用了unlock()。
也就是说,Lock类的锁机制允许在不同的方法中加锁和解锁,而synchronized关键字只能在同一个方法中加锁和解锁。

3. 通过tryLock()判断是否可以获得锁, 能获得锁返回true,否则返回false

public class Bank {
    private static double money = 10000;
    private Lock lock = new ReentrantLock();

    public void login(Thread currentUserThread) {
        //判断是否已经有线程登录
        if (!lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + " 有人已经登录进入银行  请稍等");
        } else {
            System.out.println(Thread.currentThread().getName() + "  登录进入银行" + "  当前银行余额: " + money);
        }

    }

    public void logout() {
        lock.unlock();//退出释放锁
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款: " + withdrawMoney + "  当前银行余额: " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);
                    //爸爸退出
                    bank.logout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动妈妈线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                //妈妈登录
                bank.login(Thread.currentThread());
            }
        };
        motherThread.start();
    }
}

结果:

妈妈 有人已经登录进入银行  请稍等
爸爸  登录进入银行  当前银行余额: 10000.0
爸爸  取款: 10000.0  当前银行余额: 0.0
爸爸  退出银行

4. 通过tryLock(long time,TimeUnit timeUnit)设置一段时间后重新进入 

public class Bank {
    private static double money = 10000;
    private Lock lock = new ReentrantLock();

    public void login(Thread currentUserThread) {
        //如果登录不成功,10秒后再重新尝试获得锁
        try {
            if (!lock.tryLock(10, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + " 有人已经登录进入银行,请稍等");
            } else {
                System.out.println(Thread.currentThread().getName() + "  登录进入银行" + "  当前银行余额: " + money);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public void logout() {
        lock.unlock();//退出释放锁
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款: " + withdrawMoney + "  当前银行余额: " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);
                    //爸爸退出
                    bank.logout();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动妈妈线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                //妈妈登录
                bank.login(Thread.currentThread());
            }
        };
        motherThread.start();
    }
}

结果妈妈10秒后重新尝试得到锁登录进入了银行 :

爸爸  登录进入银行  当前银行余额: 10000.0
爸爸  取款: 10000.0  当前银行余额: 0.0
爸爸  退出银行
妈妈  登录进入银行  当前银行余额: 0.0

5.如果爸爸线程调用 lock() 获得锁以后,没有unlock()释放锁,妈妈线程将会一直等待产生死锁,即使调用 interrupt()中断也没有作用。 

public class Bank {
    private static double money = 10000;
    private Lock lock = new ReentrantLock();

    public void login(Thread currentUserThread) {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 登录进入银行" + "  当前银行余额  : " + money);
    }

    public void logout() {
        lock.unlock();//退出释放锁
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款: " + withdrawMoney + "  当前银行余额: " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动妈妈线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                try {
                    Thread.sleep(1000);
                    bank.login(Thread.currentThread());
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName()+"  登录超时被中断");
                }
            }
        };
        motherThread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        motherThread.interrupt();
    }
}

结果:

妈妈线程一直等待爸爸线程释放锁,结果造成死锁。妈妈线程调用motherThread.interrupt(); 中断也不起做用。

6. 在登录获得锁时,调用lockInterruptibly(),允许中断等待的线程,使motherThread.interrupt()可用。

public class Bank {
    private static double money = 10000;
    private Lock lock = new ReentrantLock();

    public void login(Thread currentUserThread) throws InterruptedException {
        lock.lockInterruptibly();//运行等待线程被中断
        System.out.println(Thread.currentThread().getName() + " 登录进入银行" + "  当前银行余额  : " + money);
    }

    public void logout() {
        lock.unlock();//退出释放锁
        System.out.println(Thread.currentThread().getName() + "  退出银行");
    }

    public double withdraw(double withdrawMoney) {
        if (this.money < withdrawMoney) {
            System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够");
            return 0;
        }
        this.money -= withdrawMoney;
        System.out.println(Thread.currentThread().getName() + "  取款: " + withdrawMoney + "  当前银行余额: " + this.money);
        return withdrawMoney;
    }
}
public class TestLock {

    public static void main(String[] args) {
        final Bank bank = new Bank();

        //启动爸爸线程
        Thread fatherThread = new Thread("爸爸") {
            public void run() {
                try {
                    //爸爸登录
                    bank.login(Thread.currentThread());
                    //过2秒取10000
                    Thread.sleep(2000);
                    bank.withdraw(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        fatherThread.start();

        // 启动 妈妈 线程
        Thread motherThread = new Thread("妈妈") {
            public void run() {
                try {
                    Thread.sleep(1000);
                    bank.login(Thread.currentThread());
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName()+"  登录超时被中断");
                }
            }
        };
        motherThread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        motherThread.interrupt();
    }
}

结果:

爸爸 登录进入银行  当前银行余额  : 10000.0
妈妈  登录超时被中断
爸爸  取款: 10000.0  当前银行余额: 0.0
posted @ 2019-07-20 11:40  Helios_Fz  阅读(532)  评论(0编辑  收藏  举报