多线程——Lock的使用

Lock介绍:

lock关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

Lock是一个接口,其中常用的方法有:

  • 尝试获取锁,获取成功则返回,否则阻塞当前线程
    void lock();

  • 尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常
    void lockInterruptibly() throws InterruptedException;

  • 尝试获取锁,获取锁成功则返回true,否则返回false
    boolean tryLock();

  • 尝试获取锁,若在规定时间内获取到锁,则返回true,否则返回false,未获取锁之前被中断,则抛出异常
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

  • 释放锁
    void unlock();

  • 返回当前锁的条件变量,通过条件变量可以实现类似notify和wait的功能,一个锁可以有多个条件变量
    Condition newCondition();

ReentrantLock类:

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。在java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。那么,要想完完全全的弄懂ReentrantLock的话,主要也就是ReentrantLock同步语义的学习:1. 重入性的实现原理;2. 公平锁和非公平锁

一个简单的示例:

main函数:

package ReentranLock;
public class Run {
    public static void main(String[] args) {
        MyService service = new MyService();
        MyThread a1 = new MyThread(service);
        MyThread a2 = new MyThread(service);
        MyThread a3 = new MyThread(service);
        a1.start();
        a2.start();
        a3.start();
    }
}

Service类:

package ReentranLock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

    public void testMethod() {
        lock.lock();
        for (int i = 0; i < 5; i++) {
            System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
        }
        lock.unlock();
    }
}

线程类:

package ReentranLock;

public class MyThread extends Thread {
    private MyService service;

    public MyThread(MyService service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

运行结果如下:

ThreadName=Thread-0 1
ThreadName=Thread-0 2
ThreadName=Thread-0 3
ThreadName=Thread-0 4
ThreadName=Thread-0 5
ThreadName=Thread-1 1
ThreadName=Thread-1 2
ThreadName=Thread-1 3
ThreadName=Thread-1 4
ThreadName=Thread-1 5
ThreadName=Thread-2 1
ThreadName=Thread-2 2
ThreadName=Thread-2 3
ThreadName=Thread-2 4
ThreadName=Thread-2 5

结论为:该ReentrantLock类的lock()和unlock()方法确实可以实现线程的同步效果。


实现等待/通知效果

多线程——Condition的介绍以及用法

“公平锁” 和 “非公平锁”

公平锁:多线程获取锁的顺序是按照线程加锁的顺序来分配的。
非公平锁:都是随机,可能造成某些线程一直无法运行。
如何实现:
在业务类中,生成ReentrantLock对象时,通过其参数boolean值决定是公平锁还是非公平锁。

公平锁:
Lock lock = new ReentrantLock(true);
非公平锁:
Lock lock = new ReentrantLock(false);

ReentrantReadWriteLock类:

类ReentrantLock具有完全互斥排他的效果,即同一时刻只有一个线程在执行ReentrantLock.lock()方法后面的任务,这样做虽然保证了实例变量的安全性,但效率比较底下,所以JDK提供了ReentrantReadWriteLock类,效率更加高效,在某些不需要实例变量的方法中,完全可以使用读写锁ReentrantReadWriteLock 来提升该方法的运行速度。

多个读锁之间不互斥,读锁和写锁之间互斥,写锁与写锁之间也互斥及,多个读操作可异步执行,读写操作须同步执行,写写操作也须同步执行。

读读共享:

main函数:

package ReadWriteLockBegin1;

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

线程类

package ReadWriteLockBegin1;

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

service类

package ReadWriteLockBegin1;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("获取读锁" + Thread.currentThread().getName() + " "
                 + System.currentTimeMillis());
                Thread.sleep(1000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下

获取读锁A 1555038797055
获取读锁B 1555038797055

结论:两个线程几乎同时进入lock()方法后面的代码。开启两个线程同时去争抢锁,发现当锁为读锁时,两线程运行逻辑的时间时几乎一致的,即两者时异步执行的。提高了运行的效率


写写互斥:

main函数:

package ReadWriteLockBegin2;

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

线程类

package ReadWriteLockBegin2;


public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.write();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.write();
    }
}

service类

package ReadWriteLockBegin2;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("获得写锁" + Thread.currentThread().getName() + " " 
                + System.currentTimeMillis());
                Thread.sleep(1000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

运行结果如下

获得写锁A 1555039240644
获得写锁B 1555039241645

结论:由结果看,写写操作是互斥的


读写互斥:

main函数:

package ReadWriteLockBegin3;

public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        Thread.sleep(1000);
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}

线程类

package ReadWriteLockBegin3;


public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.write();
    }
}

service类

package ReadWriteLockBegin3;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Service {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            try {
                lock.readLock().lock();
                System.out.println("获得读锁" + Thread.currentThread().getName() + " " 
                + System.currentTimeMillis());
                Thread.sleep(1000);
            } finally {
                lock.readLock().unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void write() {
        try {
            try {
                lock.writeLock().lock();
                System.out.println("获得写锁" + Thread.currentThread().getName() + " " 
                + System.currentTimeMillis());
                Thread.sleep(1000);
            } finally {
                lock.writeLock().unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果如下

获得读锁A 1555039499243
获得写锁B 1555039500245

结论:读线程和写线程时同步的,说明读写是互斥的。


下一篇将总结synchronized和Lock的区别,两者的实现原理以及volatile关键字的使用和原理等看完《Java并发艺术》之后再总结。

你可能感兴趣的:(学习随笔,并发编程艺术)