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重入锁,是实现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);
类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并发艺术》之后再总结。