java的“锁”事(锁的种类)

Java‘锁’事

1、乐观锁和悲观锁

1.1、悲观锁

//认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加上锁,确保数据不会被别的线程修改,例如:synchronize和Lock的实现锁都是悲观锁
//适合写操作的场景,先加锁可以保证写操作时数据准确

1.2、乐观锁

//认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁
//在java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据
//如果这个数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,比如:放弃修改、重试抢锁等
//判断规则
//版本号机制version 最常采用的是CAS算法,java原子类中的通常操作通过CAS自旋实现的 (比较并交换)

2、8种情况演示锁运行案例

2.1、1-2案例演示

/**
问题:
1.标准访问有ab两个线程,请问先打印邮件还是消息
2.sendEmail()方法中加入暂停3秒,请问先打印邮件还是消息
结论
一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
其他的线程都只能等待,换句话说,某一个时刻,只能有一个线程去访问synchronized方法
 锁的是当前对象this,被锁定后,其他的线程都不能进入当前对象的其它的synchronized方法中
*/
class phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendMsg() {
//        try{
//            TimeUnit.SECONDS.sleep(3);
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        System.out.println("------sendMsg");
    }

    public void hello() {
        System.out.println("----------hello");
    }
}
public class LockBDemo {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone2 = new phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();
        
        new Thread(() -> {
//            phone2.sendMsg();
            phone.sendMsg();
//            phone.hello();
        }, "B").start();

    }
}

2.2、3-4案例演示

/**
问题:
3.添加一个hello()方法,请问先打印邮件还是hello()
4.有两部手机,请问先打印邮件还是消息
结论
加个普通方法后发现和同步锁无关
换成两个对象后,不是同一把锁,情况立刻变化
*/
class phone {
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public synchronized void sendMsg() {
//        try{
//            TimeUnit.SECONDS.sleep(3);
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        System.out.println("------sendMsg");
    }

    public void hello() {
        System.out.println("----------hello");
    }
}

public class LockBDemo {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone2 = new phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();

        new Thread(() -> {
            phone2.sendMsg();
//            phone.sendMsg();
            phone.hello();
        }, "B").start();

    }
}

2.3、5-6案例演示

/**
问题:
5.有两个静态同步方法,有一部手机,请问先打印邮件还是消息
6.有两个静态同步方法,有两部手机,请问先打印邮件还是消息
结论
 三种synchronized锁的有一些差别
 对于普通的同步方法,锁的是当前实例对象,通常是this,具体的一部部手机,所有的普通同步方法用的都是同一把锁 -->实例对象本身
 对于静态同步方法,锁的是当前类的class对象,例如phone.class,唯一的模板对象
 对于同步方法块,锁的是synchronized括号内的对象
*/
class phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public static synchronized void sendMsg() {
//        try{
//            TimeUnit.SECONDS.sleep(3);
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        System.out.println("------sendMsg");
    }

    public void hello() {
        System.out.println("----------hello");
    }
}

public class LockBDemo {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone2 = new phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();

        new Thread(() -> {
            phone2.sendMsg();
            phone.sendMsg();
//            phone.hello();
        }, "B").start();

    }
}

2.4、7-8案例演示

/**
问题:
 有一个静态同步方法,一个普通同步方法,一部手机,请问先打印邮件还是消息
 有一个静态同步方法,一个普通同步方法,两部手机,请问先打印邮件还是消息
结论
  当一个线程试图访问同步代码时,它首先必须得到锁,正常退出或抛出异常时必须释放锁
  
 所有的普通方法同步方法用的都是同一把锁--实例对象本身,就是new出来的具体实例对象本身,本类this
  也就是说明如果一个实例对象的普通同步方法获取锁之后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁
  
  所有的静态同步方法用的也是同一把锁--类对象本身,就是我们说过的唯一模板class
  具体实例对象this和唯一模板class,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞争锁的条件的
  但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁
*/
class phone {
    public static synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("------sendEmail");
    }

    public  synchronized void sendMsg() {
//        try{
//            TimeUnit.SECONDS.sleep(3);
//        }catch (InterruptedException e){
//            e.printStackTrace();
//        }
        System.out.println("------sendMsg");
    }

    public void hello() {
        System.out.println("----------hello");
    }
}

public class LockBDemo {
    public static void main(String[] args) {
        phone phone = new phone();
        phone phone2 = new phone();

        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();

        new Thread(() -> {
            phone2.sendMsg();
            phone.sendMsg();
//            phone.hello();
        }, "B").start();

    }
}
//打印顺序
//------sendMsg
//------sendMsg
//------sendEmail

3、公平锁和非公平锁

3.1、公平锁

3.1.1、概述
  • 公平锁是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人排队。这就是公平锁ReentrantLock lock = new ReentrantLock(true)

3.2、非公平锁

3.2.1、概述
  • 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿状态(某个线程一直得不到锁)。这就是非公平锁ReentrantLock lock = new ReentrantLock()

3.3、代码演示

class Ticket{
    private int number = 50;
    ReentrantLock lock = new ReentrantLock();//ReentrantLock() 非公平锁  ReentrantLock(true) 公平锁

    public void sale(){
        lock.lock();
        try{
            if(number > 0){
                System.out.println(Thread.currentThread().getName() + "卖出第 : \t" +(number --));
            }
        }finally {
           lock.unlock();
        }

    }
}
public class SaleTicketDome {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(() ->{
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        },"t1").start();
        new Thread(() ->{
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        },"t2").start();
        new Thread(() ->{
            for (int i = 0; i < 55; i++) {
                ticket.sale();
            }
        },"t3").start();
    }
}

3.4 问题

  • 为什么默认是非公平锁
    1. 恢复挂起的线程到真正获得锁是有时间差的,从开发人员看这时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显,所以非公平锁更充分的利用CPU的时间片,尽量减少CPU空闲状态时间
    2. 使用多线程很重要的考量点是线程切换的开销,当采用公平锁时,当一个线程请求锁获取同步状态,然后释放到同步状态,所以港释放锁的线程在此刻两次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

4、可重入锁(递归锁)

4.1、概述

  • 可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象是同一个对象)不会因为之前已经获取还没有释放而阻塞
  • ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是一定程度避免死锁。

4.2、可重入锁的种类

4.2.1、synchronized隐式可重入锁
4.2.1.1、synchronized同步代码块及同步方法演示
//同步代码块
public class ReEntryLockDemo {
    public static void main(String[] args) {
        final  Object o = new Object();
        new Thread(() ->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "\t -----外层调用" );
                synchronized (o){
                    System.out.println(Thread.currentThread().getName() + "\t -----中层调用" );
                    synchronized (o){
                        System.out.println(Thread.currentThread().getName() + "\t -----内层调用" );
                    }
                }
            }
        },"t1").start();
    }
}
//输出
//t1	 -----外层调用
//t1	 -----中层调用
//t1	 -----内层调用

//同步方法
public synchronized void  m1(){
        System.out.println(Thread.currentThread().getName() + "\t -----m1()" );
        m2();
        System.out.println(Thread.currentThread().getName() + "\t -----经过m2" );
    }

    private synchronized void m2() {
        System.out.println(Thread.currentThread().getName() + "\t -----m2()" );
        m3();
    }

    private synchronized void m3() {
        System.out.println(Thread.currentThread().getName() + "\t -----m3()" );
    }

    public static void main(String[] args) {
        ReEntryLockDemo lockDemo = new ReEntryLockDemo();
        new Thread(() ->{
            lockDemo.m1();
        },"t1").start();
    }

//输出
//t1	 -----m1()
//t1	 -----m2()
//t1	 -----m3()
//t1	 -----经过m2
4.2.2.2、synchronized可重入锁的实现机制
  • 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
  • 当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1
  • 在目标锁对象的暑假前不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放锁
  • 当monitorexit时,Java虚拟机则需要将锁对象的计数器减1,计数器为零代表锁已被释放。
4.2.2、reentrantLock显式可重入锁
4.2.1.1、reentrantLock演示
public class ReEntryLock {
    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();
        new Thread(() ->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t -----外层调用" );
                lock.lock();
                try{
                    System.out.println(Thread.currentThread().getName() + "\t -----中层调用" );
                }finally {
                    //
                    lock.unlock();
                }
            }finally {
                //由于加锁次数和释放次数不一样,第二个线程始终无法获取锁,导致一直等待
                lock.unlock(); //正常情况下,加锁几次就要解锁几次
            }
        },"t1").start();

        new Thread(() ->{
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t -----外层调用" );
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }
}
//输出
//main	 -----外层调用
//main	 -----中层调用

5、死锁及排查

5.1、概述

  • 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉它们都将无法推进下去。

5.2、死锁代码演示

public class deadLock {
    public static void main(String[] args) {
        final Object o = new Object();
        final Object o1 = new Object();

        new Thread(() -> {
            synchronized (o) {
                System.out.println(Thread.currentThread().getName() + "\t" + "o进来了");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + "\t" + "o1进来了");
                }
            }
        }, "t1").start();
        new Thread(() -> {
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName() + "\t" + "o1进来了");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o) {
                    System.out.println(Thread.currentThread().getName() + "\t" + "o进来了");
                }
            }

        }, "t2").start();
    }
}
5.2.1、死锁查看方式(命令方法)
  1. jps -l
  2. jstack 进程编号

6、写锁(独占锁)/读锁(共享锁)

7、自旋锁SpinLock

8、无锁->独占锁->读写锁->邮戳锁

9、无锁->偏向锁->轻量锁->重量锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值