多线程的锁

一、在没有锁的情况下,举例下面的例子
(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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值