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

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)