Java Synchronized锁和Lock锁实现简单的卖火车票案例

发布时间:2018-08-17作者:laosun阅读(3563)

Java

    Java的线程机制是抢占式的,这表示调度机制会周期性的中断线程,将上下文切换到另一个线程。

    Java线程的优先级设置setPriority():线程是通过映射到原生线程上来实现的,所以线程的调度最终还是取决于操作系统,操作系统的优先级于Java的优先级并不一一对应,如果操作系统的优先级大于Java的优先级,那么设置Java线程的优先级将是管用的,如果小于就不行了,这样会导致即使设置不同的优先级,也不会体现出什么效果, 取决于你的具体平台和JVM实现。至少博主在mac系统上测试是不行的。


    了解synchronized锁 和 lock锁 实现火车票出售的案例之前,我们先来简单的了解一下两种的锁的区别。


    lock锁: 相当于乐观锁,底层实现方式:CAS+volatile

    synchronized: 相当于悲观锁


    这里我们也来解释一下volatile这个关键字是什么意思: 就是使用volatile修饰的变量,会在每次线程修改后,立即刷新同步到主存(物理内存),这就是所谓的可见性。synchronized和lock两种锁都能保证可见性,但是他们都是在释放锁的时候去同步主存。


    这里着重介绍下lock锁的几个重要的方法:

        lock():获取锁

        tryLock():boolean返回值,返回值为true,表示获得锁,返回值为false,则不阻塞,直接跳过。

        tryeLock(time):获取锁等待时间,如果在指定时间内无法获取锁,则不阻塞,直接跳过。    lockInterruptibly():

        lockInterruptibly():在阻塞的时候,可以响应中断, 对线程调用 interrupt()。这点synchronized不具备。

        unlock():释放锁,但是“必须”在finally()块中执行。避免出现死锁。

    另外lock还具备读写锁:

        readlock()和writeLock()

        如果读取同一个本地文件,分别用synchronized和lock进行加锁。 那么synchronized同时只能允许一个线程进行读取。而lock锁是可以允许同时多个线程进行读取。其实读取可以多个线程同时进行,但是更新等操作不可以。

        lock的读写锁中,有几点需要注意的是:

            1:如果A线程占用了读锁,此时如果B线程要申请写锁,那么必须等待A释放读锁之后才能进行申请。

            2:如果A线程占用了写锁,此时如果B线程要申请写锁或者读锁,那么必须等待A释放了写锁之后才能申请。


    synchronized锁 和 lock锁的区别:

        1:lock是接口,而synchronized是java关键字,内置语言实现。

        2:获取锁、释放锁,对于lock来说都是手动操作,而synchronized则是自动的(比如:进入synchronized和跳出就是获取和释放)。

        3:lock锁可以让等待的线程响应中断。

        4:lock可以知道有没有获取到锁。

        5:lock可以提高多个线程的读操作的效率。


    可重入锁:

        synchronized和lock都是可重入锁,就是获取到锁之后,进入对象的其他加锁块,则不需要重新获取锁。

    可中断锁:

        lock可以响应中断,而synchronized不可以。

    公平锁:

        什么是公平锁呢? 就是等待的时间越长,则最先获取锁,synchronized不是公平锁。而lock默认也不是,但是可以设置成公平锁,比如: ReentrantLock lock = new ReentrantLock(true);  

    读写锁:

        lock特性: ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();


    下边看代码实例吧:

    实例说明: 一共有100张票,开通10个窗口进行售票,使用synchronized和lock分别实现如下所示:

    使用synchronized的实现方式:

    package com.sunjs.syn;
    
    class TrainDemo implements Runnable {
    
    	private static int total_count = 100;// 一共一百张火车票
    
    	@Override
    	public void run() {
    		while (true) {
    			if (total_count > 0) {
    				try {
    					// 为了能看出效果,我们在这停歇一百毫秒
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				sale();
    			} else {
    				break;// 票已经没有了,跳出while true循环
    			}
    		}
    		System.out.println(Thread.currentThread().getName() + " 的票已售空");
    	}
    
    	//如果将当前方法声明为static,那么能替换this锁的方式就是 SyncThread.class
    	private void sale() {
    		//synchronized使用的是this对象锁,如果某个线程获得了锁,那么其他线程只可以执行当前对象的非临界区(非加锁程序块)
    		//获得锁的线程在进入下一个加锁临界区(加锁块)则不用再次获取锁,这就是锁的可重如性。
    		//JVM负责跟踪对象被加锁的次数,在任务第一次给对象加锁的时候,计数变成1,每当这个相同的任务在这个对象上获得锁时,计数都会递增(只有首先获得锁的任务才能继续获取锁)
    		//每当任务离开一个synchronized的方法,计数递减,当计数为0时,锁被完全释放,此时别的任务线程就可以使用此资源。
    		synchronized (this) {
    			if (total_count > 0) {
    				System.out.println(Thread.currentThread().getName() + " 卖出 " + (100 - total_count + 1) + " 张票");
    				total_count--;//已经卖出,票的总数减去1
    			}
    		}
    	}
    
    }
    
    public class SyncThread {
    
    	public static void main(String[] args) {
    		TrainDemo td = new TrainDemo();
    		//启动2个窗口进行售卖票
    //		Thread t1 = new Thread(td, "1号窗口");
    //		Thread t2 = new Thread(td, "2号窗口");
    //		t1.start();
    //		t2.start();
    		
    		//启动10个窗口进行售卖票
    		for(int i=1;i<=10;i++){
    			new Thread(td, i+"号窗口").start();;
    		}
    	}
    
    }

    使用lock的实现方式:

    package com.sunjs.syn;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    class TrainDemo2 implements Runnable {
    
    	private static int total_count = 100;// 一共一百张火车票
    	
    	private final ReentrantLock lock = new ReentrantLock(true);
    
    	@Override
    	public void run() {
    		while (true) {
    			if (total_count > 0) {
    				try {
    					// 为了能看出效果,我们在这停歇一百毫秒
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				sale();
    			} else {
    				break;// 票已经没有了,跳出while true循环
    			}
    		}
    		System.out.println(Thread.currentThread().getName() + " 的票已售空");
    	}
    
    	private void sale() {
    		lock.lock();//获取锁
    		try{
    			if (total_count > 0) {
    				System.out.println(Thread.currentThread().getName() + " 卖出 " + (100 - total_count + 1) + " 张票");
    				total_count--;//已经卖出,票的总数减去1
    			}
    		}catch(Exception e){
    			e.printStackTrace();
    		}finally{
    			lock.unlock();//释放锁
    		}
    		
    	}
    
    }
    
    public class LockThread {
    
    	public static void main(String[] args) {
    		TrainDemo2 td = new TrainDemo2();
    		//启动2个窗口进行售卖票
    //		Thread t1 = new Thread(td, "1号窗口");
    //		Thread t2 = new Thread(td, "2号窗口");
    //		t1.start();
    //		t2.start();
    		
    		//启动10个窗口进行售卖票
    		for(int i=1;i<=10;i++){
    			new Thread(td, i+"号窗口").start();;
    		}
    		
    	}
    
    }

    以上两种锁的打印结果大致如下:

    3号窗口 卖出 1 张票
    1号窗口 卖出 2 张票
    7号窗口 卖出 3 张票
    ......
    8号窗口 卖出 95 张票
    4号窗口 卖出 96 张票
    6号窗口 卖出 97 张票
    2号窗口 卖出 98 张票
    5号窗口 卖出 99 张票
    10号窗口 卖出 100 张票
    10号窗口 的票已售空
    1号窗口 的票已售空
    3号窗口 的票已售空
    7号窗口 的票已售空
    9号窗口 的票已售空
    6号窗口 的票已售空
    8号窗口 的票已售空
    5号窗口 的票已售空
    2号窗口 的票已售空
    4号窗口 的票已售空


    敬请期待下几期:

    MySQL 悲观/乐观锁 实现秒杀

    redis 分布式锁 实现秒杀

    zookeeper 分布式锁 实现秒杀



3 +1

版权声明

 Java  源码  多线程

 请文明留言

0 条评论