十一、隐式锁
隐式锁是互斥锁的一种,应用于不同线程面对同一个任务的情况,可以通过关键字 synchronized 对代码块或方法进行锁定,被锁定的代码块或方法在同一时刻只能被一个线程调用,其他线程只能等待上一个线程执行完毕解锁之后才能调用。
(一)synchronized 锁定代码块
public class Task extends Thread{
static Integer i=5; //加上static,是因为main方法里实例化了两个Task对象,如果不加static,就等于task1和task2各自处理各自的i,这样锁就失去了意义。
public void run()
{
while(i>0)
{
synchronized(i) //括号内的参数为对象,所以如果是基本类型要转为包装类。
{
System.out.println("线程:"+Thread.currentThread()+"输出 "+i);
i--;
}
//在释放锁之后还让线程休眠100毫秒,是为了让该线程继续占用时间,而其他线程有足够的时间抢到锁。
try {Thread.sleep(100);}
catch (InterruptedException e) {e.printStackTrace();}
}
}
public static void main(String[] args)
{
Task task1=new Task();
Task task2=new Task();
task1.start();
task2.start();
}
}
本案例中,由于 Task 继承的是 Thread 类,导致 Task 的实例化对象既是线程,又是一个具体的实例化对象,因而锁定对象前面要添加 static 关键字。如果 Task 实现的是 Runnable 或 Callable 接口,那么 Task 的实例化对象就只是一项任务,可以将该任务添加至不同的线程,因而不需要用 static 关键字修饰,这也是我们多数情况下不偏向于继承 Thread 类的原因。
(二)synchronized 锁定方法
public class Task implements Runnable{
public synchronized void run() //锁定方法
{
for(int i=0;i<=3;i++)
{
System.out.println("线程为:"+Thread.currentThread().getName()+"输出"+i);
}
}
public static void main(String[] args)
{
Task task=new Task();
Thread th1=new Thread(task);
Thread th2=new Thread(task);
th1.start();
th2.start();
}
}
十二、隐式锁的等待与唤醒
.wait() 强制当前线程等待,直到其他线程在同一个对象上调用 .notify() 方法唤醒该线程。如果要唤醒多个等待的线程,则使用 .notifyAll() 方法。
案例(生产者消费者模式):
public class Factory {
int num=1;//库存
//生产
public synchronized void addProduct() throws InterruptedException {
if(num>=10) {
System.out.println("仓库已满,不能再生产了!");
this.wait();//强制当前线程等待,直到其他线程在同一个对象调用notify()方法后释放
}else {
Thread.sleep(1000);
num++;//生产出商品
System.out.println("生产者生产了一个商品,现在库存为:"+num);
this.notify();//唤醒消费商品的线程
}
}
//消费
public synchronized void subProduct() throws InterruptedException {
if(num<=0) {
System.out.println("库存没了,不能再消费!");
this.wait();//强制当前线程等待
}else {
Thread.sleep(1000);
num--;//消费商品
System.out.println("消费者消费了一个商品,现在库存为:"+num);
this.notify();//唤醒生产商品的线程
}
}
}
public class Productor implements Runnable{
Factory factory;
public Productor(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
//生产者不停生产商品
while(true) {
try {
this.factory.addProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Customer implements Runnable{
Factory factory;
public Customer(Factory factory) {
this.factory = factory;
}
@Override
public void run() {
//消费者不停消费商品
while(true) {
try {
this.factory.subProduct();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) {
Factory factory=new Factory();
Productor p=new Productor(factory);//生产者线程对象
Customer c=new Customer(factory); //消费者线程对象
new Thread(p).start();
new Thread(c).start();
}
}
十三、显式锁
显式锁(Lock)与隐式锁(synchronized)作用相同,但比隐式锁更为灵活。隐式锁是用关键字 synchronized 锁定代码块或方法,显式锁则是将需要锁定的内容直接编写在 .lock() 与 .unlock() 之间即可。比较以下区别:
(一).lock 与 .unlock 方法
① 隐式锁
public class Task implements Runnable{
public void run()
{
synchronized(this) //代码写在synchronized大括号内
{
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args)
{
Task task=new Task();
Thread thread1=new Thread(task);
Thread thread2=new Thread(task);
thread1.start();
thread2.start();
}
}
② 显式锁
public class Task implements Runnable{
//实例化一个Lock对象,ReentrantLock是Lock的一个子类,称为可重入锁
private Lock lock=new ReentrantLock();
public void run()
{
//锁定的代码写在.lock()与.unlock()之间
lock.lock();
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
public static void main(String[] args)
{
Task task=new Task();
Thread thread1=new Thread(task);
Thread thread2=new Thread(task);
thread1.start();
thread2.start();
}
}
本案例中,锁定的代码为:休眠 1 秒钟之后,输出当前线程的名称。从输出结果来看,一个线程调用锁定的代码块结束之后,另一个线程才开始调用,证明显式锁与隐式锁具有相同效果。
(二).tryLock 与 .unlock 方法
在前面的内容中,我们所学的锁均是阻塞式获取锁,也就是一个线程拿到锁之后,别的线程只能默默等待。使用 Lock 的 .tryLock() 方法可以将 run() 方法里的代码块分成两部分,一部分加锁,一部分不加锁。当一个线程抢到锁之后,另一个线程不必等待,而是执行不加锁的内容。
public class Task implements Runnable{
private Lock lock=new ReentrantLock();
public void run()
{
if(lock.tryLock()) //开始锁定
{
try {System.out.println(Thread.currentThread().getName()+"正在执行锁定代码");}
finally{lock.unlock();} //解除锁定
}
else {System.out.println("喂:"+Thread.currentThread().getName()+",代码锁着,你先执行我这段任务吧");}
}
public static void main(String[] args)
{
Task task=new Task();
Thread thread1=new Thread(task);
Thread thread2=new Thread(task);
thread1.start();
thread2.start();
}
}
在本案例中,if 语句里面编写锁定的代码块,else 语句里面编写不锁定的代码块;当一个线程拿到 .tryLock() 锁之后,别的线程不等锁释放,而是转头去执行 else 语句的内容。if 语句里面用 try……finally…… 语句将锁定执行的代码块放在 try 里面,将解锁语句放在 finally 里面。
十四、显式锁的等待与唤醒
隐式锁使用的是 .wait() 与 .notify() 或 .notifyAll() 方法,显式锁对应的是 condition.await() 与 condition.signal() 或 condition.signalAll() 方法。
public class TaskSum{
Lock lock;
Condition condition;
public TaskSum(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
public void method1() throws InterruptedException
{
lock.lock();
condition.await();
System.out.println(Thread.currentThread().getName()+" AAA");
lock.unlock();
}
public void method2() throws InterruptedException
{
lock.lock();
System.out.println(Thread.currentThread().getName()+" BBB");
Thread.sleep(2000);
condition.signal();
lock.unlock();
}
}
public class Task1 implements Runnable{
TaskSum tasksum;
public Task1(TaskSum tasksum) {
this.tasksum = tasksum;
}
@Override
public void run() {
try {
tasksum.method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Task2 implements Runnable{
TaskSum tasksum;
public Task2(TaskSum tasksum) {
this.tasksum = tasksum;
}
@Override
public void run() {
try {
tasksum.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
TaskSum tasksum=new TaskSum(lock,condition);
Task1 task1=new Task1(tasksum);
Task2 task2=new Task2(tasksum);
Thread th1=new Thread(task1);
Thread th2=new Thread(task2);
th1.start();
th2.start();
}
}