多线程的锁

来源:互联网 发布:香港中文大学gpa算法 编辑:程序博客网 时间:2024/04/30 12:30

一、在没有锁的情况下,举例下面的例子
(1)Account类,账户类,包括账户号码和余额

package cn.test.synchronization.one;public class Account {    private String accountNo;    private double balance;    public Account(String accountNo, double balance) {        this.accountNo = accountNo;        this.balance = balance;    }    public String getAccountNo() {        return accountNo;    }    public void setAccountNo(String accountNo) {        this.accountNo = accountNo;    }    public double getBalance() {        return balance;    }    public void setBalance(double balance) {        this.balance = balance;    }    public int hashCode(){        return accountNo.hashCode();    }    public boolean equals(Object obj){        if(obj != null && obj.getClass() == Account.class){            Account target = (Account)obj;            return target.getAccountNo().equals(accountNo);        }        return false;    }}

(2)DrawThread 取钱类,包括账户实体和取钱金额

package cn.test.synchronization.one;public class DrawThread extends Thread{    private Account account;    private double drawAmount;    public DrawThread(String name, Account account,                 double drawAmount){        super(name);        this.account = account;        this.drawAmount = drawAmount;    }    public void run(){        if(account.getBalance() >= drawAmount){            System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);            try {                Thread.sleep(1);            } catch (InterruptedException e) {                e.printStackTrace();            }            //修改余额            account.setBalance(account.getBalance() - drawAmount);            System.out.println("\t 余额为:"+account.getBalance());        }else{            System.out.println(getName() + "取钱失败!余额不足");        }    }}

注意这个类中的run()方法实现了取钱的过程,按道理这不是面向对象的思想在编程,在面向对象中有一个设计模式叫领域驱动模式,强调把类的功能应该放在类里面。

(3)测试类:

package cn.test.synchronization.one;public class TestDraw {    public static void main(String[] args) {        //创建一个账户        Account account = new Account("12345", 1000);        new DrawThread("甲", account, 800).start();        new DrawThread("乙", account, 800).start();    }}

执行出现的结果:

乙取钱成功!吐出钞票:800.0甲取钱成功!吐出钞票:800.0     余额为:200.0     余额为:-600.0

明显,由于run方法线程不安全的问题出现了以上异常的结果。
由此我们引出了线程锁的问题。关于锁的问题,有三种方式:
1、snychronize(){} :括号里面是对竞争资源的锁,这种方式是对属性加锁,而属性就是竞争资源
2、public snychronize void 方法名(){} : 这种是对this进行加锁
3、前面两种锁不能显示地加锁和显示地解锁,而这种方式就能完美的解决。同步锁(Lock),在实现多线程的安全控制中,通常喜欢用ReentrantLock(可重入锁)。使用该所进行显示加锁、显示释放锁。

二、snychronize(){}
(1)Account类,同上
(2)DrawThread,取钱类

package cn.test.synchronization.two;public class DrawThread extends Thread{    private Account account;    private double drawAmount;    public DrawThread(String name, Account account,                 double drawAmount){        super(name);        this.account = account;        this.drawAmount = drawAmount;    }    public void run(){        //synchronized可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等等        synchronized (account) {               if(account.getBalance() >= drawAmount){                System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount);                try {                    Thread.sleep(1);                } catch (InterruptedException e) {                    e.printStackTrace();                }                //修改余额                account.setBalance(account.getBalance() - drawAmount);                System.out.println("\t 余额为:"+account.getBalance());            }else{                System.out.println(getName() + "取钱失败!余额不足");            }        }    }}

(3)测试类,同上

执行的结果是:

甲取钱成功!吐出钞票:800.0     余额为:200.0乙取钱失败!余额不足

显然,没有出现异常。
三、public snychronize void 方法名(){}
(1)Account类,账户类

package cn.test.synchronization.three;public class Account {    private String accountNo;    private double balance;    public Account(String accountNo, double balance) {        this.accountNo = accountNo;        this.balance = balance;    }    public String getAccountNo() {        return accountNo;    }    public void setAccountNo(String accountNo) {        this.accountNo = accountNo;    }    public double getBalance() {        return balance;    }    public int hashCode(){        return accountNo.hashCode();    }    public boolean equals(Object obj){        if(obj != null && obj.getClass() == Account.class){            Account target = (Account)obj;            return target.getAccountNo().equals(accountNo);        }        return false;    }    //将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫    //领域驱动模式    //凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的,    //它是以降低程序的运行效率作为代价,所以我们有以下两点建议:    //1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不    //   进行同步,因为这会影响运行效率    //2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本    ///  这样可以保证性能    public synchronized void draw(double drawAmount){        if(this.getBalance() >= drawAmount){            System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);            try {                Thread.sleep(1);            } catch (InterruptedException e) {                e.printStackTrace();            }            //修改余额            this.balance = this.balance - drawAmount;            System.out.println("\t 余额为:"+this.getBalance());        }else{            System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足");        }    }}

(2)DrawThread,取钱类

package cn.test.synchronization.three;public class DrawThread extends Thread{    private Account account;    private double drawAmount;    public DrawThread(String name, Account account,                 double drawAmount){        super(name);        this.account = account;        this.drawAmount = drawAmount;    }    public void run(){        account.draw(drawAmount);    }}

(3)测试类,同上

运行结果与二相同。

=================================
这里写图片描述

可变类与不可变类讲解
凡事具有两面性,加了synchronized使得可变类更加的安全,但是这种安全是有代价的,它是以降低程序的运行效率作为代价,所以我们有以下两点建议:
1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不进行线程同步,因为这会影响运行效率
2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本,这样可以保证性能

这里写图片描述

前面两种线程同步的方式都无法显示地释放同步监视器,那么什么时候才会自动地释放同步监视器呢?
1、以下情况会自动释放同步监视器:
(1)同步方法或同步代码块已经执行完毕。包括(同步方法或同步代码块执行完最后一行、遇到break跳出同步方法或同步代码块、遇到return终止执行同步方法或同步代码块)
(2)当线程在同步方法或同步代码块遇到未处理的Error或Exception,导致异常结束时会释放同步监视器
(3)同步监视器监视的对象执行wait()方法,释放同步监视器
2、以下情况不会自动释放同步监视器:
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()(线程让步)方法来暂停当前线程的执行、当前线程不会释放同步监视器
(2)线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器

=================================

三、同步锁
(1)Account类,账户类

package cn.test.synchronization.four;import java.util.concurrent.locks.ReentrantLock;public class Account {    //定义锁对象,可重入锁    private final ReentrantLock lock = new ReentrantLock();    private String accountNo;    private double balance;    public Account(String accountNo, double balance) {        this.accountNo = accountNo;        this.balance = balance;    }    public String getAccountNo() {        return accountNo;    }    public void setAccountNo(String accountNo) {        this.accountNo = accountNo;    }    public double getBalance() {        return balance;    }    public int hashCode(){        return accountNo.hashCode();    }    public boolean equals(Object obj){        if(obj != null && obj.getClass() == Account.class){            Account target = (Account)obj;            return target.getAccountNo().equals(accountNo);        }        return false;    }    //将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫    //领域驱动模式    //凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的,    //它是以降低程序的运行效率作为代价,所以我们有以下两点建议:    //1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不    //   进行同步,因为这会影响运行效率    //2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本    ///  这样可以保证性能    public void draw(double drawAmount){        //对同步锁进行加锁        lock.lock();        try{            if(this.getBalance() >= drawAmount){                System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);                try {                    Thread.sleep(1);                } catch (InterruptedException e) {                    e.printStackTrace();                }                //修改余额                this.balance = this.balance - drawAmount;                System.out.println("\t 余额为:"+this.getBalance());            }else{                System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足");            }        }finally {            lock.unlock();        }    }}

(2)DrawThread类,取钱类

package cn.test.synchronization.four;public class DrawThread extends Thread{    private Account account;    private double drawAmount;    public DrawThread(String name, Account account,                 double drawAmount){        super(name);        this.account = account;        this.drawAmount = drawAmount;    }    public void run(){        account.draw(drawAmount);    }}

(3)测试类,同上

===============

避免死锁的几个方法:
·避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

0 0