Android开发探索

Java多线程(二)线程同步

2018-02-10  本文已影响31人  闽越布衣

线程安全

    当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
    要编写出线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变状态的访问。共享意味着变量可以由多个线程同时访问,而可变则意味着变量的值在其生命周期内可以发生变化。

Synchronized实现同步

    synchronized是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
    当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法或代码块,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

synchronized同步方法

    被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象。

方法内的变量为线程安全

    非线程安全的问题存在于实例变量中,如果是方法内的私有变量,则不存在非线程安全的问题,来看下面的例子:

public class HasSelfPrivateNum {
    public void addNum(String name) {
        try {
            int num = 0;
            if (name == "a") {
                num = 100;
                System.out.println("a set num");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set num");
            }
            System.out.println(name + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private HasSelfPrivateNum hasSelfPrivateNum;
    public ThreadA(HasSelfPrivateNum hasSelfPrivateNum) {
        this.hasSelfPrivateNum = hasSelfPrivateNum;
    }

    @Override
    public void run() {
        super.run();
        hasSelfPrivateNum.addNum("a");
    }
}
public class ThreadB extends Thread {
    private HasSelfPrivateNum hasSelfPrivateNum;
    public ThreadB(HasSelfPrivateNum hasSelfPrivateNum) {
        this.hasSelfPrivateNum = hasSelfPrivateNum;
    }

    @Override
    public void run() {
        super.run();
        hasSelfPrivateNum.addNum("b");
    }
}
public class SyschronizedDemo1 {
    public static void main(String[] args) {
        HasSelfPrivateNum hasSelfPrivateNum = new HasSelfPrivateNum();
        Thread threada = new ThreadA(hasSelfPrivateNum);
        threada.start();
        Thread threadb = new ThreadB(hasSelfPrivateNum);
        threadb.start();
    }
}
运行结果图.png

    可见,方法中的变量不存在非线程安全的问题,这是因为方法内部的变量是私用的特性造成的。

实例变量非线程安全

    如果多个线程访问一个对象中的实例变量,则可能出现非线程安全的问题,来看下面的例子,将上面的HasSelfPrivateNum类改成如下:

public class HasSelfPrivateNum {
    private int num = 0;
    public void addNum(String name) {
        try {
            if (name == "a") {
                num = 100;
                System.out.println("a set num");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set num");
            }
            System.out.println(name + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
运行结果.png

    两个线程访问没有同步的方法去操作对像中的实例变量,导致非线程安全问题。处理方法就是在在方法面前添加synchronized关键字即可,看修改后HasSelfPrivateNum类:

public class HasSelfPrivateNum {
    private int num = 0;
    public synchronized void addNum(String name) {
        try {
            if (name == "a") {
                num = 100;
                System.out.println("a set num");
                Thread.sleep(1000);
            } else {
                num = 200;
                System.out.println("b set num");
            }
            System.out.println(name + " num = " + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
运行结果.png

多个对象多个锁

    当两个线程同时访问一个类的不同实例的相同名称的同步方法时,结果是以异步的方式运行。修改上面的SyschronizedDemo1类,如下:

public class SyschronizedDemo1 {
    public static void main(String[] args) {
        HasSelfPrivateNum hasSelfPrivateNuma = new HasSelfPrivateNum();
        HasSelfPrivateNum hasSelfPrivateNumb = new HasSelfPrivateNum();
        Thread threada = new ThreadA(hasSelfPrivateNuma);
        threada.start();
        Thread threadb = new ThreadB(hasSelfPrivateNumb);
        threadb.start();
    }
}
运行结果图.png

    关键字作用在方法或代码块时,取得的是对象的锁,而不是把一段代码或方法当作锁。哪个程序先执行synchronized关键字的方法,哪个线程就先持有该方法所属对象的锁,其他线程只能等待,前提是多个线程访问的是同一个对象。
    如果多个线程访问的是多个对象,则会创建多个锁,上面的例子就创建了两个HasSelfPrivateNum对象,所以就产生了两个锁,所以运行结果为上图所示。

synchronized方法与锁对象

    A线程先持有object对象的Lock锁时,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
    A线程先持有object对象的Lock锁时,B线程如果在这时调用object对象中的synchronized类型的方法则需要等待,即同步。

synchronized锁重入

    synchronized拥有锁重入的功能,即在使用synchronized时,当一个线程得到一个对象锁后,再次请求对象锁时是可以再次得到该对象的锁的。

public class Test {
    public synchronized void test1() {
        System.out.println("test1");
        test2();
    }

    public synchronized void test2() {
        System.out.println("test2");
        test3();
    }

    public synchronized void test3() {
        System.out.println("test3");
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        Test test = new Test();
        test.test1();
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
运行结果图.png

    “可重入锁”的概念是:自己可以再次获取自己的内部锁。如果不可重入的话,就会造成死锁。
    可重入锁也支持在父子类继承的环境中,子类可通过“可重入锁”调用父类的同步方法。

静态同步synchronized方法

    关键字synchronized也可以应用在static静态方法上,表示对当前的*.java文件对应的Class类进行持锁。

public class Test {
    public static synchronized void methodA() {
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入methodA");
            Thread.sleep(2000);
            System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开methodA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static synchronized void methodB() {
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入methodB");
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开methodB");
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Test.methodA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        Test.methodB();
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB();
        threadB.setName("B");
        threadB.start();
    }
}
运行结果图.png

    从运行结果看,似乎没什么区别,其实不然,synchronized关键字作用在静态方法上是给Class类上锁,而作用在非static静态方法上是给对象上锁。

public class Test {
    public static synchronized void methodA() {
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入methodA");
            Thread.sleep(2000);
            System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开methodA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void methodB() {
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入methodB");
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开methodB");
    }

    public synchronized void methodC() {
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 进入methodC");
        System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + " 离开methodC");
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        Test.methodA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        Test.methodB();
    }
}
public class ThreadC extends Thread {
    private Test test;

    public ThreadC(Test test) {
        this.test = test;
    }

    @Override
    public void run() {
        test.methodC();
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        Test test = new Test();
        ThreadA threadA = new ThreadA();
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB();
        threadB.setName("B");
        threadB.start();
        ThreadC threadC = new ThreadC(test);
        threadC.setName("C");
        threadC.start();
    }
}
运行结果图.png

    异步的原因是一个是对象锁,一个Class锁。

其他

    出现异常时,锁自动释放。
    同步不具有继承性,子类如果想达到同步效果,须在子类对应的方法上加synchronized。

synchronized同步代码块

synchronized代码块的使用

    当两个并发线程访问同一个对象中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

public class Test {
    public void testMethod() {
        try {
            synchronized (this) {
                System.out.println("begin time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("end time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Test test;

    public ThreadA(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.testMethod();
    }
}
public class ThreadB extends Thread {
    private Test test;

    public ThreadB(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.testMethod();
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        Test test = new Test();
        ThreadA threadA = new ThreadA(test);
        threadA.start();
        ThreadB threadB = new ThreadB(test);
        threadB.start();
    }
}
运行结果图.png

synchronized代码块间的同步性

    当一个线程访问对象的一个synchronized(this)代码块时,其他线程对同一个对象中所有的synchronized(this)代码块的访问将别阻塞。同时其他线程访问同一对象的synchronized方法也将被阻塞,即synchronized(this)代码块锁定的是当前对象。

public class Test {
    public void testMethodA() {
        try {
            synchronized (this) {
                System.out.println("testMethodA begin time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("testMethodA end time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void testMethodB() {
        synchronized (this) {
            System.out.println("testMethodB begin time = " + System.currentTimeMillis());
            System.out.println("testMethodB end time = " + System.currentTimeMillis());
        }
    }
}
public class ThreadA extends Thread {
    private Test test;

    public ThreadA(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.testMethodA();
    }
}
public class ThreadB extends Thread {
    private Test test;

    public ThreadB(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.testMethodB();
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        Test test = new Test();
        ThreadA threadA = new ThreadA(test);
        threadA.start();
        ThreadB threadB = new ThreadB(test);
        threadB.start();
    }
}
运行结果图.png

将非this对象作为对象监视器

    在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。

public class Test {
    private String userName;
    private String password;
    private String type = new String();

    public void setUser(String userName, String password) {
        try {
            synchronized (type) {
                System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + "进入同步块");
                this.userName = userName;
                Thread.sleep(2000);
                this.password = password;
                System.out.println("线程:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + "离开同步块");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread {
    private Test test;

    public ThreadA(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.setUser("AA","12345");
    }
}
public class ThreadB extends Thread {
    private Test test;

    public ThreadB(Test test) {
        super();
        this.test = test;
    }

    @Override
    public void run() {
        super.run();
        test.setUser("BB","54321");
    }
}
public class SyschronizedDemo {
    public static void main(String[] args) {
        Test test = new Test();
        ThreadA threadA = new ThreadA(test);
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB(test);
        threadB.setName("B");
        threadB.start();
    }
}
运行结果图.png

volatile关键字

synchronized与volatile比较

Lock实现同步

Lock与synchronized比较

    在JDK1.5中Java提供了同步代码块的另一种机制,它比synchronized关键字更强大也更加灵活。这种机制基于Lock接口及其实现类,它比synchronized关键字好的地方:

    synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。
    如果一个方法或代码块被synchronized关键字修饰,当一个线程获取了对应的锁,并执行该方法或代码块时,其他线程便只能一直等待直至占有锁的线程释放锁。占有锁的线程释放锁一般会是以下三种情况之一:

    一般在以下几种情况下需考虑使用Lock:

    使用Lock需要注意:当我们使用synchronized时是不需要用户去手动释放锁,当synchronized方法或者代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock则必须要用户去手动释放锁 (发生异常时,不会自动释放锁),如果没有主动释放锁,就有可能导致死锁现象。

Lock的使用

    先来看看Lock接口源码:

public interface Lock {  
    void lock();  
    void lockInterruptibly() throws InterruptedException; 
    boolean tryLock();  
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 
    void unlock();  
    Condition newCondition();  
}  

    lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用来获取锁的。unLock()方法是用来释放锁的,其放在finally块里执行,可以保证锁一定被释放。newCondition() 返回绑定到此 Lock 的新的 Condition 实例 ,用于线程间的协作。

    注意事项:

ReentrantLock使用

ReentrantLock实现同步

public class ReentrantLockTest {
    private Lock lock = new ReentrantLock();

    public void testA() {
        try {
            lock.lock();
            System.out.println("testA begin ThreadName:" + Thread.currentThread().getName() + " time:" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("testA end ThreadName:" + Thread.currentThread().getName() + " time:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void testB() {
        try {
            lock.lock();
            System.out.println("testB begin ThreadName:" + Thread.currentThread().getName() + " time:" + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("testB end ThreadName:" + Thread.currentThread().getName() + " time:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockThreadA extends Thread {
    private ReentrantLockTest reentrantLockTest;

    public ReentrantLockThreadA(ReentrantLockTest reentrantLockTest) {
        this.reentrantLockTest = reentrantLockTest;
    }

    @Override
    public void run() {
        reentrantLockTest.testA();
    }
}
public class ReentrantLockThreadB extends Thread {
    private ReentrantLockTest reentrantLockTest;

    public ReentrantLockThreadB(ReentrantLockTest reentrantLockTest) {
        this.reentrantLockTest = reentrantLockTest;
    }

    @Override
    public void run() {
        reentrantLockTest.testB();
    }
}
public class ReentrantLockMain {
    public static void main(String[] args) {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
        ReentrantLockThreadA reentrantLockThreadA = new ReentrantLockThreadA(reentrantLockTest);
        ReentrantLockThreadB reentrantLockThreadB = new ReentrantLockThreadB(reentrantLockTest);
        reentrantLockThreadA.start();
        reentrantLockThreadB.start();
    }
}
运行结果图

    程序中调用ReentrantLock的lock()方法获取锁,调用unlock()方法释放锁。从程序的运行结果可以看出,当其中一个调用lock.lock()时,线程就持有了“对象的监视器”,其他的线程只能等待线程释放锁后才能再次竞争锁,效果和使用synchronized关键字一样,线程之间执行的顺序也是随机的。

使用Condition实现等待/通知

    关键字synchronized与wait()和notify()/notifyAll()方法结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助Condition对象。Condition是JDK5中出现的技术,使用它可以实现多路通知功能,也就是一个Lock对象里面可以创建多个Condition(对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度上比synchronized更灵活。
    使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的。使用ReentrantLock结合Condition可以实现选择性通知。

public class ConditionTest {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await time:" + System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println("signal time:" + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}
public class ConditionThread extends Thread {
    private ConditionTest conditionTest;

    public ConditionThread(ConditionTest conditionTest) {
        this.conditionTest = conditionTest;
    }

    @Override
    public void run() {
        conditionTest.await();
    }
}
public class ConditionMain {
    public static void main(String[] args) throws InterruptedException {
        ConditionTest conditionTest = new ConditionTest();
        ConditionThread conditionThread = new ConditionThread(conditionTest);
        conditionThread.start();
        Thread.sleep(5000);
        conditionTest.signal();
    }
}
运行结果图

    从运行结果中可以看到,成功实现了等待/通知的模式。

使用多个Condition实现通知部分线程

public class ConditionTest {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("awaitA begin time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("awaitA end time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("awaitB begin time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("awaitB end time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("signalAll_A time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("signalAll_B time:" + System.currentTimeMillis() + " ThreadName:" + Thread.currentThread().getName());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
public class ConditionThreadA extends Thread {
    private ConditionTest conditionTest;

    public ConditionThreadA(ConditionTest conditionTest) {
        this.conditionTest = conditionTest;
    }

    @Override
    public void run() {
        conditionTest.awaitA();
    }
}
public class ConditionThreadB extends Thread {
    private ConditionTest conditionTest;

    public ConditionThreadB(ConditionTest conditionTest) {
        this.conditionTest = conditionTest;
    }

    @Override
    public void run() {
        conditionTest.awaitB();
    }
}
public class ConditionMain {
    public static void main(String[] args) throws InterruptedException {
        ConditionTest conditionTest = new ConditionTest();
        ConditionThreadA conditionThreadA = new ConditionThreadA(conditionTest);
        conditionThreadA.setName("A");
        conditionThreadA.start();
        ConditionThreadB conditionThreadB = new ConditionThreadB(conditionTest);
        conditionThreadB.setName("B");
        conditionThreadB.start();
        Thread.sleep(5000);
        conditionTest.signalAll_A();
    }
}
运行结果图.png

    从运行结果可以看,我们可以用Condition对线程进行分组,然后唤醒指定种类的线程。

公平锁与非公平锁

    锁Lock分为公平锁和非公平锁,公平锁表示获取锁的顺序是按照线程加锁顺序来分配,即FIFO先进先出顺序。而非公平锁是随机获取锁的,是一种抢占机制,这可能造成某些线程一直拿不到锁。

公平锁
public class ReentrantLockTest {
    private Lock lock;

    public ReentrantLockTest(boolean isFair) {
        lock = new ReentrantLock(isFair);
    }

    public void test() {
        try {
            lock.lock();
            System.out.println("ThreadName:" + Thread.currentThread().getName() + "获得锁");
        } finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockThreadA extends Thread {
    private ReentrantLockTest reentrantLockTest;

    public ReentrantLockThreadA(ReentrantLockTest reentrantLockTest) {
        this.reentrantLockTest = reentrantLockTest;
    }

    @Override
    public void run() {
        reentrantLockTest.test();
    }
}
public class ReentrantLockMain {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest(true);
        ReentrantLockThreadA[] threadAs = new ReentrantLockThreadA[10];
        for (int i = 0; i < 10; i++) {
            threadAs[i] = new ReentrantLockThreadA(reentrantLockTest);
        }
        for (int i = 0; i < 10; i++) {
            threadAs[i].start();
        }
    }
}
运行结果图.png

    当我们调用ReentrantLock(boolean isFair)构造函数传入true值时,即为公平锁,线程获取锁是按顺序来的。

非公平锁
public class ReentrantLockMain {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest(false);
        ReentrantLockThreadA[] threadAs = new ReentrantLockThreadA[10];
        for (int i = 0; i < 10; i++) {
            threadAs[i] = new ReentrantLockThreadA(reentrantLockTest);
        }
        for (int i = 0; i < 10; i++) {
            threadAs[i].start();
        }
    }
}
非公平锁.png

    当我们调用ReentrantLock(boolean isFair)构造函数传入false值时,即为非公平锁,线程获取锁的顺序是随机的。

ReentrantLock相关方法

ReentrantReadWriteLock使用

    读写锁表示有两个锁,一个是读操作相关的锁,即共享锁;一个是写操作相关的锁,即排他锁;也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。

读读共享

public class ReentrantReadWriteLockTest {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void read() {
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println("ThreadName: " + Thread.currentThread().getName() + " 获得读锁 Time:" + System.currentTimeMillis());
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}
public class ReentrantReadWriteLockA extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockA(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.read();
    }
}
public class ReentrantReadWriteLockB extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockB(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.read();
    }
}
public class ReentrantReadWriteLockMain {
    public static void main(String[] args) {
        ReentrantReadWriteLockTest reentrantReadWriteLockTest = new ReentrantReadWriteLockTest();
        ReentrantReadWriteLockA reentrantReadWriteLockA = new ReentrantReadWriteLockA(reentrantReadWriteLockTest);
        ReentrantReadWriteLockB reentrantReadWriteLockB = new ReentrantReadWriteLockB(reentrantReadWriteLockTest);
        reentrantReadWriteLockA.start();
        reentrantReadWriteLockB.start();
    }
}
运行结果图.png

    从运行结果可以看出,两个线程(几乎)同时获得锁,说明ReentrantReadWriteLock 的readLock()读锁是可以多个线程共享的,即读读共享,这样能提供程序的运行效率。

写写互斥

public class ReentrantReadWriteLockTest {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void write() {
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println("ThreadName: " + Thread.currentThread().getName() + " 获得写锁 Time:" + System.currentTimeMillis());
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
}
public class ReentrantReadWriteLockA extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockA(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.write();
    }
}
public class ReentrantReadWriteLockB extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockB(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.write();
    }
}
public class ReentrantReadWriteLockMain {
    public static void main(String[] args) {
        ReentrantReadWriteLockTest reentrantReadWriteLockTest = new ReentrantReadWriteLockTest();
        ReentrantReadWriteLockA reentrantReadWriteLockA = new ReentrantReadWriteLockA(reentrantReadWriteLockTest);
        ReentrantReadWriteLockB reentrantReadWriteLockB = new ReentrantReadWriteLockB(reentrantReadWriteLockTest);
        reentrantReadWriteLockA.start();
        reentrantReadWriteLockB.start();
    }
}
运行结果图.png

    从运行结果可以看出,使用写锁writeLock()时,同一时间只能有一个线程获取锁,另一个线程必须等待获得锁的线程释放锁后才能再次竞争锁,说明ReentrantReadWriteLock 的writeLock()写锁是互斥的,即写写互斥。

读写互斥

public class ReentrantReadWriteLockTest {
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void read() {
        try {
            reentrantReadWriteLock.readLock().lock();
            System.out.println("ThreadName: " + Thread.currentThread().getName() + " 获得读锁 Time:" + System.currentTimeMillis());
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
    public void write() {
        try {
            reentrantReadWriteLock.writeLock().lock();
            System.out.println("ThreadName: " + Thread.currentThread().getName() + " 获得写锁 Time:" + System.currentTimeMillis());
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }
}
public class ReentrantReadWriteLockA extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockA(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.read();
    }
}
public class ReentrantReadWriteLockB extends Thread {
    private ReentrantReadWriteLockTest reentrantReadWriteLockTest;

    public ReentrantReadWriteLockB(ReentrantReadWriteLockTest reentrantReadWriteLockTest) {
        this.reentrantReadWriteLockTest = reentrantReadWriteLockTest;
    }

    @Override
    public void run() {
        reentrantReadWriteLockTest.write();
    }
}
public class ReentrantReadWriteLockMain {
    public static void main(String[] args) {
        ReentrantReadWriteLockTest reentrantReadWriteLockTest = new ReentrantReadWriteLockTest();
        ReentrantReadWriteLockA reentrantReadWriteLockA = new ReentrantReadWriteLockA(reentrantReadWriteLockTest);
        ReentrantReadWriteLockB reentrantReadWriteLockB = new ReentrantReadWriteLockB(reentrantReadWriteLockTest);
        reentrantReadWriteLockA.start();
        reentrantReadWriteLockB.start();
    }
}
运行结果图.png

    从运行结果可以看出,使用写锁writeLock()与读锁readLock()时,同一时间只能有一个线程获取锁,另一个线程必须等待获得锁的线程释放锁后才能再次竞争锁,说明ReentrantReadWriteLock 的writeLock()写锁与readLock()读锁是互斥的,即读写互斥。

线程同步面试

同步代码块和同步方法的区别

    两者的区别主要体现在同步锁上面。对于实例的同步方法,因为只能使用this来作为同步锁,如果一个类中需要使用到多个锁,为了避免锁的冲突,必然需要使用不同的对象,这时候同步方法不能满足需求,只能使用同步代码块(同步代码块可以传入任意对象);或者多个类中需要使用到同一个锁,这时候多个类的实例this显然是不同的,也只能使用同步代码块,传入同一个对象。

volatile关键字的作用

    保证变量对线程透明,即保证每次读取到volatile变量,一定是最新的数据;

synchronized和Lock的区别


    整理文章主要为了自己日后复习用,文章中可能会引用到别的博主的文章内容,如涉及到博主的版权问题,请博主联系我。

上一篇 下一篇

猜你喜欢

热点阅读