public class ReentrantLock5 extends Thread{
private static Lock lock = new ReentrantLock(true);
// true 获得公平锁
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + i+" 获得锁");
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock5 rl=new ReentrantLock5();
Thread th1=new Thread(rl);
Thread th2=new Thread(rl);
th1.start();
th2.start();
}
}
ReentrantLock还可以指定为公平锁什么是公平锁,什么是不公平锁,假设很多个线程访问同一份资源的时候都要锁定, 其中某一个线程A如果拿到了,他占有这把锁之后,其他人是都访问不了的,都得在那等着;什么时候一旦线程A释放了这把锁,那么剩下的线程中哪个线程得到这把锁,这事说不定,这个要看线程调度器自己去选哪个了,所以这叫竞争锁;也就是说等待的线程里面是没 有公平性可言的;不过这种效率比较高,线程调度器不用计算到底哪个线程等的时间更长,所以默认的synchronized是非公平锁; 公平锁就是,谁等的时间长让谁得到那把锁。
面试题:生产者消费者程序:
要求:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用
public class ResumeContainer<T> {
private List<T> lists = new ArrayList<>();
final private LinkedList<T> list = new LinkedList<>();
private int max_ = 10;
private int cnt = 0;
public synchronized void put(T t){
while (list.size() == max_){// 对比这里if 和 while 带来的区别
try {
System.out.println("list size --------------------: " + list.size());
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
System.out.println("producer put " + t);
cnt++;
this.notifyAll();// 通知消费者前来消费
}
public synchronized T get(){
T t = null;
while (list.size() == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = list.remove(0);
cnt --;
this.notifyAll();
return t;
}
public static void main(String[] args){
ResumeContainer<Object> container = new ResumeContainer<>();
// 消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
System.out.println("consumer get "+container.get());
}
}, "get ").start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 2; i++) {
new Thread(() ->{
for (int j = 0; j < 25; j++) {
container.put(j);
}
}, "put ").start();
}
}
}
使用while
假设容器中已经满了,如果用的是if,这个线程A发现list.size()==max已经满了,就this.wait()住了;如果容器中被拿走了元素,线程A被叫醒了,它会从this.wait()开始继续往下运行,准备执行lists.add(),可是它被叫醒了之后还没有往里扔的时候,另外一个线程往list里面扔了一个,线程A拿到锁之后不再进行if判断,而是继续执行lists.add()就会出问题了;如果用while,this.wait()继续往下执行的时候需要在while中再检查一遍,就不会出问题。
改进
public class ResumeContainer<T> {
private List<T> lists = new ArrayList<>();
final private LinkedList<T> list = new LinkedList<>();
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
private int max_ = 10;
private int cnt = 0;
public void put(T t){
try{
lock.lock();
while (list.size() == max_){
System.out.println("=============== list full ================");
producer.await();
}
System.out.println(String.format("=============== put %s ================",String.valueOf(t)));
list.add(t);
cnt++;
consumer.signalAll();// 通知消费者线程进行消费
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public T get(){
T t = null;
try{
lock.lock();
while (list.size()==0){
consumer.await();
}
t = list.removeFirst();
System.out.println(String.format("=============== get %s ================",String.valueOf(t)));
cnt++;
producer.signalAll();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
public static void main(String[] args){
ResumeContainer<Object> container = new ResumeContainer<>();
// 消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
container.get();
}
}, "get ").start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 2; i++) {
new Thread(() ->{
for (int j = 0; j < 25; j++) {
container.put(j);
}
}, "put ").start();
}
}
}
使用wati和notify写线程程序的时候就像使用汇编语言一样,写起来会比较费劲,使用lock和condition好处在于可以精确的通知那些线程被叫醒,哪些线程不必被叫醒,这个效率显然要比notifyAll把所有线程全叫醒要高很多。
线程局部变量
public class ThreadLocal {
volatile static Person p = new Person();
public static void main(String[] args){
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.name);
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.name = "wangwu";
}).start();
}
}
class Person{
public String name = "zhangsan";
}
现在这两个线程是互相影响的;第二个线程改了名字之后,第一个线程就能读的到了;有的时候就想线程2的改变,不想让线程1知道ThreadLocal线程局部变量 * * ThreadLocal是使用空间换时间,synchronized是使用时间换空间
public class ThreadLocal1 {
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args){
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tl.get());
}).start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
tl.set(new Person());
System.out.println(tl.get());
}).start();
}
}
class Person{
public String name = "zhangsan";
}
输出:
Basic.Person@2ee37501
null
上面代码的输出,在两个线程里面对person进行了修改,二者得输出都是不一样的,也就是同一个变量在俩个线程中是隔离的ThreadLocal的意思就是,tl里面的变量,自己的线程自己用;你别的线程里要想用的话,不好意思你自己往里扔;不能用我线程里面放的东西;相当于每个线程都有自己的变量,互相之间不会产生冲突;可以理解为person对象每个线程里面拷贝了一份,改的都是自己那份,都是自己线程本地的变量,所以空间换时间;ThreadLocal在效率上会更高一些;