精华内容
下载资源
问答
  • Java 的锁公平锁和非公平锁概念如何创建两者区别题外话可重入锁和递归锁ReentrantLock概念代码作用可重入锁验证证明ReentrantLock自旋锁概念优缺点手写自旋锁独占锁(写锁) / 共享锁(读锁) / 互斥锁概念为什么会...

    公平锁和非公平锁

    概念

    公平锁
    是指多个线程按照申请锁的顺序来获取锁,类似于排队买饭,先来后到,先来先服务,就是公平的,也就是队列

    非公平锁
    是指多个线程获取锁的顺序,并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的线程(也就是某个线程一直得不到锁)

    如何创建

    并发包中ReentrantLock的创建可以指定析构函数的boolean类型来得到公平锁或者非公平锁,默认是非公平锁

    /**
    * 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁
    */
    Lock lock = new ReentrantLock(true);
    

    两者区别

    公平锁:就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列中的第一个,就占用锁,否者就会加入到等待队列中,以后安装FIFO的规则从队列中取到自己

    非公平锁: 非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

    题外话

    Java ReenttrantLock通过构造函数指定该锁是否公平,默认是非公平锁,因为非公平锁的优点在于吞吐量比公平锁大,对于synchronized而言,也是一种非公平锁.

    可重入锁和递归锁ReentrantLock

    概念

    可重入锁就是递归锁
    指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

    也就是说:线程可以进入任何一个它已经拥有的锁所同步的代码块

    ReentrantLock / Synchronized 就是一个典型的可重入锁.

    代码

    可重入锁就是,在一个method1方法中加入一把锁,方法2也加锁了,那么他们拥有的是同一把锁.

    public synchronized void method1() {
    	method2();
    }
    
    public synchronized void method2() {
    
    }
    

    也就是说我们只需要进入method1后,那么它也能直接进入method2方法,因为他们所拥有的锁,是同一把。

    作用

    可重入锁的最大作用就是避免死锁

    可重入锁验证

    证明Synchronized

    /**
     * 可重入锁(也叫递归锁)
     * 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
     *
     * 也就是说:`线程可以进入任何一个它已经拥有的锁所同步的代码块`
     * @author: 轻狂书生FS
     * @create: 2020-04-15-12:12
     */
    
    /**
     * 资源类
     */
    class Phone {
    
        /**
         * 发送短信
         * @throws Exception
         */
        public synchronized void sendSMS() throws Exception{
            System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
    
            // 在同步方法中,调用另外一个同步方法
            sendEmail();
        }
    
        /**
         * 发邮件
         * @throws Exception
         */
        public synchronized void sendEmail() throws Exception{
            System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()");
        }
    }
    public class ReenterLockDemo {
    
    
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            // 两个线程操作资源列
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    phone.sendSMS();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "t2").start();
        }
    }
    

    在这里,我们编写了一个资源类phone,拥有两个加了synchronized的同步方法,分别是sendSMS 和 sendEmail,我们在sendSMS方法中,调用sendEmail。最后在主线程同时开启了两个线程进行测试,最后得到的结果为:

    t1	 invoked sendSMS()
    t1	 invoked sendEmail()
    t2	 invoked sendSMS()
    t2	 invoked sendEmail()
    

    这就说明当 t1 线程进入sendSMS的时候,拥有了一把锁,同时t2线程无法进入,直到t1线程拿着锁,执行了sendEmail 方法后,才释放锁,这样t2才能够进入

    t1	 invoked sendSMS()      t1线程在外层方法获取锁的时候
    t1	 invoked sendEmail()    t1在进入内层方法会自动获取锁
    
    t2	 invoked sendSMS()      t2线程在外层方法获取锁的时候
    t2	 invoked sendEmail()    t2在进入内层方法会自动获取锁
    

    证明ReentrantLock

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 资源类
     */
    class Phone implements Runnable{
    
        Lock lock = new ReentrantLock();
    
        /**
         * set进去的时候,就加锁,调用set方法的时候,能否访问另外一个加锁的set方法
         */
        public void getLock() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t get Lock");
                setLock();
            } finally {
                lock.unlock();
            }
        }
    
        public void setLock() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t set Lock");
            } finally {
                lock.unlock();
            }
        }
    
        @Override
        public void run() {
            getLock();
        }
    }
    public class ReenterLockDemo {
    
    
        public static void main(String[] args) {
            Phone phone = new Phone();
    
            /**
             * 因为Phone实现了Runnable接口
             */
            Thread t3 = new Thread(phone, "t3");
            Thread t4 = new Thread(phone, "t4");
            t3.start();
            t4.start();
        }
    }
    

    现在我们使用ReentrantLock进行验证,首先资源类实现了Runnable接口,重写Run方法,里面调用get方法,get方法在进入的时候,就加了锁

        public void getLock() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t get Lock");
                setLock();
            } finally {
                lock.unlock();
            }
        }
    

    然后在方法里面,又调用另外一个加了锁的setLock方法

        public void setLock() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t set Lock");
            } finally {
                lock.unlock();
            }
        }
    

    最后输出结果我们能发现,结果和加synchronized方法是一致的,都是在外层的方法获取锁之后,线程能够直接进入里层.

    t3	 get Lock
    t3	 set Lock
    t4	 get Lock
    t4	 set Lock
    

    当我们在getLock方法加两把锁会是什么情况呢? (阿里面试)

        public void getLock() {
            lock.lock();
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t get Lock");
                setLock();
            } finally {
                lock.unlock();
                lock.unlock();
            }
        }
    

    得到结果

    t3	 get Lock
    t3	 set Lock
    

    也就是说程序直接卡死,线程不能出来,也就说明我们申请几把锁,最后需要解除几把锁

    当我们只加一把锁,但是用两把锁来解锁的时候,又会出现什么情况呢?

        public void getLock() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t get Lock");
                setLock();
            } finally {
                lock.unlock();
                lock.unlock();
            }
        }
    

    这个时候,运行程序会直接报错

    t3	 get Lock
    t3	 set Lock
    t4	 get Lock
    t4	 set Lock
    Exception in thread "t3" Exception in thread "t4" java.lang.IllegalMonitorStateException
    	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    	at com.moxi.interview.study.thread.Phone.getLock(ReenterLockDemo.java:52)
    	at com.moxi.interview.study.thread.Phone.run(ReenterLockDemo.java:67)
    	at java.lang.Thread.run(Thread.java:745)
    java.lang.IllegalMonitorStateException
    	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
    	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
    	at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
    	at com.moxi.interview.study.thread.Phone.getLock(ReenterLockDemo.java:52)
    	at com.moxi.interview.study.thread.Phone.run(ReenterLockDemo.java:67)
    	at java.lang.Thread.run(Thread.java:745)
    

    自旋锁

    概念

    自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

    原来提到的比较并交换,底层使用的就是自旋,自旋就是多次尝试,多次访问,不会阻塞的状态就是自旋。

    优缺点

    优点:循环比较获取直到成功为止,没有类似于wait的阻塞

    缺点:当不断自旋的线程越来越多的时候,会因为执行while循环不断的消耗CPU资源

    手写自旋锁

    通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到

    /**
     * 手写一个自旋锁
     *
     * 循环比较获取直到成功为止,没有类似于wait的阻塞
     *
     * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
     * @author: 轻狂书生FS
     * @create: 2020-04-15-15:46
     */
    public class SpinLockDemo {
    
        // 现在的泛型装的是Thread,原子引用线程
        AtomicReference<Thread>  atomicReference = new AtomicReference<>();
    
        public void myLock() {
            // 获取当前进来的线程
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "\t come in ");
    
            // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
            while(!atomicReference.compareAndSet(null, thread)) {
    
            }
        }
    
        /**
         * 解锁
         */
        public void myUnLock() {
    
            // 获取当前进来的线程
            Thread thread = Thread.currentThread();
    
            // 自己用完了后,把atomicReference变成null
            atomicReference.compareAndSet(thread, null);
    
            System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
        }
    
        public static void main(String[] args) {
    
            SpinLockDemo spinLockDemo = new SpinLockDemo();
    
            // 启动t1线程,开始操作
            new Thread(() -> {
    
                // 开始占有锁
                spinLockDemo.myLock();
    
    
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 开始释放锁
                spinLockDemo.myUnLock();
    
            }, "t1").start();
    
    
            // 让main线程暂停1秒,使得t1线程,先执行
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 1秒后,启动t2线程,开始占用这个锁
            new Thread(() -> {
    
                // 开始占有锁
                spinLockDemo.myLock();
                // 开始释放锁
                spinLockDemo.myUnLock();
    
            }, "t2").start();
    
        }
    }
    

    最后输出结果

    t1	 come in 
    .....五秒后.....
    t1	 invoked myUnlock()
    t2	 come in 
    t2	 invoked myUnlock()
    

    首先输出的是 t1 come in

    然后1秒后,t2线程启动,发现锁被t1占有,所有不断的执行 compareAndSet方法,来进行比较,直到t1释放锁后,也就是5秒后,t2成功获取到锁,然后释放。

    独占锁(写锁) / 共享锁(读锁) / 互斥锁

    概念

    独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁

    共享锁:指该锁可以被多个线程锁持有

    对ReentrantReadWriteLock其读锁是共享,其写锁是独占

    写的时候只能一个人写,但是读的时候,可以多个人同时读

    为什么会有写锁和读锁

    原来我们使用ReentrantLock创建锁的时候,是独占锁,也就是说一次只能一个线程访问,但是有一个读写分离场景,读的时候想同时进行,因此原来独占锁的并发性就没这么好了,因为读锁并不会造成数据不一致的问题,因此可以多个人共享读

    多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行,
    但是如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
    
    • 读-读:能共存
    • 读-写:不能共存
    • 写-写:不能共存

    代码实现

    实现一个读写缓存的操作,假设开始没有加锁的时候,会出现什么情况

    /**
     * 读写锁
     * 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
     * 但是,如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
     *
     * @author: 轻狂书生FS
     * @create: 2020-04-15-16:59
     */
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    
    /**
     * 资源类
     */
    class MyCache {
    
        private volatile Map<String, Object> map = new HashMap<>();
        // private Lock lock = null;
    
        /**
         * 定义写操作
         * 满足:原子 + 独占
         * @param key
         * @param value
         */
        public void put(String key, Object value) {
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        }
    
        public void get(String key) {
            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
        }
    
    
    }
    public class ReadWriteLockDemo {
    
        public static void main(String[] args) {
    
            MyCache myCache = new MyCache();
            // 线程操作资源类,5个线程写
            for (int i = 0; i < 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.put(tempInt + "", tempInt +  "");
                }, String.valueOf(i)).start();
            }
            // 线程操作资源类, 5个线程读
            for (int i = 0; i < 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.get(tempInt + "");
                }, String.valueOf(i)).start();
            }
        }
    }
    

    我们分别创建5个线程写入缓存

            // 线程操作资源类,5个线程写
            for (int i = 0; i < 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.put(tempInt + "", tempInt +  "");
                }, String.valueOf(i)).start();
            }
    

    5个线程读取缓存,

            // 线程操作资源类, 5个线程读
            for (int i = 0; i < 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.get(tempInt + "");
                }, String.valueOf(i)).start();
            }
    

    最后运行结果:

    0	 正在写入:0
    4	 正在写入:4
    3	 正在写入:3
    1	 正在写入:1
    2	 正在写入:2
    0	 正在读取:
    1	 正在读取:
    2	 正在读取:
    3	 正在读取:
    4	 正在读取:
    2	 写入完成
    4	 写入完成
    4	 读取完成:null
    0	 写入完成
    3	 读取完成:null
    0	 读取完成:null
    1	 写入完成
    3	 写入完成
    1	 读取完成:null
    2	 读取完成:null
    

    我们可以看到,在写入的时候,写操作都没其它线程打断了,这就造成了,还没写完,其它线程又开始写,这样就造成数据不一致

    解决方法

    上面的代码是没有加锁的,这样就会造成线程在进行写入操作的时候,被其它线程频繁打断,从而不具备原子性,这个时候,我们就需要用到读写锁来解决了

    /**
    * 创建一个读写锁
    * 它是一个读写融为一体的锁,在使用的时候,需要转换
    */
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    

    当我们在进行写操作的时候,就需要转换成写锁

    // 创建一个写锁
    rwLock.writeLock().lock();
    
    // 写锁 释放
    rwLock.writeLock().unlock();
    

    当们在进行读操作的时候,在转换成读锁

    // 创建一个读锁
    rwLock.readLock().lock();
    
    // 读锁 释放
    rwLock.readLock().unlock();
    

    这里的读锁和写锁的区别在于,写锁一次只能一个线程进入,执行写操作,而读锁是多个线程能够同时进入,进行读取的操作

    完整代码:

    /**
     * 读写锁
     * 多个线程 同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
     * 但是,如果一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写
     *
     * @author: 轻狂书生FS
     * @create: 2020-03-15-16:59
     */
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * 资源类
     */
    class MyCache {
    
        /**
         * 缓存中的东西,必须保持可见性,因此使用volatile修饰
         */
        private volatile Map<String, Object> map = new HashMap<>();
    
        /**
         * 创建一个读写锁
         * 它是一个读写融为一体的锁,在使用的时候,需要转换
         */
        private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        /**
         * 定义写操作
         * 满足:原子 + 独占
         * @param key
         * @param value
         */
        public void put(String key, Object value) {
    
            // 创建一个写锁
            rwLock.writeLock().lock();
    
            try {
    
                System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
    
                try {
                    // 模拟网络拥堵,延迟0.3秒
                    TimeUnit.MILLISECONDS.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                map.put(key, value);
    
                System.out.println(Thread.currentThread().getName() + "\t 写入完成");
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 写锁 释放
                rwLock.writeLock().unlock();
            }
        }
    
        /**
         * 获取
         * @param key
         */
        public void get(String key) {
    
            // 读锁
            rwLock.readLock().lock();
            try {
    
                System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
    
                try {
                    // 模拟网络拥堵,延迟0.3秒
                    TimeUnit.MILLISECONDS.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                Object value = map.get(key);
    
                System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 读锁释放
                rwLock.readLock().unlock();
            }
        }
    
        /**
         * 清空缓存
         */
        public void clean() {
            map.clear();
        }
    
    
    }
    public class ReadWriteLockDemo {
    
        public static void main(String[] args) {
    
            MyCache myCache = new MyCache();
    
            // 线程操作资源类,5个线程写
            for (int i = 1; i <= 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.put(tempInt + "", tempInt +  "");
                }, String.valueOf(i)).start();
            }
    
            // 线程操作资源类, 5个线程读
            for (int i = 1; i <= 5; i++) {
                // lambda表达式内部必须是final
                final int tempInt = i;
                new Thread(() -> {
                    myCache.get(tempInt + "");
                }, String.valueOf(i)).start();
            }
        }
    }
    

    运行结果:

    1	 正在写入:1
    1	 写入完成
    2	 正在写入:2
    2	 写入完成
    3	 正在写入:3
    3	 写入完成
    4	 正在写入:4
    4	 写入完成
    5	 正在写入:5
    5	 写入完成
    2	 正在读取:
    3	 正在读取:
    1	 正在读取:
    4	 正在读取:
    5	 正在读取:
    2	 读取完成:2
    1	 读取完成:1
    4	 读取完成:4
    3	 读取完成:3
    5	 读取完成:5
    

    从运行结果我们可以看出,写入操作是一个一个线程进行执行的,并且中间不会被打断,而读操作的时候,是同时5个线程进入,然后并发读取操作。

    展开全文
  • java的锁机制

    千次阅读 2017-02-13 13:53:18
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了...

    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。

    众所周知,在Java多线程编程中,一个非常重要的方面就是线程的同步问题。
    关于线程的同步,一般有以下解决方法:

    1. 在需要同步的方法的方法签名中加入synchronized关键字。

    2. 使用synchronized块对需要进行同步的代码段进行同步。

    3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。

    另外,为了解决多个线程对同一变量进行访问时可能发生的安全性问题,我们不仅可以采用同步机制,更可以通过JDK 1.2中加入的ThreadLocal来保证更好的并发性。

    本篇中,将详细的讨论Java多线程同步机制,并对ThreadLocal做出探讨。

    大致的目录结构如下:

    一、线程的先来后到——问题的提出:为什么要有多线程同步?Java多线程同步的机制是什么?
    二、给我一把锁,我能创造一个规矩——传统的多线程同步编程方法有哪些?他们有何异同?
    三、Lock来了,大家都让开—— Java并发框架中的Lock详解。
    四、你有我有全都有—— ThreadLocal如何解决并发安全性?
    五、总结——Java线程安全的几种方法对比。


    一、线程的先来后到

    我们来举一个Dirty的例子:某餐厅的卫生间很小,几乎只能容纳一个人如厕。为了保证不受干扰,如厕的人进入卫生间,就要锁上房门。我们可以把卫生间想 象成是共享的资源,而众多需要如厕的人可以被视作多个线程。假如卫生间当前有人占用,那么其他人必须等待,直到这个人如厕完毕,打开房门走出来为止。这就 好比多个线程共享一个资源的时候,是一定要分出先来后到的。

    有人说:那如果我没有这道门会怎样呢?让两个线程相互竞争,谁抢先了,谁就可以先干活,这样多好阿?但是我们知道:如果厕所没有门的话,如厕的人一起涌向 厕所,那么必然会发生争执,正常的如厕步骤就会被打乱,很有可能会发生意想不到的结果,例如某些人可能只好被迫在不正确的地方施肥……

    正是因为有这道门,任何一个单独进入如厕的人都可以顺利的完成他们的如厕过程,而不会被干扰,甚至发生以外的结果。这就是说,如厕的时候要讲究先来后到。


    那么在Java 多线程程序当中,当多个线程竞争同一个资源的时候,如何能够保证他们不会产生“打架”的情况呢?有人说是使用同步机制。没错,像上面这个例子,就是典型的 同步案例,一旦第一位开始如厕,则第二位必须等待第一位结束,才能开始他的如厕过程。一个线程,一旦进入某一过程,必须等待正常的返回,并退出这一过程, 下一个线程才能开始这个过程。这里,最关键的就是卫生间的门。其实,卫生间的门担任的是资源锁的角色,只要如厕的人锁上门,就相当于获得了这个锁,而当他 打开锁出来以后,就相当于释放了这个锁。

    也就是说,多线程的线程同步机制实际上是靠锁的概念来控制的。那么在Java程序当中,锁是如何体现的呢?


    让我们从JVM的角度来看看锁这个概念:

    在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:
    1)保存在堆中的实例变量
    2)保存在方法区中的类变量

    这两类数据是被所有线程共享的。
    (程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)

    在java虚拟机中,每个对象和类在逻辑上都是和一个监视器相关联的。
    对于对象来说,相关联的监视器保护对象的实例变量。

    对于类来说,监视器保护类的类变量。

    (如果一个对象没有实例变量,或者一个类没有变量,相关联的监视器就什么也不监视。) 
    为了实现监视器的排他性监视能力,java虚拟机为每一个对象和类都关联一个锁。代表任何时候只允许一个线程拥有的特权。线程访问实例变量或者类变量不需锁。

    但是如果线程获取了锁,那么在它释放这个锁之前,就没有其他线程可以获取同样数据的锁了。(锁住一个对象就是获取对象相关联的监视器)

    类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。

    一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

    java编程人员不需要自己动手加锁,对象锁是java虚拟机内部使用的。

    在java程序中,只需要使用synchronized块或者synchronized方法就可以标志一个监视区域。当每次进入一个监视区域时,java 虚拟机都会自动锁上对象或者类。

    看到这里,我想你们一定都疲劳了吧?o(∩_∩)o...哈哈。让我们休息一下,但是在这之前,请你们一定要记着:
    当一个有限的资源被多个线程共享的时候,为了保证对共享资源的互斥访问,我们一定要给他们排出一个先来后到。而要做到这一点,对象锁在这里起着非常重要的作用。

    在上一篇中,我们讲到了多线程是如何处理共享资源的,以及保证他们对资源进行互斥访问所依赖的重要机制:对象锁。



    本篇中,我们来看一看传统的同步实现方式以及这背后的原理。



    很多人都知道,在Java多线程编程中,有一个重要的关键字,synchronized。但是很多人看到这个东西会感到困惑:“都说同步机制是通过对象锁来实现的,但是这么一个关键字,我也看不出来Java程序锁住了哪个对象阿?“


    没错,我一开始也是对这个问题感到困惑和不解。不过还好,我们有下面的这个例程:

    1. public class ThreadTest extends Thread {   
    2.     private int threadNo;   
    3.     public ThreadTest(int threadNo) {   
    4.         this.threadNo = threadNo;   
    5.     }   
    6.     public static void main(String[] args) throws Exception {   
    7.         for (int i = 1; i < 10; i++) {   
    8.            new ThreadTest(i).start();   
    9.             Thread.sleep(1);   
    10.         }   
    11.      }   
    12.     
    13.     @Override  
    14.      public synchronized void run() {   
    15.         for (int i = 1; i < 10000; i++) {   
    16.             System.out.println("No." + threadNo + ":" + i);   
    17.         }   
    18.      }   
    19.  }   

          这个程序其实就是让10个线程在控制台上数数,从1数到9999。理想情况下,我们希望看到一个线程数完,然后才是另一个线程开始数数。但是这个程序的执行过程告诉我们,这些线程还是乱糟糟的在那里抢着报数,丝毫没有任何规矩可言。
         但是细心的读者注意到:run方法还是加了一个synchronized关键字的,按道理说,这些线程应该可以一个接一个的执行这个run方法才对阿。
         但是通过上一篇中,我们提到的,对于一个成员方法加synchronized关键字,这实际上是以这个成员方法所在的对象本身作为对象锁。在本例中,就是 以ThreadTest类的一个具体对象,也就是该线程自身作为对象锁的。一共十个线程,每个线程持有自己 线程对象的那个对象锁。这必然不能产生同步的效果。换句话说,如果要对这些线程进行同步,那么这些线程所持有的对象锁应当是共享且唯一的! 

    我们来看下面的例程:

    1. public class ThreadTest2 extends Thread {   
    2.  private int threadNo; private String lock;   
    3.  public ThreadTest2(int threadNo, String lock) {   
    4.   this.threadNo = threadNo;   
    5.      this.lock = lock;   }   
    6. public static void main(String[] args) throws Exception {   
    7.    String lock = new String("lock");   
    8.      for (int i = 1; i < 10; i++) {     
    9.   new ThreadTest2(i, lock).start();   
    10.       Thread.sleep(1);   
    11.      }   
    12.   }     
    13. public void run() {     
    14.  synchronized (lock) {   
    15.       for (int i = 1; i < 10000; i++) {   
    16.        System.out.println("No." + threadNo + ":" + i);   
    17.     }      
    18.  }     
    19.  }   
    20.  }  

     

            我们注意到,该程序通过在main方法启动10个线程之前,创建了一个String类型的对象。并通过ThreadTest2的构造函数,将这个对象赋值 给每一个ThreadTest2线程对象中的私有变量lock。根据Java方法的传值特点,我们知道,这些线程的lock变量实际上指向的是堆内存中的 同一个区域,即存放main函数中的lock变量的区域。
            程序将原来run方法前的synchronized关键字去掉,换用了run方法中的一个synchronized块来实现。这个同步块的对象锁,就是 main方法中创建的那个String对象。换句话说,他们指向的是同一个String类型的对象,对象锁是共享且唯一的!

    于是,我们看到了预期的效果:10个线程不再是争先恐后的报数了,而是一个接一个的报数。

    再来看下面的例程:

    1 public class ThreadTest3 extends Thread {   

    •  2    

       

    •  
    •  3     private int threadNo;   
    •  4     private String lock;   
    •  5    
    •  6     public ThreadTest3(int threadNo) {   
    •  7         this.threadNo = threadNo;   
    •  8     }   
    •  9    
    • 10     public static void main(String[] args) throws Exception {   
    • 11        
    • 12         for (int i = 1; i < 20; i++) {   
    • 13             new ThreadTest3(i).start();   
    • 14             Thread.sleep(1);   
    • 15         }   
    • 16     }   
    • 17    
    • 18     public static synchronized void abc(int threadNo) {   
    • 19         for (int i = 1; i < 10000; i++) {   
    • 20               
    • 21                 System.out.println("No." + threadNo + ":" + i);           
    • 22         }   
    • 23     }   
    • 24    
    • 25     public void run() {   
    • 36         abc(threadNo);   
    • 27     }   
    • 28 }  

      细心的读者发现了:这段代码没有使用main方法中创建的String对象作为这10个线程的线程锁。而是通过在run方法中调用本线程中一个静态的同步 方法abc而实现了线程的同步。我想看到这里,你们应该很困惑:这里synchronized静态方法是用什么来做对象锁的呢?



      我们知道,对于同步静态方法,对象锁就是该静态放发所在的类的Class实例,由于在JVM中,所有被加载的类都有唯一的类对象,具体到本例,就是唯一的 ThreadTest3.class对象。不管我们创建了该类的多少实例,但是它的类实例仍然是一个!



      这样我们就知道了:

      1、对于同步的方法或者代码块来说,必须获得对象锁才能够进入同步方法或者代码块进行操作;


      2、如果采用method级别的同步,则对象锁即为method所在的对象,如果是静态方法,对象锁即指method所在的
      Class对象(唯一);


      3、对于代码块,对象锁即指synchronized(abc)中的abc;


      4、因为第一种情况,对象锁即为每一个线程对象,因此有多个,所以同步失效,第二种共用同一个对象锁lock,因此同步生效,第三个因为是
      static因此对象锁为ThreadTest3的class 对象,因此同步生效。

      如上述正确,则同步有两种方式,同步块和同步方法(为什么没有wait和notify?这个我会在补充章节中做出阐述)

      如果是同步代码块,则对象锁需要编程人员自己指定,一般有些代码为synchronized(this)只有在单态模式才生效;
      (本类的实例有且只有一个)

      如果是同步方法,则分静态和非静态两种 。

      静态方法则一定会同步,非静态方法需在单例模式才生效,推荐用静态方法(不用担心是否单例)。

      所以说,在Java多线程编程中,最常见的synchronized关键字实际上是依靠对象锁的机制来实现线程同步的。
      我们似乎可以听到synchronized在向我们说:“给我一把 锁,我能创造一个规矩”。

    展开全文
  • 重入简单理解就是对同一个线程而言,它可以重复获取。例如这个线程可以连续获取两次,但是释放锁的次数也一定要是两次。下面是一个简单例子: public class ReenterLock { private static ...

    重入锁简单理解就是对同一个线程而言,它可以重复的获取锁。例如这个线程可以连续获取两次锁,但是释放锁的次数也一定要是两次。下面是一个简单例子:

    public class ReenterLock {
    
        private static ReentrantLock lock = new ReentrantLock();
    
        private static int i = 0;
    
        // 循环1000000次
        private static Runnable runnable = () -> IntStream.range(0, 1000000).forEach((j) -> {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        });
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            thread1.start();
            thread2.start();
            // 利用join,等thread1,thread2结束后,main线程才继续运行,并打印 i
            thread1.join();
            thread2.join();
            // 利用lock保护的 i,最终结果为 2000000,如果不加,则值肯定小于此数值
            System.out.println(i);
        }
    }

    从上面的代码可以看到,相比于synchronized,开发者必须手动指定锁的位置和什么时候释放锁,这样必然增加了灵活性。

    线程中断响应

    如果线程阻塞于synchronized,那么要么获取到锁,继续执行,要么一直等待。重入锁提供了另一种可能,就是中断线程。下面的例子是利用两个线程构建一个死锁,然后中断其中一个线程,使另一个线程获取锁的例子:

    public class ReenterLockInterrupt {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static Runnable runnable = () -> {
            try {
                // 利用 lockInterruptibly 申请锁,这是可以进中断申请的申请锁操作
                lock.lockInterruptibly();
                // 睡眠20秒,在睡眠结束之前,main方法里要中断thread2的获取锁操作
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                String threadName = Thread.currentThread().getName();
                // 中断后抛出异常,最后要释放锁
                // 如果是线程1则释放锁,因为线程2就没拿到锁,所以不用释放
                if ("Thread-1".equals(threadName)) lock.unlock();
                System.out.println(threadName+" 停止");
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread-1");
            Thread thread2 = new Thread(runnable, "thread-2");
            thread1.start();
    
            // 让主线程停一下,让thread1获取锁后再启动thread2
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 这里什么也不做
            }
    
            thread2.start();
            thread2.interrupt();
        }
    }

    thread-1拿到锁之后,线程即持有锁并等待20秒,然后thread-2启动,并没有拿到锁,这时候中断thread-2线程,线程2退出。

    有限时间的等待锁

    顾名思义,简单理解就是在指定的时间内如果拿不到锁,则不再等待锁。当持有锁的线程出问题导致长时间持有锁的时候,你不可能让其他线程永远等待其释放锁。下面是一个例子:

    public class ReenterTryLock {
        private static ReentrantLock reenterLock = new ReentrantLock();
    
        private static Runnable runnable = () -> {
            try {
                // tryLock()方法会返回一个布尔值,获取锁成功则为true
                if (reenterLock.tryLock(3, TimeUnit.SECONDS)) {
                    Thread.sleep(5000);
                } else {
                    System.out.println(Thread.currentThread().getName() + "获取锁失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 最后,如果当前前程在持有锁,则释放锁
                if (reenterLock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + "释放锁了");
                    reenterLock.unlock();
                }
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread-1");
            Thread thread2 = new Thread(runnable, "thread-2");
    
            thread1.start();
            thread2.start();
        }
    }

    这里使用tryLock()第一个获取锁的线程,会停止5秒。而获取锁的设置为3秒获取不到锁则放弃,所以第二个去尝试获取锁的线程是获取不到锁而被迫停止的。如果tryLock()方法不传入任何参数,那么获取锁的线程不会等待锁,则立即返回false。

    公平锁与非公平锁

    当一个线程释放锁时,其他等待的线程则有机会获取锁,如果是公平锁,则分先来后到的获取锁,如果是非公平锁则谁抢到锁算谁的,这就相当于排队买东西和不排队买东西是一个道理。Java的synchronized关键字就是非公平锁

    那么重入锁ReentrantLock()是公平锁还是非公平锁?

    重入锁ReentrantLock()是可以设置公平性的,可以参考其构造方法:

    // 通过传入一个布尔值来设置公平锁,为true则是公平锁,false则为非公平锁
    public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

    构建一个公平锁需要维护一个有序队列,如果实际需求用不到公平锁则不需要使用公平锁。下面用一个例子来演示公平锁与非公平锁的区别:

    public class ReenterTryLockFair {
        // 分别设置公平锁和非公平锁,分析打印结果
        private static ReentrantLock lock = new ReentrantLock(true);
    
        private static Runnable runnable = () -> {
            while (true) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + " 获取了锁");
                } finally {
                    lock.unlock();
                }
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread---1");
            Thread thread2 = new Thread(runnable, "thread---2");
            Thread thread3 = new Thread(runnable, "thread---3");
    
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }

    当设置为true即公平锁的时候,可以看到打印非常规律,截取一段儿打印结果:

    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁

    可以看到,都是thread–1,thread–2,thread–3,无限循环下去,如果设置的为非公平锁,打印结果就混乱没有规律了:

    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---1 获取了锁

    Condition

    同jdk中的等待/通知机制类似,只不过Condition是用在重入锁这里的。有了Condition,线程就可以在合适的时间等待,在合适的时间继续执行。

    Condition接口包含以下方法:

    // 让当前线程等待,并释放锁
    void await() throws InterruptedException;
    // 和await类似,但在等待过程中不会相应中断
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒等待中的线程
    void signal();
    // 唤醒等待中的所有线程
    void signalAll();

    下面是一个简单示例:

    public class ReenterLockCondition {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static Condition condition = lock.newCondition();
    
        private static Runnable runnable = () -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "进入等待。。");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        };
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(runnable, "thread--1");
            thread.start();
    
            Thread.sleep(2000);
    
            lock.lock();
            condition.signal();
            System.out.println("主线程发出信号");
            lock.unlock();
        }
    }

    thread–1启动,拿到锁,然后进入等待并且释放锁,2秒后,主线程拿到锁,然后发出信号并释放锁,最后,thread–1继续执行。下面是打印结果:

    thread--1进入等待。。
    主线程发出信号
    thread--1继续执行
    展开全文
  • java的锁机制,synchronize与Lock比较

    千次阅读 2017-10-11 10:46:46
    占有锁的线程等待IO或者其他原因被阻塞,没有释放锁的情况下,其他线程一直阻塞 多个线程同时读写文件时候,读和读操作也会发生冲突 我们没有办法知道当前我们线程是否成功获取了,只能傻傻等待 有这些限制...

    以前线程同步只知道synchronize关键字,后来才知道还有个lock,为什么还要有个lock来实现同步呢

    synchronized的局限性

    • 占有锁的线程等待IO或者其他原因被阻塞,没有释放锁的情况下,其他线程一直阻塞
    • 多个线程同时读写文件的时候,读和读操作也会发生冲突
    • 我们没有办法知道当前我们的线程是否成功获取了锁,只能傻傻的等待

    有这些限制所有其他的同步机制来解决,所以就有了lock,lock常用的两个接口和两个实现
    第一个,Lock接口,我们来看下它的定义

    package java.util.concurrent.locks;
    import java.util.concurrent.TimeUnit;
    
    public interface Lock {
    
        void lock();
    
        void lockInterruptibly() throws InterruptedException;
    
        boolean tryLock();
    
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
        void unlock();
    
        Condition newCondition();
    }

    这几个方法的作用分别是

    • lock()获取锁,没有获取一直等待,无返回值
    • lockInterruptibly()获取锁一直等待,无返回值,但是可以被thread.interrupt()方法中断,抛出异常
    • tryLock()获取锁,不等待,返回是否获取成功
    • tryLock(long time, TimeUnit unit)获取锁,没有获取等待指定时间,返回是否获取成功,等待时候可以被thread.interrupt()方法中断,抛出异常
    • unlock()释放锁
    • newCondition()实现线程间的交互,与Object的wait,notify,notify对应
      生产者消费者两种实现方式

    synchronize与lock的锁的释放

    • synchronize满足下列三个条件之一释放锁
      • 占有锁的线程执行完毕
      • 占有锁的线程异常退出
      • 占有锁的线程进入waiting状态释放锁
    • Lock必须调用unlock()方法
      Lock接口的唯一实现ReentrantLock,意思是可重入锁,可重入锁后面介绍

    第二个,ReadWriteLock接口,我们来看下它的定义

    package java.util.concurrent.locks;
    
    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading
         */
        Lock readLock();
    
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing
         */
        Lock writeLock();
    }

    注释写的很清楚,为了解决两个线程同时读操作还互斥的问题
    ReentrantReadWriteLock()为ReadWriteLock 接口的实现

    一些锁的概念

    • 可重入锁,同一个线程调用同锁的多个代码块和方法不会重复加锁,sy和Lock和readwritelock都是可重入锁
    • 可中断锁 在等待的过程中可中断,lockInterruptibly()获取的就是可中断锁,中断会抛出异常
    • 公平锁,上面介绍的两个锁都可以加入Boolean型的构造参数,默认是非公平锁,多个线程同时等待,当前线程执行完毕,随机执行下个线程,公平锁即先到先执行
    • 读写锁,读写锁,即读锁和写锁是分开的
    展开全文
  • Java对象锁的理解

    万次阅读 热门讨论 2018-07-16 14:57:27
    以前理解Java的锁机制认为:锁的类型分为‘类锁’,’方法锁‘,’对象锁‘。 1.类锁(synchronize(静态对象)):类的所有对象都要竞争锁。 2.方法锁(在方法前加synchronize): 同一对象同一方法需要竞争锁。 3....
  • java锁的含义

    千次阅读 2018-06-23 20:11:42
    今天做到了一个题,发现自己对java的锁理解的不太够。觉得这个问题可能有的人也会有理解上的失误。 What is true while A is in a synchronized (list) { ... } block? – It owns the lock on list – It does not ...
  • java内置与显示

    千次阅读 2019-03-18 10:39:48
    多线程编程同步时我们有可能会用到锁,Java的锁可以分为内置锁(sychronized)和显示锁(例如ReentrantLock) 内置锁: 1、sychronized 作用于实例方法时,锁对象是this 2、sychronized 作用于静态方法时,锁对象...
  • Java的锁

    万次阅读 多人点赞 2016-04-20 21:38:47
    在学习或者使用Java的过程中进程会遇到各种各样的锁的概念:公平锁、非公平锁、自旋锁、可重入锁、偏向锁、轻量级锁、重量级锁、读写锁、互斥锁等待。这里整理了Java中的各种锁,若有不足之处希望大家在下方留言探讨...
  • Java锁的分类

    千次阅读 2020-04-12 17:55:33
    Java锁的分类1、可重入/不可重入2、可中断3、公平/非公平4、独享(互斥)/共享(读写)5、乐观/悲观6、分段7、偏向/轻量级/重量级8、自旋 先来一段很常见死锁代码: class ...
  • Java锁机制

    千次阅读 2018-05-16 00:03:00
    那么什么是java锁机制,以及什么时候使用java的锁呢?让我们看看以下几种场景吧!一、同步锁案例:假设现在我们现在有很多人去商店买衣服,因为我们每个人买衣服是可能同时执行的,不可能商店要求同一时刻只能有一个...
  • 目录 一,概述 二,CAS算法 三,Java对象的对象头,以及Mark Word ...什么是java的锁? 为什么java要有锁? java的锁为什么需要优化? 怎么优化的? 1,java中使用synchronized关键字来实现同步功能...
  • JAVA锁

    2019-09-17 15:49:30
    一. JAVA锁的概念 1、自旋: 2、乐观: 3、悲观: 4、独享: 5:共享:(限流)
  • Java悲观与乐观锁的区别及使用场景 文章目录Java悲观与乐观锁的区别及使用场景定义适用场景总结 定义 悲观(Pessimistic Lock) : 每次获取数据时候,都会担心数据被修改,所以每次获取数据时候都会进行...
  • Java 锁的理解

    千次阅读 2020-08-08 10:52:33
        用过并发包朋友对这个应该不会陌生,ReentrantLock 是 Java JUC(java.util.concurrent)包中提供一种可重入,是一种递归无阻塞同步机制。ReentrantLock 等同于synchronized关键字,但是 ...
  • Java锁的种类

    千次阅读 2019-04-17 16:55:21
    Java 提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包含两部分,一个作为锁的对象引用,一个作为由这个锁保护的代码块。以synchronized来修饰的方法就是一种横跨整个方法体的...
  • Java的锁和数据库的锁

    千次阅读 2019-04-05 10:33:15
    面试中经常被问到锁,Java中有锁,数据库中也有锁,这两个中的锁都是怎么分类的呢?有两篇博客关于这个问题讲的特别好,详见下文~ Java的锁 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 59,071
精华内容 23,628
关键字:

java的锁

java 订阅