线程同步的方法

线程同步的三种方法:

1.同步代码块
2.同步方法
3.同步锁
public class Account {
    private String accountNo;
    private double balance;
    public Account(){}

    public Account(String accountNo,double balance){
        this.accountNo = accountNo;
        this.balance = balance;
    }
//省略get和set方法...
}
//1.同步代码块
public class DrawThread extends Thread{
    private Account account;
    private double drawAmount;

    DrawThread(String name,Account account,double drawAmount){
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    public void run(){
        //使用account作为同步监视器,任何线程进入下面同步代码块之前
        //必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
        //这种做法符合:“加锁-->修改-->释放锁”的逻辑
        synchronized (account) {
        if (account.getBalance() >= drawAmount) {
            System.out.println(getName()+
                "取钱成功!吐出钞票:"+drawAmount);
//          try {
//              Thread.sleep(1);
//          } catch (InterruptedException e) {
//              // TODO Auto-generated catch block
//              e.printStackTrace();
//          }
            account.setBalance(account.getBalance()-drawAmount);
            System.out.println("\t余额为: "+account.getBalance());
        }
        else {
            System.out.println(getName()+"取钱失败!余额不足!");
        }
    }//同步代码块结束,该线程释放同步锁
    }
}

上面是用同步代码块来实现多线程安全,下面是另一种方法:同步方法。同步方法的同步监视器是this,也就是对象本身。具体就是:只要将Account类对balance的访问设置成线程安全的,那么只要把balance的方法修改成同步方法即可

//2.同步方法
/*public class Account{
    //省略Filed和其他方法
    //.....
    public synchronized void draw(double drawAmount){
        if (balance >= drawAmount) {
            //吐钞操作
        }
        else {
            //取钱失败
        }
    }
}*/

上面程序为Account类增加了一个代表取钱的draw()方法,并使用了synchronized关键字修饰该方法,使其变为同步方法。同步方法的同步监视器是this,因此对于同一个Account账户而言,任意时刻只能有一个线程获得对Account对象的锁定,然后进入draw()方法执行取钱操作。这样就保证了多个线程的并发取钱的线程安全。

public class DrawTest {
    public static void main(String[] args) {
        Account acct = new Account("1234567",1000);
        new DrawThread("甲方", acct, 800).start();
        new DrawThread("乙方", acct, 800).start();
    }
}

多次运行上面的程序都可能看到:
/*甲方取钱成功!吐出钞票:800.0
乙方取钱成功!吐出钞票:800.0
余额为: 200.0
余额为: -600.0
*/
//这样的错误结果,但是也有可能看到正确结果,这正是多线程编程的“偶然错误”,因为线程调度的不确定性,假设系统调度器在被注释掉的地方暂停,让另一个线程执行那么就会出现上面的错误(为了强制暂停,只要取消注释,将总能看到错误结果)

之所以出现这样的结果,是因为run() 方法的方法体不具有同步安全性————程序中有两个并发线程在修改Account对象;而且系统恰好在注释掉的代码处执行线程切换,切换给另一个修改Account对象的线程,所以就出现了问题。(主要就是取决于线程的切换时机)

3.同步锁

public class X {
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    //定义需要保证线程安全的方法
    public void m(){
        //加锁
        lock.lock();
        try {
            //需要保证线程安全的代码
            //...method body
        } catch (Exception e) {
        }finally{
            lock.unlock();
        }
    }
}

使用ReentrantLock对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。
使用Lock与使用同步方法有点类似,只是使用Lock时显示使用Lock对象作为同步锁,而使用同步方法时系统隐式使用当前对象作为同步监视器,同样都符合“加锁-->修改-->释放锁”的操作模式,而且使用Lock对象时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象同一时刻只能有一个线程能进入临界区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值