java锁相关之synchronized关键字概念与使用

java锁相关之synchronized关键字概念与使用

JAVA中锁的概念

  • 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断判断锁是否能够被成功获取,知道获取到锁才会推出循环;
  • 乐观锁:假定没有冲突,在修改数据时如果发现要修改的数据和之前获取的不一致,则重新读取数据,修改后重新尝试修改;
  • 悲观锁:假定会发生冲突,同步所有对数据的相关操作,从读数据就开始上锁;
  • 独享锁(写):给资源上写锁,线程可以修改资源,其他线程不能再加锁;(单写)
  • 共享锁(读):给资源上读锁后,只能读不能改,其他线程只能加读锁,不能加写锁;(多读)
  • 可重入锁与不可重入锁:线程拿到一把锁后,可以自由进入同一把锁所同步的其他代码
  • 公平锁与非公平锁:争取锁的顺序,如果先来后到,则为公平

同步关键字synchronized

相关概念

  • 用于实例方法、静态方法,隐式制定锁对象
  • 用于代码块时,显示制定锁对象
  • 锁的作用于:对象锁、类锁
  • 特性:synchronized是一个可重入、独享、悲观锁
  • 锁优化:
    • 锁消除:(开启锁消除的参数:-XX:+DoEscapeAnalysis -XX:+EliminateLocks)–一般是在循环体或者频繁调用的时会触发JIT编译,进行锁消除,例如:频繁使用StringBuffder.append时
    • 锁粗化:JVM做了锁粗化的优化,但是我们可以从代码层进行优化

栗子

对实例方法的锁定,锁定的是实例对象,当相同对象调用两个方法是,会进行锁定,如果有两个对象同时调用,则不会锁定

	public synchronized void update() {
    	...
    }

等价于

	public void update() {
    	synchronized(this){
    		...
    	}
    }

测试示例:

class Counter {
    // synchronized (this){}
    public synchronized void update() {
        System.out.println("访问数据库1");
        try {
            for (int i = 1; i < 4; i++) {
                System.out.println("啊,我被【" + Thread.currentThread().getName() + "】锁住了" + i + "秒");
                Thread.sleep(1000);
            }
            System.out.println("啊,我被【" + Thread.currentThread().getName() + "】释放了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void updateBlock() {
        synchronized (this) {

            System.out.println("访问数据库2");
            try {
                for (int i = 1; i < 4; i++) {
                    System.out.println("啊,我被【" + Thread.currentThread().getName() + "】锁住了" + i + "秒");
                    Thread.sleep(1000);
                }
                System.out.println("啊,我被【" + Thread.currentThread().getName() + "】释放了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

此时使用的是两个实例进行测试

public static void main(String args[]) {
        Counter ct1 = new Counter();
        Counter ct2 = new Counter();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("T1 method");
                ct1.update();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("T2 method");
                ct2.updateBlock();
            }
        }).start();
    }

输出结果为:
访问数据库1
啊,我被【T1 method】锁住了1秒
访问数据库2
啊,我被【T2 method】锁住了1秒
啊,我被【T1 method】锁住了2秒
啊,我被【T2 method】锁住了2秒
啊,我被【T1 method】锁住了3秒
啊,我被【T2 method】锁住了3秒
啊,我被【T1 method】释放了
啊,我被【T2 method】释放了

将ct2更换为ct1实例后,此时会对实例进行锁定,在T1线程结束后,T1获取的锁会被释放,此时才会被T2线程获取

输出结果为:
访问数据库1
啊,我被【T1 method】锁住了1秒
啊,我被【T1 method】锁住了2秒
啊,我被【T1 method】锁住了3秒
啊,我被【T1 method】释放了
访问数据库2
啊,我被【T2 method】锁住了1秒
啊,我被【T2 method】锁住了2秒
啊,我被【T2 method】锁住了3秒
啊,我被【T2 method】释放了

对静态方法加锁,属于对类进行加锁,多个实例访问方法依然会被锁住

	public static synchronized void update() {
    	...
    }

等价于

	public static void update() {
    	synchronized(Counter.class){
    		...
    	}
    }

测试示例:

class Counter {
    // synchronized (Counter.class)
    public static synchronized void staticUpdate() {
        System.out.println("访问数据库3");
        try {
            for (int i = 1; i < 4; i++) {
                System.out.println("啊,我被【" + Thread.currentThread().getName() + "】锁住了" + i + "秒");
                Thread.sleep(1000);
            }
            System.out.println("啊,我被【" + Thread.currentThread().getName() + "】释放了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void staticUpdateBlock() {
        synchronized (Counter.class) {
            System.out.println("访问数据库4");
            try {
                for (int i = 1; i < 4; i++) {
                    System.out.println("啊,我被【" + Thread.currentThread().getName() + "】锁住了" + i + "秒");
                    Thread.sleep(1000);
                }
                System.out.println("啊,我被【" + Thread.currentThread().getName() + "】释放了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
public static void main(String args[]) {
        Counter ct1 = new Counter();
        Counter ct2 = new Counter();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("T3 method");
                ct1.staticUpdate(); // 此处为了方便与之前的实例方法比较,实际应该用Counter.staticUpdate();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("T4 method");
                ct2.staticUpdateBlock(); // 此处为了方便与之前的实例方法比较,实际应该用Counter.staticUpdate();
            }
        }).start();
    }

输出结果为:
访问数据库3
啊,我被【T3 method】锁住了1秒
啊,我被【T3 method】锁住了2秒
啊,我被【T3 method】锁住了3秒
啊,我被【T3 method】释放了
访问数据库4
啊,我被【T4 method】锁住了1秒
啊,我被【T4 method】锁住了2秒
啊,我被【T4 method】锁住了3秒
啊,我被【T4 method】释放了

总结:

  1. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;
  2. 如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
  3. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
  4. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值