美文网首页
多线程系列第(六)篇---Lock和synchronized

多线程系列第(六)篇---Lock和synchronized

作者: sofarsogoo_932d | 来源:发表于2017-10-11 22:13 被阅读0次

    1.问题背景

    在多线程中,经常会遇到多个线程访问一个共享资源的问题,为了保证数据的一致性,就引入了一种锁的机制。线程想要访问共享资源必须要拿到锁,拿到锁的线程可以访问共享资源,访问结束后会释放锁,这样其他线程才有机会拿到锁进而访问共享资源。

    2.锁的分类

    • 可重入锁
      同一个线程在同步方法中可以执行另一个同步方法,而不需要重新获得锁

    • 可中断锁
      在等待锁的过程中可中断

    • 公平锁
      按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利

    • 读写锁
      多资源的读取和写入分开处理,读的时候可以多个线程一起读,写的时候必须同步地写

    3.锁机制

    当某线程首次申请锁时,系统将会记录锁的占有者,并将计数器置为1,当同一个线程再次请求该锁时,计数器加1,当该线程退出一个同步块时,计数器减1,直到计数器的值为0时,其他线程才有机会申请锁

    4.死锁是如何产生的

    线程1持有A资源,同时请求获取B资源,但此时这个B资源已经被线程2占有了,同时线程2请求A资源。

    死锁demo

    public class DeadLockDemo {
    
    class Thread1 extends Thread {
        private Object obj1;
        private Object obj2;
    
        public Thread1(Object obj1, Object obj2, String name) {
            super(name);
            this.obj1 = obj1;
            this.obj2 = obj2;
        }
    
        public void run() {
            System.out.println(getName() + "开始执行");
            System.out.println(getName() + "正在申请资源A...");
            synchronized (obj1) {
                System.out.println(getName() + ":申请资源A成功");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(getName() + "正在申请资源B...");
                synchronized (obj2) {
                    System.out.println(getName() + ":申请资源B成功");
                }
            }
            System.out.println(getName() + "执行结束");
        }
    
    }
    
    class Thread2 extends Thread {
        private Object obj1;
        private Object obj2;
    
        public Thread2(Object obj1, Object obj2, String name) {
            super(name);
            this.obj1 = obj1;
            this.obj2 = obj2;
        }
    
        public void run() {
            System.out.println(getName() + "开始执行");
            System.out.println(getName() + "正在申请资源B...");
            synchronized (obj2) {
                System.out.println(getName() + ":申请资源B成功");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(getName() + "正在申请资源A...");
                synchronized (obj1) {
                    System.out.println(getName() + ":申请资源A成功");
                }
            }
            System.out.println(getName() + "执行结束");
        }
    
    }
    
    public static void main(String[] args) {
        DeadLockDemo demo = new DeadLockDemo();
        Object obj1 = new Object(); // 资源A
        Object obj2 = new Object(); // 资源B
        Thread1 t1 = demo.new Thread1(obj1, obj2, "线程1");
        Thread2 t2 = demo.new Thread2(obj1, obj2, "线程2");
        t1.start();
        t2.start();
        }
    }
    
    运行结果
    线程1开始执行
    线程1正在申请资源A...
    线程1:申请资源A成功
    线程2开始执行
    线程2正在申请资源B...
    线程2:申请资源B成功
    线程2正在申请资源A...
    线程1正在申请资源B...
    

    5.下面将用代码来介绍锁的使用

    下文中将要用到的锁均采用ReentrantLock这个类

    5.1 一个简单的锁

    在上一篇介绍synchronized关键字时,3.2给出的程序其实就是一个用synchronized实现的简单的锁,下面将给出通过Lock来实现锁

    public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    
    private volatile static int number;
    
    private static void add() {
        lock.lock();
        number++;
        lock.unlock();
    }
    
    public static void main(String[] args) {
    
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
    
                public void run() {
                    add();
                }
            }).start();
        }
    
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(number);
      }
    }
    
    5.2 可重入性锁

    synchronized和Lock都是可重入性锁,下面只给出Lock的重入性demo,synchronized是一个道理

    public class ReentrantLockDemo {
    
    private ReentrantLock lock = new ReentrantLock(false);
    
    private void outer() {
        lock.lock();
        System.out.println("我是outer");
        inner();
        lock.unlock();
    }
    
    private void inner() {
        lock.lock();
        System.out.println("我是inner");
        lock.unlock();
    
    }
    
    public static void main(String[] args) {
        ReentrantLockDemo demo = new ReentrantLockDemo();
        demo.outer();
      }
    }
    
    运行结果
    我是outer
    我是inner
    

    额外介绍一种非重入性锁

    public class MyLockDemo {
    
    // 一种非可重入锁
    class MyLock {
        private boolean isLocked = false;
    
        public synchronized void lock() {
            while (isLocked) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            isLocked = true;
        }
    
        public synchronized void unlock() {
            isLocked = false;
            notify();
        }
    }
    
    private MyLock lock = new MyLock();
    
    private void outer() {
        lock.lock();
        System.out.println("我是outer");
        inner();
        lock.unlock();
    }
    
    private void inner() {
        lock.lock();
        System.out.println("我是inner");
        lock.unlock();
    
    }
    
    public static void main(String[] args) {
        MyLockDemo demo = new MyLockDemo();
        demo.outer();
      }
    }
    
    运行结果
    我是outer
    分析,由于在inner方法中调用lock时,会执行wait方法,使得当前线程处于阻塞状态
    
    5.2 可中断锁

    Lock是可中断锁,通过调用lockInterruptibly方法,该方法会抛出中断异常,当线程被中断时,会立即抛出异常,而不会等待获取锁

    public class InterruptLockDemo {
    private ReentrantLock lock = new ReentrantLock();
    
    public void methodA() {
        try {
            lock.lockInterruptibly();
            System.out.println("我是方法A:" + System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    
    }
    
    public void methodB() {
        try {
            lock.lockInterruptibly();
            System.out.println("我是方法B:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+":我被中断了"+System.currentTimeMillis());
        } finally {
            if (lock.tryLock()) {
                lock.unlock();
            }
        }
    }
    
    public static void main(String[] args) {
        final InterruptLockDemo demo = new InterruptLockDemo();
        Thread t1 = new Thread(new Runnable() {
    
            public void run() {
                demo.methodA();
    
            }
        },"线程1");
    
        Thread t2 = new Thread(new Runnable() {
    
            public void run() {
                demo.methodB();
            }
        },"线程2");
    
        t1.start();
        t2.start();
    
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        t2.interrupt();
      }
    }
    
    
    当去掉t2.interrupt时,运行结果
    我是方法A:1507729561117
    我是方法B:1507729563118
    
    加上t2.interrupt时,运行结果
    我是方法A:1507729665791
    线程2:我被中断了1507729666795
    
    注:线程1调用的是方法A,线程2调用的是方法B
    

    结果分析
    1.线程1和线程2调用两个同步方法,当调用到方法A时,会睡2秒,此时线程2就必须等待2秒才能执行方法B

    2.在主线程睡了1秒发现还没获取到锁时,调用线程的中断方法,立即抛出异常,看后面的时间戳就知道(相差的1秒刚好是主线程睡眠的1秒)

    synchronized是不可中断锁

    public class NoInterruptLockDemo {
    
      public synchronized void methodA() {
        try {
            System.out.println("我是方法A:" + System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    }
    
      public synchronized void methodB() {
        System.out.println("我是方法B:" + System.currentTimeMillis());
    
    }
    
    public static void main(String[] args) {
        final NoInterruptLockDemo demo = new NoInterruptLockDemo();
        Thread t1 = new Thread(new Runnable() {
    
            public void run() {
                demo.methodA();
    
            }
        }, "线程1");
    
        Thread t2 = new Thread(new Runnable() {
    
            public void run() {
                demo.methodB();
            }
        }, "线程2");
    
        t1.start();
        t2.start();
    
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        t2.interrupt();
      }
    }
    
    运行结果
    我是方法A:1507730267055
    我是方法B:1507730269059
    

    结果分析
    上述代码和Lock的一样,只是将lock换成了synchronized,就算调用了t2.interrupt,方法B和方法A执行时间仍然相差了2秒,因为synchronized方法无法抛出InterruptedException异常

    5.3 公平锁

    synchronized是非公平锁,而Lock可以选择公平锁,也可以选择费公平锁

      public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    在构造方法可以传入一个布尔值,true表示公平锁,false表示非公平锁

    5.4 读写锁

    ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
    可以通过readLock()获取读锁,通过writeLock()获取写锁。

    6. synchronized和Lock比较

    • 存在层次
      前者是关键字,后者是类

    • 锁的获取
      前者自动获取,后者调用lock方法

    • 锁的释放
      前者是在执行完同步块时自动释放
      后者需要在finally里面主动调用unlock方法

    • 锁类型
      前者可重入,不可中断,非公平
      后者可重入,可中断,可公平

    • 锁状态
      前者无法判断
      后者可判断,通过tryLock方法的返回值来判断,false表示没拿到锁
      tryLock()和tryLock(long time, TimeUnit unit)
      前者是无论拿没拿到锁都立即返回结果
      后者是在没拿到锁时会等待一段时间,在期限时间内如果还拿不到锁就返回false,如果在一开始就拿到锁,立即返回true

    • 性能
      前者适合少量同步
      后者适合大量同步

    相关文章

      网友评论

          本文标题:多线程系列第(六)篇---Lock和synchronized

          本文链接:https://www.haomeiwen.com/subject/qpxgyxtx.html