美文网首页Android开发探索
Java多线程(二)线程同步

Java多线程(二)线程同步

作者: 闽越布衣 | 来源:发表于2018-02-10 17:41 被阅读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
    • 当多个线程同时执行synchronized(x)同步代码块时呈同步效果。
    • 当其他线程执行x对象中的synchronized同步方法是呈同步效果。
    • 当其他线程执行x对象方法里面的synchronized(this)代码块也呈同步效果。
    • 同步synchronized(class)代码块的作用跟synchronized static 方法的作用是一样的。即对Class类进行持锁。
    • synchronized(string)同步块与String联合使用时,由于JVM具有String常量池的功能,所有可能导致别的线程一直无法执行。

    volatile关键字

    • 关键字volatile主要作用是使变量在多个线程间可见。
    • 关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
    • 关键字volatile不具备同步性也就不支持原子性。

    synchronized与volatile比较

    • 关键字volatile是线程同步的轻量级实现,所以volatile的性能相比synchronized要好,但是关键字volatile只能修饰变量,而synchronized可以修饰方法以及代码块。
    • 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
    • volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
    • volatile解决的是变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性。

    Lock实现同步

    Lock与synchronized比较

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

    • 提供了更多的功能。tryLock()方法的实现,这个方法试图获取锁,如果锁已经被其他线程占用,它将返回false并继续往下执行代码。
    • Lock接口允许分离读和写操作,允许多个线程读或只有一个写线程。
    • 具有更好的性能。

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

    • 占有锁的线程执行完了该方法或代码块,然后释放对锁的占有;
    • 占有锁线程执行发生异常,此时JVM会让线程自动释放锁;
    • 占有锁线程进入 WAITING 状态从而释放锁,例如在该线程中调用wait()方法等。

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

    • 能够响应中断。在使用synchronized关键字的情形下,假如占有锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,那么其他线程就只能一直等待。这会极大影响程序执行效率。因此,就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间 (解决方案:tryLock(long time, TimeUnit unit)) 或者能够响应中断 (解决方案:lockInterruptibly())),这种情况可以通过 Lock 解决。
    • 多线程读情况。当多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作也会发生冲突现象,但是读操作和读操作不会发生冲突现象。但是如果采用synchronized关键字实现同步的话,就会导致一个问题,即当多个线程都只是进行读操作时,也只有一个线程在可以进行读操作,其他线程只能等待锁的释放而无法进行读操作。因此,需要一种机制来使得当多个线程都只是进行读操作时,线程之间不会发生冲突。同样地,Lock也可以解决这种情况 (解决方案:ReentrantReadWriteLock) 。
    • 判断线程是否成功获得锁。我们可以通过Lock得知线程有没有成功获取到锁 (解决方案:ReentrantLock) ,但这个是synchronized无法办到的。

        使用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 实例 ,用于线程间的协作。

    • lock():用来获取锁,如果锁已被其他线程获取,则进行等待,直到获取锁;使用Lock必须在try…catch…块中进行,并且将释放锁的操作放在finally块中进行,确保锁能被释放,防止死锁的发生。
    • tryLock():尝试性地获取锁,不管有没有获取到都马上返回,拿到锁就返回true,不然就返回false 。
    • tryLock(long time, TimeUnit unit):如果获取不到锁,就等待一段时间,超时返回false。
    • lockInterruptibly():当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。例如,当两个线程A与B同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只能继续等待,那么对线程B调用threadB.interrupt()方法就能够中断线程B的等待过程。所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException,但推荐使用后者。

        注意事项:

    • 当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有在继续等待的情况下,才可以响应中断的。使用synchronized 获取锁,当一个线程处于等待某个锁的状态时,是无法被中断的,只有一直等待下去。
    • 在使用Lock时,无论以哪种方式获取锁,习惯上最好一律将获取锁的代码放到 try…catch…,将锁的unlock操作放到finally子句中,如果线程获取锁,执行过程发生异常能确保锁的释放,避免死锁的产生;如果线程没有获取到锁,在执行finally子句时,执行unlock操作,从而抛出 IllegalMonitorStateException,因为该线程并未获得到锁却执行了解锁操作。

    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();
        }
    }
    
    运行结果图

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

    • Object类中的wait()方法相当于Condition类中的await()方法;
    • Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)方法;
    • Object类中的notify()方法相当于Condition类中的signal()方法;
    • Object类中的notifyAll()方法相当于Condition类中的signalAll()方法;

    使用多个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相关方法

    • int getHoldCount() : 查询当前线程保持此锁定的个数,即调用了lock()方法的次数;
    • int getQueueLength() : 查询正在等待获取此锁的线程个数;
    • int getWaitQueueLength(Condition condition) : 查询等待与此锁相关的给定条件condition线程个数;
    • boolean hasQueuedThread(Thread thread) : 查询指定的线程是否正在等待获取此锁;
    • boolean hasQueuedThreads() : 查询是否有线程正在等待获取此锁;
    • boolean hasWaiters(Condition condition) : 查询是否有线程正在等待与此锁定有关的condition条件;
    • boolean isFair() : 判断是不是公平锁;
    • boolean isHeldByCurrentThread() : 查询当前线程是否保持此锁;
    • boolean isLocked() : 查询是否有任意的线程保持此锁;

    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的区别

    • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
    • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()方法去释放锁,则很可能造成死锁的现象,因此使用Lock时需要在finally块中释放锁;
    • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
    • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
    • Lock可以提高多个线程进行读操作的效率。

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

    相关文章

      网友评论

        本文标题:Java多线程(二)线程同步

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