多线程的线程同步和锁
线程同步
当多个线程访问同一个对象,并且线程还想修改对象,这时候就需要线程同步,线程同步其实就是一个等待机制,多个需要访问此对象的线程进入对象的等待池形成队列,等待前一个线程使用完毕,下一个线程再使用。
- 线程同步的形成条件:队列+锁
队列
- 线程排队。
锁_synchronized隐式定义锁
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 多线程竞争下,加锁,释放锁对导致较多的上下文切换和调度问题,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
- 方法里需要修改的内容才需要锁,锁太多,资源浪费。
- 总之就是性能下降!
public synchronized void method(){}
- 默认的synchronized修饰词锁的是this(类本身)。
- synchronized同步块锁的是任何对象。
怎么判断该锁谁!
package syn;
//不安全取钱
//两个人去银行取钱
//银行需要账户
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(1000,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girl = new Drawing(account,100,"你老婆");
you.start();
girl.start();
}
}
//账户
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
synchronized (account){
//判断有没有钱
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"取光了钱");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
account.money = account.money-drawingMoney;
nowMoney = nowMoney +drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney);
}
}
}
- 首先应该为进行数据增、删、改的地方加锁。
- 上述代码中run方法中加锁的话默认锁住了本类(银行),但银行并没有变化,变化的是账户的钱。
- 这时使用同步代码块,参数放入账户的钱,修改语句放在块里即可锁住。
死锁
- 多个线程互相抱着对方需要的资源,然后形成僵持
package syn;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0, "灰姑娘");
Makeup g2 = new Makeup(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
//需要的资源,只有一份(static保证唯一)
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choise; //选择
String girlName;//使用化妆品的人
Makeup(int choise, String girlName) {
this.choise = choise;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
}
}
//互相持有对方的锁(需要拿到对方的资源)
private void makeup() throws InterruptedException {
if (choise == 0) {
synchronized (lipstick) {
//口红的锁
System.out.println(this.girlName + "获得了口红的锁");
Thread.sleep(1000);
synchronized (mirror) {//一秒钟后获得了镜子
//镜子的锁
System.out.println(this.girlName + "获得了镜子的锁");
}
}
} else {
synchronized (mirror) {
//镜子的锁
System.out.println(this.girlName + "获得了镜子的锁");
Thread.sleep(2000);
synchronized (lipstick) {//一秒钟后获得了口红
//口红的锁
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
}
}
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要解决以上一条即可避免死锁(锁不能嵌套锁!)
Lock锁_显式定义锁
ReentrantLock(可重入锁)类实现了Lock
class A {
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();//上锁
try{
//保证线程安全的代码
}finally{
lock.unlock();//开锁
}
}
}
synchronized与Lock对比
- Lock是显式锁,手动开启后需要关闭,synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
- Lock锁性能更好,JVM花费的调度时间更少,性能和扩展性都更好。
- 优先使用顺序:Lock>synchronized代码块>synchronized方法锁