synchronized锁与lock锁的区别
引言
在学习多线程时,当时为了解决线程并发问题,曾有两种锁,一种是synchronized同步块,同步方法,一种就是Lock锁,那么这两种锁之间有什么区别?谁更好用呢?
synchronized
同步方法(synchronized)
本质:队列+锁
-
由于我们可以通过private关键字来保证数据对象只被方法访问,所以只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
public synchronized void method(int args){}
-
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就会独占该锁,直到该方法返回才释放锁,后面被堵塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
弊端
方法里面需要修改的内容才需要锁,锁得太多,浪费资源
同步块
-
同步块:synchronized(Obj){}
-
Obj称之为 同步监视器
-
obj可以是任何对象,但是推荐使用共享资源作为监视器
-
同步方法中无需指定监视器,因为同步方法的监视器就是this,就是这个对象本身,或者是class
-
-
同步监视器的执行过程
-
第一个线程访问,锁定同步监视器,执行其中代码
-
第二个线程访问,发现同步监视器被锁定,无法访问
-
第一个线程访问完毕,解锁同步监视器
-
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
-
案例展示:
package com.lyc.synchornized;
import java.awt.dnd.DragGestureEvent;
//不安全的取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(700,"奖学金");
Bank MIng = new Bank(account,100,"小明");
Bank Teacher = new Bank(account,50,"老师");
MIng.start();
Teacher.start();
}
}
//账户
class Account{
int money;
String name;
public Account(int money,String name){
this.money=money;
this.name=name;
}
}
//银行:模拟取款
class Bank extends Thread{
private Account account;//账户
private int DrawingMoney;//取了多少钱
private int nowMoney;// 现在手里有多少钱
public Bank(Account accout,int DrawingMoney,String name){
super(name);
this.account = accout;
this.DrawingMoney = DrawingMoney;
}
//取钱
//synchronized 默认锁的是this
//所以要使用同步块
@Override
public void run() {
//锁的对象就是变化的量 需要增删改
synchronized (account){
if (account.money<DrawingMoney){
System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");
return;
}
//sleep可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//判断有没有钱
//卡内余额 = 余额-你取的钱
account.money = account.money - DrawingMoney;
//你手里的钱
nowMoney = nowMoney + DrawingMoney;
System.out.println(account.name+"余额为"+account.money);
//Thread.currentThread().getName() = this.getName()
System.out.println(this.getName()+"手里的钱为"+nowMoney);
}
}
Lock
我们直接去jdk文档中去找
Lock接口实现类主要分为三类:
ReentrantLock(可重入锁【常用】) , ReentrantReadWriteLock.ReadLock (读锁), ReentrantReadWriteLock.WriteLock(写锁)
我们再去源码中看看:
发现在ReentrantLock中还有公平锁与非公平锁之分
公平锁:顾名思义,十分公平,讲究先来后到,不能插队
非公平锁:十分不公平:可以插队(默认)
那么如何使用Lock锁呢?
lock三部曲
1.new ReentrantLock()
2.lock.lock();//加锁
3.lock.unlock();//解锁
代码示例:
package com.lyc.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//@FunctionalInterface 函数式接口,jdk1.8新特性 lambda表达式 (参数)->{代码}
new Thread(() ->
{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
new Thread(() ->
{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
new Thread(() ->
{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
new Thread(() ->
{for (int i = 0; i < 40; i++) ticket.sale();},"D").start();
}
}
//lock三部曲
//1.new ReentrantLock()
//2.lock.lock();//加锁
//3.lock.unlock();//解锁
class Ticket2{
private int number = 50;
//买票的方式
Lock lock = new ReentrantLock();//可重入锁
public void sale() {
lock.lock();//加锁
try {
//业务代码
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余了" + number);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();//解锁
}
}
}
区别(重点)
1.Synchronized是内置的Java关键字,lock是一个Java类
2.Synchronized无法判断锁的状态,Lock可以判断是否获得了锁
3.Synchronized 会自动释放锁(同步块结束就释放了)lock必须手动释放锁,如果不释放锁,就会造成死锁
4.Synchronized 线程1(同步块 阻塞) 线程2(就会一直等待) Lock锁就不会一直等待(try lock()
原因:Lock
实现提供了使用synchronized
方法和语句的附加功能,通过提供非阻塞尝试来获取锁( tryLock()
),尝试获取可被中断的锁( lockInterruptibly()
) ,以及尝试获取可以超时( tryLock(long, TimeUnit)
)。
5.Synchronized 可重入锁,不可中断,非公平 Lock ,可重入锁,可以中断,非公平(可以自己设置)
6.Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码
这就是我总结的两者之间的区别,希望能帮助到大家