Java中的锁实战

一 对某个对象加锁

// 锁的是对象,不是代码
public class T {
    private int count = 10;
    private Object o = new Object();

    public void m() {
        // 任何线程要执行下面的代码,必须先拿到 o 的锁,只有拿到 o 这把锁,才能执行下面这段代码。
        // 一旦 A 线程拿到这把锁,B 线程只有等 A 线程执行完,释放了 o 这把锁,B 再获取这把锁,B 线程才能执行。
        synchronized (o) {
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}

二 用当前对象当锁

public class T {
    private int count = 10;

    public void m() {
        // 锁的是 this
        synchronized (this) { // 任何线程要执行下面的代码,必须先拿到 this 的锁,用当前对象当锁
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
}

三 在方法前加 synchronized

public class T {
    private int count = 10;
    public synchronized void m() { // 等同于在方法代码执行时加 synchronized(this)
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
}

四 静态方法前加 synchronized

public class T {
    private static int count = 10;
    // 锁的是 XX.class
    public synchronized static void m() { // 这里等同于 synchronized(T.class)
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void mm() {
        synchronized (T.class) { // 这里不能写成 synchronized(this)
            count--;
        }
    }
}

五 synchronized 的小测试

1 代码

public class T implements Runnable {
    private /*volatile*/ int count = 100;


    public synchronized void run() { // 必须加 synchronized ,否则两个不同的线程可能会输出相同的值
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void main(String[] args) {
        T t = new T();
        for (int i = 0; i < 100; i++) {
            new Thread(t, "THREAD" + i).start();
        }
    }
}

2 测试

......
THREAD32 count = 9
THREAD68 count = 8
THREAD69 count = 7
THREAD82 count = 6
THREAD55 count = 5
THREAD59 count = 4
THREAD66 count = 3
THREAD62 count = 2
THREAD63 count = 1
THREAD47 count = 0

六 各个线程对应的锁对象必须相同,否则是锁不住的

1 代码

public class T implements Runnable {
    private int count = 10;


    public synchronized void run() {
        count--;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            T t = new T(); // 注意:这里5个线程对应T对象是不同的,所以并不能锁住
            new Thread(t, "THREAD" + i).start();
        }
    }
}

2 测试

THREAD1 count = 9
THREAD2 count = 9
THREAD0 count = 9
THREAD4 count = 9
THREAD3 count = 9

七 同步方法和非同步方法同时执行

1 代码

// 同步方法和非同步方法可以同时调用,是锁不住的
public class T {
    // 同步方法
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName() + " m1 start...");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m1 end");
    }

    // 非同步方法
    public void m2() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " m2 ");
    }

    public static void main(String[] args) {
        T t = new T();
        new Thread(t::m1, "t1").start();
        new Thread(t::m2, "t2").start();
        
      /*
      lambda 表达式写法
      new Thread(()->t.m1(), "t1").start();
      new Thread(()->t.m2(), "t2").start();
      */
      
      /*
      //1.8 之前的写法
      new Thread(new Runnable() {


         @Override
         public void run() {
            t.m1();
         }
         
      });
      */
    }
}

2 测试

t1 m1 start...
t2 m2
t1 m1 end

八 写方法加锁读方法不加锁问题

1 代码

/**
* @className: Account
* @description: 面试题:模拟银行账户
* 对业务写方法加锁
* 对业务读方法不加锁
* 这样实现容易产生脏读问题
* @date: 2021/6/20
*/
import java.util.concurrent.TimeUnit;

public class Account {
    String name;
    double balance;
    // 对业务写方法加锁
    public synchronized void set(String name, double balance) {
        this.name = name;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }

    // 对业务读方法不加锁
    public /*synchronized*/ double getBalance(String name) { // 只有在这里加锁,才不会产生脏读问题
        return this.balance;
    }

    public static void main(String[] args) {
        Account a = new Account();
        new Thread(() -> a.set("zhangsan", 100.0)).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(a.getBalance("zhangsan"));

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(a.getBalance("zhangsan"));
    }
}

2 测试

0.0
100.0

九 可重入锁

1 代码

/**
* @className: T
* @description: 可重入锁
* 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁
* 也就是说 synchronized 获得的锁是可重入的
* @date: 2021/6/20
*/
import java.util.concurrent.TimeUnit;

public class T {
   synchronized void m1() {
      System.out.println("m1 start");
      try {
         TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      m2();
      System.out.println("m1 end");
   }
   
   synchronized void m2() {
      try {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("m2");
   }

   public static void main(String[] args) {
      new T().m1();
   }
}

2 测试

m1 start
m2
m1 end

十 父子类可重入锁,它们锁的都是 this 对象

1 代码

/**
* @className: T
* @description: 父子类相同方法是可重入加锁
* @date: 2021/6/20
*/
import java.util.concurrent.TimeUnit;

public class T {
    // 父类 m 方法是对 TT 对象加锁,是可重入锁
    synchronized void m() {
        System.out.println("m start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        new TT().m();
    }
}

class TT extends T {
    // 子类 m 方法是对 TT 对象加锁
    @Override
    synchronized void m() {
        System.out.println("child m start");
        super.m();
        System.out.println("child m end");
    }
}

2 测试

child m start
m start
m end
child m end

十一 锁和异常

1 代码

/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
* 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
* 因此要非常小心的处理同步业务逻辑中的异常
*/
import java.util.concurrent.TimeUnit;

public class T {
    int count = 0;
    synchronized void m() {
        System.out.println(Thread.currentThread().getName() + " start");
        while (true) {
            count++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 5) {
                int i = 1 / 0; // 此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        T t = new T();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        };
        new Thread(r, "t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r, "t2").start();
    }
}

2 测试

t1 start
t1 count = 1
t1 count = 2
t1 count = 3
t1 count = 4
t1 count = 5
t2 start
t2 count = 6
Exception in thread "t1" java.lang.ArithmeticException: / by zero
    at com.mashibing.juc.c_011.T.m(T.java:27)
    at com.mashibing.juc.c_011.T$1.run(T.java:38)
    at java.lang.Thread.run(Thread.java:748)
t2 count = 7
t2 count = 8
t2 count = 9
t2 count = 10

十二 小结

1 JDK 早期的是重量级的,它要到OS底层申请锁。

2 锁升级

当 sync (Object)

第1步:markword 在 Object 记录这个线程 ID ,这个机制是偏向锁。

第2步:如果发生线程争用:升级为自旋锁。

第3步:旋10次以后,升级为重量级锁,到 OS 申请锁。

3 锁的使用场景

执行时间短(加锁代码),线程数少,用自旋锁(占CPU,不占用OS)。

执行时间长,线程数多,用重量级锁。(不占CPU,在等待队列中)

锁只能升级,不能降级。

4 synchronized(Object)

不能用 String 常量 Integer Long

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值