多线程的线程同步和锁

线程同步

当多个线程访问同一个对象,并且线程还想修改对象,这时候就需要线程同步,线程同步其实就是一个等待机制,多个需要访问此对象的线程进入对象的等待池形成队列,等待前一个线程使用完毕,下一个线程再使用。

  • 线程同步的形成条件:队列+锁

队列

  • 线程排队。

锁_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对比

  1. Lock是显式锁,手动开启后需要关闭,synchronized是隐式锁,出了作用域自动释放。
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁。
  3. Lock锁性能更好,JVM花费的调度时间更少,性能和扩展性都更好。
  4. 优先使用顺序:Lock>synchronized代码块>synchronized方法锁