推荐:Java并发编程汇总
Java并发编程一Lock和ReentrantLock初使用
首先我们来演示一下两个线程不断的打印字符串。
测试代码:
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
public static void main(String[] args) {
new LockDemo().init();
}
private void init() {
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("kaven");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("wyy");
}
}
}).start();
}
static class Outputer {
Lock lock = new ReentrantLock();
//字符串打印方法,一个个字符的打印
public void output(String name) {
int len = name.length();
lock.lock();
try {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
} finally {
lock.unlock();
}
}
}
}
输出:
kaven
kaven
wyy
kaven
wyy
wyy
kaven
kaven
wyy
我只粘贴了一部分输出,很明显这些输出是正常的,毕竟我们使用了ReentrantLock
类(Lock
接口的实现类,接下来都会以ReentrantLock
类进行代码实现)在容易出现线程安全的地方上了锁,如果不上锁会怎么样?
测试代码:
package lock.reentrantlock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
public static void main(String[] args) {
new LockDemo().init();
}
private void init() {
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("kaven");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("wyy");
}
}
}).start();
}
static class Outputer {
Lock lock = new ReentrantLock();
//字符串打印方法,一个个字符的打印
public void output(String name) {
int len = name.length();
// lock.lock();
try {
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
} finally {
// lock.unlock();
}
}
}
}
输出:
wkaven
yy
kaven
wyy
wyy
kaven
kaven
很明显输出出现了问题,是因为在使用共享资源的地方没有上锁,导致了线程安全问题的缘故。
我们来看看Lock
接口有哪些方法。
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock()
:调用该方法,线程将会去尝试获取锁,当成功获取锁后,从该方法返回,如果锁被其他线程占用,则当前线程会处于休眠状态,直到获取锁为止。lockInterruptibly()
:尝试去获取锁并且可被中断,和lock()
方法的不同之处在于该方法会响应中断,即在锁的获取过程中可以中断当前线程。tryLock()
:尝试非阻塞的获取锁,调用该方法会立即返回,如果能够获取锁则返回true
,否则返回false
。tryLock(long time, TimeUnit unit)
:尝试获取锁并且有超时时间,当前线程在3
种情况下会返回,当前线程在超时时间内获取了锁
、当前线程在超时时间内被中断
、超时时间结束,返回false
。unlock()
:释放锁。
这里不会介绍newCondition()
。
lock()、unlock()
Lock
不会像synchronized
一样,出现异常的时候会自动释放锁,所以最佳实践是在finally
中释放锁,以便保证发生异常的时候锁一定会被释放。
如下代码:
package lock.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MustUnlock {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try{
//获取本锁保护的资源
System.out.println(Thread.currentThread().getName()+"开始执行任务");
}finally {
lock.unlock();
}
}
}
lockInterruptibly()
测试代码:
package lock.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterruptibly implements Runnable {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
LockInterruptibly lockInterruptibly = new LockInterruptibly();
Thread thread0 = new Thread(lockInterruptibly);
Thread thread1 = new Thread(lockInterruptibly);
thread0.start();
thread1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "尝试获取锁");
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + "获取到了锁");
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "睡眠期间被中断了");
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "获得锁期间被中断了");
}
}
}
输出:
Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-0获取到了锁
Thread-1获得锁期间被中断了
Thread-0释放了锁
从输出也可以看出Thread-1
在尝试获取锁期间被中断了,这也是synchronized
所不具备的。
tryLock()、tryLock(long time, TimeUnit unit)
测试代码:
package lock.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 用tryLock来避免死锁
*/
public class TryLock implements Runnable {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
TryLock tryLock = new TryLock();
new Thread(tryLock).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tryLock.lock.tryLock()){
System.out.println("第一次尝试获取锁成功");
tryLock.lock.unlock();
}
else{
System.out.println("第一次尝试获取锁失败");
}
try {
if (tryLock.lock.tryLock(5 , TimeUnit.SECONDS)){
System.out.println("第二次尝试获取锁成功");
tryLock.lock.unlock();
}
else{
System.out.println("第二次尝试获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName()+" 获取了锁");
try {
Thread.sleep(5000);
}catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+" 释放了锁");
}
}
}
输出:
Thread-0 获取了锁
第一次尝试获取锁失败
Thread-0 释放了锁
第二次尝试获取锁成功
结果已经很明显了,tryLock()
会非阻塞的尝试获取锁。tryLock(long time, TimeUnit unit)
也会尝试获取锁并且有超时时间,有3
种情况下会返回:
- 当前线程在超时时间内获取了锁。
- 当前线程在超时时间内被中断。
- 超时时间结束,返回
false
。
这也是synchronized
所不具备的。
ReentrantLock
类还有些其他的方法和属性。
getHoldCount()
测试代码:
package lock.reentrantlock;
import java.util.concurrent.locks.ReentrantLock;
public class GetHoldCount {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.lock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
lock.unlock();
System.out.println(lock.getHoldCount());
}
}
输出:
0
1
2
3
2
1
0
getHoldCount()
方法会得到当前线程持有这个锁的次数,Queries the number of holds on this lock by the current thread
。
源码如下:
/**
* Queries the number of holds on this lock by the current thread.
*/
public int getHoldCount() {
return sync.getHoldCount();
}
ReentrantLock
是一种可重入锁,我们来实现一下。
测试代码:
package lock.reentrantlock;
import java.util.concurrent.locks.ReentrantLock;
public class RecursionDemo {
private static ReentrantLock lock = new ReentrantLock();
private static void accessResource() {
lock.lock();
try {
System.out.println("已经对资源进行了处理");
if (lock.getHoldCount()<5) {
System.out.println(lock.getHoldCount());
accessResource();
System.out.println(lock.getHoldCount());
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
accessResource();
}
}
输出:
已经对资源进行了处理
1
已经对资源进行了处理
2
已经对资源进行了处理
3
已经对资源进行了处理
4
已经对资源进行了处理
4
3
2
1
输出很明显展现了ReentrantLock
是一种可重入锁。
fair属性
设置fair
属性为true
,则表明为公平锁,设置为false
,则表明为非公平锁。
测试代码:
package lock.reentrantlock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[5];
for (int i = 0; i < 5; i++) {
thread[i] = new Thread(new Job(printQueue));
}
for (int i = 0; i < 5; i++) {
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable {
PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
printQueue.printJob(new Object());
}
}
class PrintQueue {
private Lock queueLock = new ReentrantLock(true);
public void printJob(Object document) {
System.out.println(Thread.currentThread().getName() + "等待打印");
queueLock.lock();
try {
int duration = new Random().nextInt(3) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
System.out.println(Thread.currentThread().getName() + "打印完毕");
}
}
}
输出:
Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-1正在打印,需要2秒
Thread-1打印完毕
Thread-2正在打印,需要2秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
从输出可以看出线程获取锁的顺序是公平的,即先来先得,如果大家觉得数据量太小说明不了问题,可以自己尝试一下用大数据量进行测试。
我们再来测试一下非公平锁的情况。
测试代码:
package lock.reentrantlock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[5];
for (int i = 0; i < 5; i++) {
thread[i] = new Thread(new Job(printQueue));
}
for (int i = 0; i < 5; i++) {
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable {
PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
printQueue.printJob(new Object());
}
}
class PrintQueue {
private Lock queueLock = new ReentrantLock(false);
public void printJob(Object document) {
System.out.println(Thread.currentThread().getName() + "等待打印");
queueLock.lock();
try {
int duration = new Random().nextInt(3) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
System.out.println(Thread.currentThread().getName() + "打印完毕");
}
queueLock.lock();
try {
int duration = new Random().nextInt(3) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
System.out.println(Thread.currentThread().getName() + "打印完毕");
}
}
}
输出:
Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕
Thread-1正在打印,需要3秒
Thread-1打印完毕
Thread-1正在打印,需要2秒
Thread-1打印完毕
Thread-2正在打印,需要2秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-3正在打印,需要2秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
Thread-2正在打印,需要1秒
Thread-2打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
有没有看出问题来,如果是公平锁,Thread-0打印完毕
后应该是Thread-1正在打印,需要3秒
,而这里是Thread-0正在打印,需要3秒
,很明显是不公平锁。
Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕
上面的代码改成公平锁输出如下:
Thread-0等待打印
Thread-0正在打印,需要1秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-1正在打印,需要3秒
Thread-1打印完毕
Thread-2正在打印,需要3秒
Thread-2打印完毕
Thread-3正在打印,需要3秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕
Thread-1正在打印,需要1秒
Thread-1打印完毕
Thread-2正在打印,需要1秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-4正在打印,需要3秒
Thread-4打印完毕
这才是公平锁的输出情况。