线程同步方案


前言

本片文章介绍了实现线程同步的三种方法
1、同步代码块
2、同步方法
3、Lock锁


一、线程同步是什么?

线程同步就是让多个线程实现先后依次访问共享资源,这样就解决了安全问题,它最常见的方案就是加锁。
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后解锁,然后其他线程才能再加锁进来。

二、方式一:同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

同步锁的注意事项:
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug
对于实例方法建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象

同步代码块是如何实现线程安全的?
对出现问题的核心代码使用synchronized进行加锁
每次只能一个线程占锁进入访问

代码如下:

/*
同步代码块
    把访问共享资源的核心代码给上锁,以此保证线程安全

格式
    synchronized(同步锁){
        访问共享资源的核心代码
    }

原理
    每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行

注意
    1、对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug注意
    2、同步锁不建议随便使用一个唯一的对象,也能锁住,但可能影响无关线程, 建议使用共享资源作为锁对象
        对于实例方法建议使用this作为锁对象规范
        对于静态方法建议使用字码(类名.class) 对象作为锁对象
*/
public class Account {

    //账户余额
    private Integer balance = 100000;

    //取钱
    public void drawMoney(Integer money) {
        //0.获取线程名称
        String threadName = Thread.currentThread().getName();

        synchronized (this) {
            //1. 判断余额是否充足
            if (money > balance) {
                System.out.println(threadName + "取钱失败,余额不足");
                return;//方法结束
            }

            //2. 如果够,出钱
            System.out.println(Thread.currentThread().getName() + "取钱成功");

            //3. 更新余额
            balance -= money;
            System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
        }
    }
}

三、方式二:同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理:
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

同步方法是如何保证线程安全的?
对出现问题的核心方法使用synchronized修饰
每次只能一个线程占锁进入访问

代码如下:


/*
同步方法
    把访问共享资源的核心方法给上锁,以此保证线程安全。

格式
    修饰符 synchronized 返回值类型 方法名称(形参列表) {
    	操作共享资源的代码
    }

原理
    每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
    同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
    如果方法是实例方法:同步方法默认用this作为的锁对象。
    如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
*/
public class Account {

    //账户余额
    private Integer balance = 100000;

    //取钱
    public synchronized void drawMoney(Integer money) {
        //0.获取线程名称
        String threadName = Thread.currentThread().getName();

        //1. 判断余额是否充足
        if (money > balance) {
            System.out.println(threadName + "取钱失败,余额不足");
            return;//方法结束
        }

        //2. 如果够,出钱
        System.out.println(Thread.currentThread().getName() + "取钱成功");

        //3. 更新余额
        balance -= money;
        System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
    }
}


/*
测试类
*/
public class Demo {

    public static void main(String[] args) {
        //1. 创建一个账户对象
        Account account = new Account();

        //2. 创建两个取钱的人,并把账户交给它
        Person person1 = new Person(account);
        Person person2 = new Person(account);

        //3. 启动2个线程
        person1.start();
        person2.start();

    }
}
//取钱人
public class Person extends Thread {

    //账户
    private Account account;
    public Person(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        //调用取钱的方法
        account.drawMoney(100000);
    }
}

四、方式三:Lock锁

Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

Lock的常用方法
void lock() 获得锁
void unlock() 释放锁

获得Lock锁的实现类对象
private final Lock lock= new ReentrantLock();

代码如下:



import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
Lock锁概述
    Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
    Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建锁对象

方法
    public ReentrantLock() 创建锁对象
    public void lock()    上锁
    public void unlock() 释放锁

Lock锁使用规范
    规范1、锁对象创建在成员位置,使用final修饰
    规范2、释放锁的代码写在finally块中
*/
public class Account {

    //账户余额
    private Integer balance = 100000;
    private final Lock lock= new ReentrantLock();

    //取钱
    public void drawMoney(Integer money) {

        //0.获取线程名称
        String threadName = Thread.currentThread().getName();

        try {
            lock.lock();
            //1. 判断余额是否充足
            if (money > balance) {
                System.out.println(threadName + "取钱失败,余额不足");
                return;//方法结束
            }
            //2. 如果够,出钱
            System.out.println(Thread.currentThread().getName() + "取钱成功");
            //3. 更新余额
            balance -= money;
            System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
        }catch (Exception e){
            System.out.println(e);
        }finally {
            lock.unlock();
        }


    }
}


/*
测试类
*/
public class Demo {

    public static void main(String[] args) {
        //1. 创建一个账户对象
        Account account = new Account();

        //2. 创建两个取钱的人,并把账户交给它
        Person person1 = new Person(account);
        Person person2 = new Person(account);

        //3. 启动2个线程
        person1.start();
        person2.start();

    }
}


//取钱人
public class Person extends Thread {

    //账户
    private Account account;
    public Person(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        //调用取钱的方法
        account.drawMoney(100000);
    }
}

注意:
使用lock锁最好用try catch finally 捕获异常,因为一旦代码出现异常,在finally 中也会释放锁的。


总结

是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。
而对于lock锁就比较灵活了。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值