Java-synchronized&lock

Java学习笔记-synchronized&lock

举例:卖票行为,当我们不使用锁来进行卖票行为时,我们会发现得到得结果并不是我们想要得结果

public class SellTicket {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"A").start();
        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"B").start();
        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"C").start();


    }

}

class Ticket{

    private int num = 30;

    public void sell(){

        if (num > 0)
            System.out.println(Thread.currentThread().getName() + "买到了第" + (num --) + "张");
       
}

这个时候我们会发现出现了有多个线程同时操作一个对象的情况,那么这种情况并不是我们所希望出现的情况,我们希望在进行卖票时,这张票只能卖一次,而且卖出去就只属于一个人,而不能出现两个人同时购买到了一张票,那么这时候我们就需要对这个卖票的行为进行一些操作

public class SellTicketSyn {

    public static void main(String[] args) {

        Ticket ticket = new Ticket();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sell();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sell();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sell();
            }
        },"C").start();
    }

}

class Ticket{

    private int num = 30;

    public synchronized void sell(){
        if (num > 0){
            System.out.println(Thread.currentThread().getName() + "买到了第" + (num --) + "张");
        }
    }

}

在上述的卖票中,我们对Ticket类卖票的这个方法加上了synchronized关键字,此时,这个方法就已经被上了锁,也即当某个线程正在进行这个操作时,即获取了这个对象的锁,这个对象一般都是共享资源变量,为了防止多个线程同时操作一个共享资源变量引起冲突而有的操作,当某个线程获取到这个对象的锁之后,其他竞争锁的线程就会进入等待队列中,直至该线程释放锁,则所有处于等待队列的线程继续竞争该锁。

synchronized

那么,synchronized到底是什么呢?

  • synchronized是Java提供的原子性内置锁,这种内置的而且使用者看不到的锁也被称为监视器锁
  • 使用synchronized后,会在编译之后在同步的代码块前后加上monitorenter和monitorexit字节码指令,依赖操作系统底层互斥锁实现
  • 主要实现原子性操作和解决共享变量的内存可见性问题

当执行monitorenter指令时会尝试获取对象锁,如果该对象没有被锁定或是已经获得了锁,那么锁的计数器就会+1,此时,所有竞争锁的线程就会进入等待队列中。

而当执行monitorexit指令时会将计数器-1,当计数器为0时,则锁释放,此时处于等待队列中的线程则重新开始竞争锁。

synchronized是排它锁,也即如果当一个线程获得锁时,其他线程必须等待该线程释放锁才能获得锁,而又因为Java线程与操作系统中原生线程一一对应,线程被唤醒或是阻塞时都会从用户态进到内核态,这种转换是十分消耗性能的。

那么,什么是内存可见性呢?

​ 从内存语义上看,加锁的过程会清除工作内存中的共享变量,并从主内存中读入共享变量,释放锁的过程就是将工作内存中的共享变量写回进主内存中。所以,线程对所有变量的操作都是在工作内存中进行,线程间无法直接互相访问,变量传递需要通过主存完成。那么很有可能就会出现一个问题:线程A从主存中获取了共享变量n并将其修改为1放回主存,此时线程B也从主存获取了共享变量n将其修改为2,但线程A再去获取时,共享变量n仍然是1,这就是共享变量的内存不可见问题,也即线程B写入的值对线程A不可见

synchronized应用方式

  • 修饰实例方法,也就是对该实例对象加锁,进入同步代码块前要获取该实例对象的锁
  • 修饰静态方法,也就是对当前类对象加锁,进入同步代码块前要获取该类对象的锁
  • 修饰代码块,给指定对象加锁,进入同步代码块前要获取该对象的锁

实现上述方式对卖票加锁还有另一种方式:

public class SellTicketLock {

    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();

        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"A").start();
        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"B").start();
        new Thread(()->{for(int i = 0; i < 40; i ++) ticket2.sell();},"C").start();


    }

}

class Ticket2{

    private int num = 30;

    private Lock lock = new ReentrantLock();

    public void sell(){

        // 显示上锁
        lock.lock();
        try {
            if (num > 0)
                System.out.println(Thread.currentThread().getName() + "买到了第" + (num --) + "张");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 必须要手动释放锁,否则死锁
            lock.unlock();
        }
    }

}

synchronized与lock区别

  • synchronized是内置的Java关键字,而lock是一个类
  • synchronized无法判断获取锁的状态,但lock可以判断是否获取了锁
  • synchronized可以自动释放锁,lock必须手动释放锁,不然就会造成死锁
  • synchronized不可中断,lock中的trylock是可以被中断的
  • synchronized和lock都是可重入锁,synchronized是非公平锁,而lock可以通过修改构造方法中参数设置为公平锁
  • synchronized适合锁少量代码同步问题,lock适合锁大量的代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值