Java 中的Synchronized 和 ReentrantLock

1 篇文章 0 订阅

Java 中的Synchronized 和 ReentrantLock

在java中,解决同步问题,很多时候都会使用到synchronized和Lock,这两者都是在多线程并发时候常使用的锁机制

Synchronized 是属于JVM层面的锁,相当于如果我们给某个方法或者变量增加Synchronized关键字,这个方法或者变量的锁的管理就会由系统来自动管理

synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问。

使用方式

  1. synchronized关键字修饰方法。public synchronized void save(){}  synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

  2. synchronized关键字修饰代码块。synchronized(object){}  同步是一种高开销的操作,因此应该尽量减少同步的内容。 通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

案例1

public class ThreadTest extends Thread {
    private int threadNo;

    public static void main(String[] args) {
        for (int i = 1; i < 5; i++) {
            new ThreadTest(i).start();
        }
    }

    public ThreadTest(int threadNo) {
        this.threadNo = threadNo;
    }

    public void run() {
        synchronized (ThreadTest.this) {
            for (int i = 0; i < 10; i++) {
                System.out.println("No." + threadNo + ":" + i);
            }
        }
    }
}

运行结果

No.2:0
No.2:1
No.2:2
No.2:3
No.2:4
No.2:5
No.2:6
No.2:7
No.2:8
No.2:9
No.4:0
No.4:1
No.1:0
No.3:0
No.1:1
No.1:2
No.1:3
No.1:4
No.1:5
No.1:6
No.1:7
No.1:8
No.1:9
No.4:2
No.3:1
No.4:3
No.3:2
No.3:3
No.3:4
No.4:4
No.3:5
No.4:5
No.3:6
No.4:6
No.3:7
No.4:7
No.3:8
No.4:8
No.3:9
No.4:9

可以看到多个线程还是报数没有顺序,为什么呢?因为 synchronized (ThreadTest.this) {} 每一个线程都持有当前Thread的this,各自为政治,还是没有统一一个对象或者标准来衡量。相当于秦之前没有统一度量衡。

下面秦朝统一了一个度量衡,叫做Object lock;

案例2

public class ThreadTest extends Thread {
    private int threadNo;
    private static Object lock = new Object();
    public static void main(String[] args) {
        for (int i = 1; i < 5; i++) {
            new ThreadTest(i).start();
        }
    }

    public ThreadTest(int threadNo) {
        this.threadNo = threadNo;
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 0; i < 10; i++) {
                System.out.println("No." + threadNo + ":" + i);
            }
        }
    }
}

运行结果

No.1:0
No.1:1
No.1:2
No.1:3
No.1:4
No.1:5
No.1:6
No.1:7
No.1:8
No.1:9
No.4:0
No.4:1
No.4:2
No.4:3
No.4:4
No.4:5
No.4:6
No.4:7
No.4:8
No.4:9
No.3:0
No.3:1
No.3:2
No.3:3
No.3:4
No.3:5
No.3:6
No.3:7
No.3:8
No.3:9
No.2:0
No.2:1
No.2:2
No.2:3
No.2:4
No.2:5
No.2:6
No.2:7
No.2:8
No.2:9

此时,不论是哪个线程,都在使用同一个对象锁lock进行操作,谁获取到这个锁,谁来报数。可以看到此时的报数规则是:一个线程报数完毕后,另一个线程进行报数。

这其中就涉及到了synchronized锁的管理,synchronized锁的管理是通过JVM来管理的。

ReentrantLock:
ReentranLock是可重入锁(是当前线程,直接获取到锁。实现可重入性。),正因为有可重入行,才分为公平锁(去可重入性)和非公平锁(保持可重入性)
公平锁:

多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,这样就保证了队列中的第一个先得到锁。

优点:所有的线程都能得到资源,不会饿死在队列中。

缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:

多个线程不按照申请锁的顺序去获得锁,而是同时直接去尝试获取锁(插队),获取不到(插队失败),再进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。

优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。

缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。

构造方法:
ReentrantLock lock = new ReentrantLock(false);// 非公平锁
ReentrantLock lock = new ReentrantLock(true);// 公平锁
怎么理解公平锁和不公平锁?
案例3

public class FairLocked implements Runnable {
	private int seatNumber = 100;
	/**
	 * 公平锁实现 ReentrantLock构造方法中设置为true:代表公平锁
	 * 
	 * 设置为false:代表非公平锁 默认也是非公平锁
	 * 
	 */
	/** private ReentrantLock lock = new ReentrantLock(true); */
 
	/** private ReentrantLock lock = new ReentrantLock(false); */
	private ReentrantLock lock = new ReentrantLock();
 
	@Override
	public void run() {
		while (true) {
			try {
				lock.lock();
				if (seatNumber > 0) {
					Thread.sleep(100);
					--seatNumber;
					System.out.println(Thread.currentThread().getName() + "占用1个座位,还剩余 " + seatNumber + "个座位");
				} else {
					System.out.println(Thread.currentThread().getName() + ":不好意思,票卖完了!");
					break;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
 
				lock.unlock();
			}
		}
	}
 
	public static void main(String[] args) {
		FairLocked rlbr = new FairLocked();
		Thread t1 = new Thread(rlbr, "A窗口");
		Thread t2 = new Thread(rlbr, "B窗口");
		t1.start();
		t2.start();
	}
}
B窗口占用1个座位,还剩余 99个座位
B窗口占用1个座位,还剩余 98个座位
B窗口占用1个座位,还剩余 97个座位
B窗口占用1个座位,还剩余 96个座位
B窗口占用1个座位,还剩余 95个座位
B窗口占用1个座位,还剩余 94个座位
B窗口占用1个座位,还剩余 93个座位
B窗口占用1个座位,还剩余 92个座位
B窗口占用1个座位,还剩余 91个座位
B窗口占用1个座位,还剩余 90个座位
B窗口占用1个座位,还剩余 89个座位
B窗口占用1个座位,还剩余 88个座位
B窗口占用1个座位,还剩余 87个座位
B窗口占用1个座位,还剩余 86个座位
B窗口占用1个座位,还剩余 85个座位
B窗口占用1个座位,还剩余 84个座位
B窗口占用1个座位,还剩余 83个座位
B窗口占用1个座位,还剩余 82个座位
B窗口占用1个座位,还剩余 81个座位
B窗口占用1个座位,还剩余 80个座位
B窗口占用1个座位,还剩余 79个座位
B窗口占用1个座位,还剩余 78个座位
B窗口占用1个座位,还剩余 77个座位
B窗口占用1个座位,还剩余 76个座位
B窗口占用1个座位,还剩余 75个座位
B窗口占用1个座位,还剩余 74个座位
B窗口占用1个座位,还剩余 73个座位
B窗口占用1个座位,还剩余 72个座位
B窗口占用1个座位,还剩余 71个座位
B窗口占用1个座位,还剩余 70个座位
B窗口占用1个座位,还剩余 69个座位
B窗口占用1个座位,还剩余 68个座位
B窗口占用1个座位,还剩余 67个座位
B窗口占用1个座位,还剩余 66个座位
B窗口占用1个座位,还剩余 65个座位
B窗口占用1个座位,还剩余 64个座位
B窗口占用1个座位,还剩余 63个座位
B窗口占用1个座位,还剩余 62个座位
B窗口占用1个座位,还剩余 61个座位
B窗口占用1个座位,还剩余 60个座位
B窗口占用1个座位,还剩余 59个座位
B窗口占用1个座位,还剩余 58个座位
B窗口占用1个座位,还剩余 57个座位
B窗口占用1个座位,还剩余 56个座位
B窗口占用1个座位,还剩余 55个座位
B窗口占用1个座位,还剩余 54个座位
B窗口占用1个座位,还剩余 53个座位
B窗口占用1个座位,还剩余 52个座位
B窗口占用1个座位,还剩余 51个座位
B窗口占用1个座位,还剩余 50个座位
B窗口占用1个座位,还剩余 49个座位
B窗口占用1个座位,还剩余 48个座位
B窗口占用1个座位,还剩余 47个座位
B窗口占用1个座位,还剩余 46个座位
B窗口占用1个座位,还剩余 45个座位
B窗口占用1个座位,还剩余 44个座位
B窗口占用1个座位,还剩余 43个座位
B窗口占用1个座位,还剩余 42个座位
B窗口占用1个座位,还剩余 41个座位
B窗口占用1个座位,还剩余 40个座位
B窗口占用1个座位,还剩余 39个座位
B窗口占用1个座位,还剩余 38个座位
B窗口占用1个座位,还剩余 37个座位
B窗口占用1个座位,还剩余 36个座位
B窗口占用1个座位,还剩余 35个座位
B窗口占用1个座位,还剩余 34个座位
B窗口占用1个座位,还剩余 33个座位
B窗口占用1个座位,还剩余 32个座位
B窗口占用1个座位,还剩余 31个座位
B窗口占用1个座位,还剩余 30个座位
B窗口占用1个座位,还剩余 29个座位
B窗口占用1个座位,还剩余 28个座位
B窗口占用1个座位,还剩余 27个座位
B窗口占用1个座位,还剩余 26个座位
B窗口占用1个座位,还剩余 25个座位
B窗口占用1个座位,还剩余 24个座位
B窗口占用1个座位,还剩余 23个座位
B窗口占用1个座位,还剩余 22个座位
B窗口占用1个座位,还剩余 21个座位
B窗口占用1个座位,还剩余 20个座位
B窗口占用1个座位,还剩余 19个座位
B窗口占用1个座位,还剩余 18个座位
B窗口占用1个座位,还剩余 17个座位
B窗口占用1个座位,还剩余 16个座位
B窗口占用1个座位,还剩余 15个座位
B窗口占用1个座位,还剩余 14个座位
B窗口占用1个座位,还剩余 13个座位
B窗口占用1个座位,还剩余 12个座位
B窗口占用1个座位,还剩余 11个座位
B窗口占用1个座位,还剩余 10个座位
B窗口占用1个座位,还剩余 9个座位
B窗口占用1个座位,还剩余 8个座位
B窗口占用1个座位,还剩余 7个座位
B窗口占用1个座位,还剩余 6个座位
B窗口占用1个座位,还剩余 5个座位
B窗口占用1个座位,还剩余 4个座位
B窗口占用1个座位,还剩余 3个座位
B窗口占用1个座位,还剩余 2个座位
B窗口占用1个座位,还剩余 1个座位
B窗口占用1个座位,还剩余 0个座位
B窗口:不好意思,票卖完了!
A窗口:不好意思,票卖完了!

此时构建了一个不公平锁来进行线程管理,根据打印结果我们理解不公平锁:

  1. 非公平锁是无序的锁竞争,刚释放锁的线程很大程度上能比较快的获取到锁,B刚刚释放了锁,很打概率获取到锁,对于CPU的消耗相对于公平锁开销更小
  2. 导致A窗口饿死,如果A能竞争到资源,可能会打印一些数据。

公平锁是严格的以FIFO的方式进行锁的竞争,但是非公平锁是无序的锁竞争,刚释放锁的线程很大程度上能比较快的获取到锁,队列中的线程只能等待,所以非公平锁可能会有“饥饿”的问题。但是重复的锁获取能减小线程之间的切换,而公平锁则是严格的线程切换,这样对操作系统的影响是比较大的,所以非公平锁的吞吐量是大于公平锁的,这也是为什么JDK将非公平锁作为默认的实现。

如果将 ReentrantLock 设置 为公平锁,看看结果

A窗口占用1个座位,还剩余 99个座位
B窗口占用1个座位,还剩余 98个座位
A窗口占用1个座位,还剩余 97个座位
B窗口占用1个座位,还剩余 96个座位
A窗口占用1个座位,还剩余 95个座位
B窗口占用1个座位,还剩余 94个座位
A窗口占用1个座位,还剩余 93个座位
B窗口占用1个座位,还剩余 92个座位
A窗口占用1个座位,还剩余 91个座位
B窗口占用1个座位,还剩余 90个座位
A窗口占用1个座位,还剩余 89个座位
B窗口占用1个座位,还剩余 88个座位
A窗口占用1个座位,还剩余 87个座位
B窗口占用1个座位,还剩余 86个座位
A窗口占用1个座位,还剩余 85个座位
B窗口占用1个座位,还剩余 84个座位
A窗口占用1个座位,还剩余 83个座位
B窗口占用1个座位,还剩余 82个座位
A窗口占用1个座位,还剩余 81个座位
B窗口占用1个座位,还剩余 80个座位
A窗口占用1个座位,还剩余 79个座位
B窗口占用1个座位,还剩余 78个座位
A窗口占用1个座位,还剩余 77个座位
B窗口占用1个座位,还剩余 76个座位
A窗口占用1个座位,还剩余 75个座位
B窗口占用1个座位,还剩余 74个座位
A窗口占用1个座位,还剩余 73个座位
B窗口占用1个座位,还剩余 72个座位
A窗口占用1个座位,还剩余 71个座位
B窗口占用1个座位,还剩余 70个座位
A窗口占用1个座位,还剩余 69个座位
B窗口占用1个座位,还剩余 68个座位
A窗口占用1个座位,还剩余 67个座位
B窗口占用1个座位,还剩余 66个座位
A窗口占用1个座位,还剩余 65个座位
B窗口占用1个座位,还剩余 64个座位
A窗口占用1个座位,还剩余 63个座位
B窗口占用1个座位,还剩余 62个座位
A窗口占用1个座位,还剩余 61个座位
B窗口占用1个座位,还剩余 60个座位
A窗口占用1个座位,还剩余 59个座位
B窗口占用1个座位,还剩余 58个座位
A窗口占用1个座位,还剩余 57个座位
B窗口占用1个座位,还剩余 56个座位
A窗口占用1个座位,还剩余 55个座位
B窗口占用1个座位,还剩余 54个座位
A窗口占用1个座位,还剩余 53个座位
B窗口占用1个座位,还剩余 52个座位
A窗口占用1个座位,还剩余 51个座位
B窗口占用1个座位,还剩余 50个座位
A窗口占用1个座位,还剩余 49个座位
B窗口占用1个座位,还剩余 48个座位
A窗口占用1个座位,还剩余 47个座位
B窗口占用1个座位,还剩余 46个座位
A窗口占用1个座位,还剩余 45个座位
B窗口占用1个座位,还剩余 44个座位
A窗口占用1个座位,还剩余 43个座位
B窗口占用1个座位,还剩余 42个座位
A窗口占用1个座位,还剩余 41个座位
B窗口占用1个座位,还剩余 40个座位
A窗口占用1个座位,还剩余 39个座位
B窗口占用1个座位,还剩余 38个座位
A窗口占用1个座位,还剩余 37个座位
B窗口占用1个座位,还剩余 36个座位
A窗口占用1个座位,还剩余 35个座位
B窗口占用1个座位,还剩余 34个座位
A窗口占用1个座位,还剩余 33个座位
B窗口占用1个座位,还剩余 32个座位
A窗口占用1个座位,还剩余 31个座位
B窗口占用1个座位,还剩余 30个座位
A窗口占用1个座位,还剩余 29个座位
B窗口占用1个座位,还剩余 28个座位
A窗口占用1个座位,还剩余 27个座位
B窗口占用1个座位,还剩余 26个座位
A窗口占用1个座位,还剩余 25个座位
B窗口占用1个座位,还剩余 24个座位
A窗口占用1个座位,还剩余 23个座位
B窗口占用1个座位,还剩余 22个座位
A窗口占用1个座位,还剩余 21个座位
B窗口占用1个座位,还剩余 20个座位
A窗口占用1个座位,还剩余 19个座位
B窗口占用1个座位,还剩余 18个座位
A窗口占用1个座位,还剩余 17个座位
B窗口占用1个座位,还剩余 16个座位
A窗口占用1个座位,还剩余 15个座位
B窗口占用1个座位,还剩余 14个座位
A窗口占用1个座位,还剩余 13个座位
B窗口占用1个座位,还剩余 12个座位
A窗口占用1个座位,还剩余 11个座位
B窗口占用1个座位,还剩余 10个座位
A窗口占用1个座位,还剩余 9个座位
B窗口占用1个座位,还剩余 8个座位
A窗口占用1个座位,还剩余 7个座位
B窗口占用1个座位,还剩余 6个座位
A窗口占用1个座位,还剩余 5个座位
B窗口占用1个座位,还剩余 4个座位
A窗口占用1个座位,还剩余 3个座位
B窗口占用1个座位,还剩余 2个座位
A窗口占用1个座位,还剩余 1个座位
B窗口占用1个座位,还剩余 0个座位
A窗口:不好意思,票卖完了!
B窗口:不好意思,票卖完了!

可以看出,AB线程是非常有序的,就是进行排队执行!线程A释放完锁后,线程B获取到锁进行打印,等待100ms,如此这样循环

参考

  1. https://www.cnblogs.com/moxiaotao/p/9767963.html
  2. https://blog.csdn.net/weixin_39309402/article/details/106466843
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值