1、尝试锁
在m2中lock.tryLock()是尝试获取锁。可以传入参数,也可以不用传入参数。如果传入参数会引起阻塞,阻塞的时间为传入的参数时间。在时间范围内如果获取到锁返回true,如果没获取到锁返回false。m2中释放锁的时候需要判断是否获取到锁,否则可能会抛出异常。
package com.sunyou.p3;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//尝试锁
public class Test2 {
Lock lock = new ReentrantLock();
public void m1() {
lock.lock();
System.out.println("m1() start");
try {
Thread.sleep(5000);
System.out.println("m1() end");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void m2() {
boolean trylock = false;
try {
trylock = lock.tryLock(5,TimeUnit.SECONDS);
if(trylock) {
System.out.println("try success");
}else {
System.out.println("try failure");
}
}catch(Exception e) {
e.printStackTrace();
}finally {
if(trylock) {
lock.unlock();
}
}
}
public static void main(String[] args) {
Test2 t = new Test2();
new Thread(new Runnable() {
public void run() {
t.m1();
}
}).start();
new Thread(new Runnable() {
public void run() {
t.m2();
}
}).start();
}
}
2、可被打断的锁
加锁的时候可以加为lock.lockInterruptibly(),即可以被打断的锁。如果被打断了会抛出异常。在m1执行过程中,在main中可以直接打断m2,并抛出异常
package com.sunyou.p3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//可被打断
public class Test3 {
Lock lock = new ReentrantLock();
public void m1() {
lock.lock();
System.out.println("m1() start");
try {
Thread.sleep(5000);
System.out.println("m1() end");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void m2() {
try {
lock.lockInterruptibly();
System.out.println("m2() start");
} catch (InterruptedException e) {
System.out.println("m2() interrupt");
e.printStackTrace();
}finally {
try {
lock.unlock();
}catch(Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test3 t = new Test3();
new Thread(new Runnable() {
public void run() {
t.m1();
}
}).start();
Thread t2 = new Thread(new Runnable() {
public void run() {
t.m2();
}
});
t2.start();
t2.interrupt();
}
}
阻塞状态:普通阻塞、等待队列、锁池队列
- 普通阻塞:sleep(time),可被打断。调用thread.interrupt()方法,可以打断阻塞状态,抛出异常。
- 等待队列:wait()方法被调用,也是一种阻塞状态,只能由notify唤醒。无法打断。
- 锁池队列:无法获取锁标记。不是所有的锁池队列都可被打断。
使用ReentrantLock的lock方法,获取锁标记的时候,如果需要阻塞等待锁标记,无法被打断。
使用Reentrantlock的lockInterruptibly方法,获取锁标记的时候,如果需要阻塞等待,可以被打断。
3、公平锁
如果不不公平锁,当某个线程释放锁资源后,不会考虑其他线程等待的时间,下一次哪个线程抢到锁资源就那个线程准备运行。对于公平锁,等待时间最长的线程会优先得到锁资源。使用Reentrantlock的构造函数传入true表示为公平锁。
package com.sunyou.p3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//公平锁
public class Test4 extends Thread{
Lock lock = new ReentrantLock(true);
@Override
public void run() {
for(int i = 0; i < 5; i++){
lock.lock();
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
}
public static void main(String[] args) {
Test4 t = new Test4();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
4、使用while+notifyAll实现消费者生产者模式
这种方式的notifyAll可能会唤醒本方的线程,下面设置10个线程进行生产,5个线程进行消费。使用一个list集合来装生产消费对象。如果生产满了,会唤醒所有对象包含生产线程。所以不好。
package com.sunyou.p4;
import java.util.LinkedList;
//生产者消费者
public class Test1 {
private LinkedList<Object> list = new LinkedList<>();
private static final int MAXSIZE = 10;
private static int count = 0;
public int getCount() {
return count;
}
public synchronized void put(Object obj) {
while (list.size() == MAXSIZE) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(obj);
System.out.println(Thread.currentThread().getName()+"生产了烤鸡");
count++;
this.notifyAll();
}
public synchronized void get() {
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName()+"消费了" + list.removeFirst());
this.notifyAll();
}
public static void main(String[] args) {
Test1 t = new Test1();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
while(true)
t.put("烤鸡");
}
},"生产者"+i).start();
}
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
while (true)
t.get();
}
},"消费者"+i).start();
}
}
}
5、使用Condition来指定唤醒对方的线程
package com.sunyou.p4;
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//生产者消费者模式
public class Test2 {
private LinkedList<Object> list = new LinkedList<>();
private static final int MAXSIZE = 10;
private Lock lock = new ReentrantLock();
Condition consumer = lock.newCondition();
Condition producer = lock.newCondition();
public void put(Object obj) {
lock.lock();
try {
while (list.size() == MAXSIZE) {
producer.await();
}
list.add(obj);
System.out.println(Thread.currentThread().getName() + "生产了" + obj);
consumer.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get() {
lock.lock();
try {
while (list.size() == 0) {
consumer.await();
}
System.out.println(Thread.currentThread().getName() + "消费了" + list.removeFirst());
producer.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Test2 t = new Test2();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
while (true)
t.put("烤鸡");
}
}, "生产者" + i).start();
}
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
public void run() {
while (true)
t.get();
}
}, "消费者" + i).start();
}
}
}
6、锁的底层实现
java虚拟机中的同步(synchronized)基于进入和退出管程(monitor)对象实现。同步方法并不是由monitor enter 和 monitor exit指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的ACC_SYNCHRONIZED标志来隐式实现的。
对象头:存储对象的hashCode、锁信息或分代年龄或GC标志,类型指针指向对象的类元数据,JVM通过这个指针确定该对象是那个类的实例等信息。
实例变量:存放类的属性数据信息,包括父类的属性信息。
当在对象上加锁时,数据是记录在对象头中。当执行synchronized同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是monitor对象的起始地址。每个对象都存在一个monitor与之关联,对象与其monitor之间的关系存在很多实现方式,如monitro可以与对象一起创建销毁或当前线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它便处于锁定状态。在java虚拟机中,monitor是由ObjectMonitor实现的,其中有两个队列,_WaitSet和_EntryList,以及_Ower标记。第一个管理等待队列线程,第二个管理锁池阻塞线程,第三个用于记录当前执行线程。
当多个线程并发访问同一同步代码块时,首先会进入_EntryList,当线程获取锁标记后,monitor中的_Ower记录此线程,并在monitor中的计数器递增加1代表锁定,其他线程在_EntryList中继续阻塞。若执行线程调用wait方法,则monoitor中的计数器执行赋值为0计算,并将_Ower标记赋值为null,代表放弃锁,执行线程进入_WaitSet中阻塞。若执行线程调用notify/notifyAll方法,_WaitSet中的线程被唤醒,进入EntryList中阻塞,等待获取锁标记。若执行线程的同步代码块执行结束,同样会释放锁标记,monitor中的_Ower赋值为null,且计数器赋值为0。