前言
本片文章介绍了实现线程同步的三种方法
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锁就比较灵活了。