2、高并发编程ReentrantLock

 

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();
	}
}

阻塞状态:普通阻塞、等待队列、锁池队列

  1. 普通阻塞:sleep(time),可被打断。调用thread.interrupt()方法,可以打断阻塞状态,抛出异常。
  2. 等待队列:wait()方法被调用,也是一种阻塞状态,只能由notify唤醒。无法打断。
  3. 锁池队列:无法获取锁标记。不是所有的锁池队列都可被打断。

使用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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值