Java 多线程(四)——线程同步(synchronized、ReentrantLock)

  同步,是指协同步调,按预定的先后次序进行运行。而不是从字面上理解的“一起工作”。

1 一个线程安全问题

  银行取钱问题:使用两个线程来模拟两个人对同一账户取钱操作。

package thread;

/**
 * Created by Zen9 on 2016/3/9.
 */
public class DrawTest {
    public static void main(String[] args) {
        //创建一个账户
        Account account = new Account(1000);
        //模拟两个线程对同一个账户取钱
        new DrawThread("A",account,800).start();
        new DrawThread("B",account,800).start();
    }
}

//账户
class Account{
    //余额
    private double balance;

    public Account(double balance){
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }

    public void draw(double drawAmount){
        if (balance >= drawAmount){
            System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount);
            try{
                Thread.sleep(1);
            }catch (InterruptedException ex){
                ex.printStackTrace();
            }
            balance -= drawAmount;
            System.out.println("余额为:" + balance);
        }else {
            System.out.println(Thread.currentThread().getName() + "余额不足!");
        }
    }
}

//取钱线程
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;
    }

    @Override
    public void run() {
        account.draw(drawAmount);
    }
}

运行上面程序,有可能出现如下结果,这是不希望出现的错误:
这里写图片描述

2 synchronized关键字实现同步

  使用对象互斥锁来保证共享数据操作的完整性。每个对象都对应一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只有一个线程访问该对象。使用synchronized关键字与对象互斥锁联系。

2.1 同步代码块

  Java多线程引入同步监视器来解决多线程并发访问问题:

synchronized(obj)
{
  //同步代码块
  ...
}

  其中,obj就是同步监视器。线程开始执行同步代码之前,必须先获得对同步监视器的锁定,即获得对象的互斥锁。
  同步监视器的目的是阻止多个线程对统一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

  改写上面的DrawThread类的run()方法为同步代码块:

//取钱线程
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;
    }

    @Override
    public void run() {
        //使用account作为同步监视器,任何线程进入同步代码之前
        //必须先获得account对象的锁定,其他线程无法获得锁
        // 逻辑:“加锁、修改、释放锁”
        synchronized (account) {    //修改之处///
            account.draw(drawAmount);
        }
    }
}

这里写图片描述

2.2 同步方法

  synchronized关键字还可以放到方法声明中,表示整个方法为同步方法,例如:

//账户
class Account{
    //余额
    private double balance;

    public Account(double balance){
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }

    //synchronized修饰方法
    synchronized public void draw(double drawAmount){   //修改之处///
        if (balance >= drawAmount){
            System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount);
            try{
                Thread.sleep(1);
            }catch (InterruptedException ex){
                ex.printStackTrace();
            }
            balance -= drawAmount;
            System.out.println("余额为:" + balance);
        }else {
            System.out.println(Thread.currentThread().getName() + "余额不足!");
        }
    }
}

//取钱线程
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;
    }

    @Override
    public void run() {
            account.draw(drawAmount);
    }
}

  运行结果与synchronized修饰代码块一样。

  对于synchronized修饰的实例方法(非static方法),无须显示指定同步监视器,同步方法的同步监视器是this,即调用该方法的对象。

2.3 释放监视器的锁定

  线程进入同步代码块(方法)之前,会对同步监视器加锁。程序无法显示释放对同步监视器的锁定,当出现如下情况时会释放:

  • 同步方法(代码块)执行结束时
  • 在同步方法(代码块)中遇到break、return等终止语句
  • 在同步方法(代码块)中出现了未处理的Error或Exception,导致异常结束
  • 在同步方法(代码块)中执行了同步监视器对象的wait()方法,当前线程暂停,释放同步监视器

线程不会释放同步监视器的情况:

  • 线程执行同步方法(代码块)时,程序调用了Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
  • 线程执行同步方法(代码块)时,其他线程调用了该线程的suspend()方法将该线程挂起

3 同步锁(Lock对象)实现同步

  同步锁由Lock对象充当,通过显示定义同步锁对象来实现同步。
  Lock是控制多个线程对共享资源进行访问的工具,比较常用的是ReentrantLock(可重入锁),该Lock对象可以显式地加锁、释放锁。
  ReentrantLock锁具有可重入性,即一个线程可以对已被加锁的ReentrantLock锁再次加锁。

改写上面Account类:

//账户
class Account{

    private double balance;

    public Account(double balance){
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }

    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();  //修改之处///

    public void draw(double drawAmount){
        //加锁
        lock.lock();  //修改之处
        try{
            if (balance >= drawAmount){
                System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount);
                try{
                    Thread.sleep(1);
                }catch (InterruptedException ex){
                    ex.printStackTrace();
                }
                balance -= drawAmount;
                System.out.println("余额为:" + balance);
            }else {
                System.out.println(Thread.currentThread().getName() + "余额不足!");
            }
        }finally {
            //释放锁
            lock.unlock();  //修改之处///
        }
    }
}

  使用Lock与使用synchronized同步代码块相似,同样符合“加锁、修改、解锁”的逻辑。只是Lock是显式使用Lock对象作为同步锁,而synchronized同步代码块是使用当前对象作为同步监视器。

4 死锁

  当两(多)个线程互相等待对方释放资源(同步监视器)时就会发生死锁。一旦发生死锁,整个程序既不会发生任何异常,也不会出现任务提示,只是所有线程都处于阻塞状态,无法继续。

例如:

package thread;

/**
 * Created by Zen9 on 2016/3/9.
 */
public class DeadLockTest {
    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();
        new Thread(deadLock).start();
        deadLock.init();
    }
}

class DeadLock implements Runnable{
    A a = new A();
    B b = new B();
    public void init(){
        Thread.currentThread().setName("主线程");
        a.Amethod(b);
        System.out.println("进入主线程之后");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("副线程");
        b.Bmethod(a);
        System.out.println("进入副线程后");
    }
}

class A{
    //进入该方法前,对A实例加锁
    synchronized public void Amethod(B b){
        System.out.println(Thread.currentThread().getName() + " 进入A实例的Amethod方法");
        try {
            Thread.sleep(200);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 企图调用B的B_another_method方法");

        //视图调用B实例的方法,但B实例已被锁定
        b.B_another_method();
    }

    synchronized public void A_another_method(){
        System.out.println("进入A类的A_another_method方法");
    }
}

class B{
    //进入该方法时,对B实例加锁
    synchronized public void Bmethod(A a){
        System.out.println(Thread.currentThread().getName() + " 进入B实例的Bmethod方法");
        try {
            Thread.sleep(200);
        }catch (InterruptedException ex){
            ex.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 企图调用A的A_another_method方法");

        //视图调用A实例的方法,但A实例已被锁定
        a.A_another_method();
    }

    synchronized public void B_another_method(){
        System.out.println("进入B类的B_another_method方法");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值