JUC第四课——LockSupport、三道面试题、源码阅读原则

LockSupport

用于创建锁和其他同步类的基本线程阻塞(不释放锁)原语。

基本操作

package character04;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * @author laimouren
 */
public class LockSupport01 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                if(i == 5){
                    //意思是阻塞线程
                    LockSupport.park();
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        try {
            TimeUnit.SECONDS.sleep(8);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after 8 seconds");
        //unpark()可以在park()之前调用,使park()方法失效
        //相比wait()notify()更灵活
        LockSupport.unpark(t);
    }
}

小知识:object.notify()不释放锁,wait()方法被唤醒了之后仍需要锁
即当一个线程调用object.notify()的时候,另一个线程并不会马上执行,因为object对象的锁还是当前线程所持有,需要当前线程执行完或wait()

面试题

1. 实现一个容器,提供两个方法,add,size

写两个线程,线程1添加10个元素到容器中,线程2 监控元素个数,当个数到5个的时候,线程2给出提示并结束
可以使用两个notify()+wait()或者两个countdownLatch
下面提供使用两次LockSupport.unpark()+park();

public class Test2 {
	
	//实现一个容器 提供两个方法  add和size
	//写两个线程,线程1添加10个元素到容器中,线程2实现监控容器中元素的个数,当个数到5个时,线程2给出提示并且结束
	
	//实现原理 LockSupport park(阻塞线程)和unpark(Tread)(唤醒某个线程)
	//List list = new ArrayList<>(); //定义一个容器 (非线程安全)
	volatile List list =Collections.synchronizedList(new LinkedList<>());//定义一个线程安全的list
	public void add(Object o){ this.list.add(o); } //容器中添加对象
	public int size(){return this.list.size(); } //获取容器大小
	
	static Thread t2 =null;
	static Thread t1 = null;
	public static void main(String[] args) {
		Test2 test = new Test2();
		
		//线程1 负责监控容器长度是否等于5
		t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("t2 启动");
				LockSupport.park(); //阻塞t2线程
				if(test.size() == 5){
					System.out.println("t2 结束");
				}
				LockSupport.unpark(t1);//唤醒线程t1  t2线程结束
			}
		},"t2");

	
		t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
					
					System.out.println("t1 启动");
					for (int i = 0; i < 10; i++) {
						test.add(i);
						System.out.println("add "+i);
						if(test.size() == 5){
							//LockSupport.park(); //阻塞线程
							LockSupport.unpark(t2);//唤醒线程t2
							LockSupport.park(); //阻塞t1线程
						}
						
					}
				}
				
		},"t1");
		
		t2.start();//启动线程
		t1.start();//启动线程
		
	}
}

2. 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用

synchronized方法:

package character04;


import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyContatiner1<T> {
    final private LinkedList<T> list = new LinkedList<>();
    final private int MAX = 10;//最多10个元素
    private int count = 0;

    public synchronized void put(T t){
        while (list.size() == MAX){
            try{
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        list.add(t);
        count++;
        //通知消费者进行消费
        this.notifyAll();
    }

    public synchronized T get(){
        T t = null;
        while (list.size() == 0){
            try {
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        t = list.removeFirst();
        count--;
        //通知生产者进行生产
        this.notifyAll();
        return t;
    }

    public static void main(String[] args) {
        MyContatiner1<String> container = new MyContatiner1<>();
        //启动消费者线程
        for (int i = 0;i < 10;i++){
            new Thread(()->{
                for(int j = 0;j < 5; j++){
                    System.out.println(container.get());
                }
            },"consumer"+i).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(Thread.currentThread().getName()+"->"+j);
                }
            },"producer"+i).start();
        }
    }
}

缺陷就是消费者线程可能错误的唤醒消费者线程,生产者线程可能错误的唤醒生产者线程,导致争抢严重

condition方法:

package com.example.demo.test;
 
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class MyContainer<T> {
	final private LinkedList<T> list = new LinkedList<>();
	final private int MAX = 10;//最多10个元素
 
	private Lock lock = new ReentrantLock();
	//Condition意思是一个等待队列,新建了两个等待队列
	private Condition producer = lock.newCondition();//生产者条件
	private Condition consumer = lock.newCondition();//消费者条件
 
	public void put(T t){
		try {
			lock.lock();
			while (list.size() == MAX){
				producer.await();//在这个条件下,生产者线程全部等待
			}
			list.add(t);
			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();
			producer.signalAll();//通知生产者可以进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		return t;
	}
 
	public static void main(String[] args) {
		MyContainer<String> container = new MyContainer<>();
		//启动消费者线程
		for (int i = 0;i < 10;i++){
			new Thread(()->{
				for(int j = 0;j < 5; j++){
					System.out.println(container.get());
				}
			},"consumer"+i).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(Thread.currentThread().getName()+"->"+j);
				}
			},"producer"+i).start();
		}
	}
}

3. 用两个线程顺序打印A1B2C3D4…Z26

源码阅读原则

读源码必须理解别人的思路;所以需要数据结构基础设计模式
读源码,先读骨架

  1. 跑不起来不读(不要静态的点方法进去看实现,而是通过debug一步步得到具体的实现是哪个)
  2. 解决问题就好——目的性
  3. 一条线索到底
  4. 无关细节略过
  5. 一般不读静态
  6. 一般动态读法

ReentrantLock源码分析

模板方法设计模式
在这里插入图片描述
通过debug来看具体实现

AQS(CLH)初了解

为什么核心是CAS+volatile
因为核心变量state是用volatile修饰的,由子类指定含义
使用双向链表,节点装的是线程thread
以ReentrantLock为例,获得锁的过程是,先看state是否为0,如果是0,说明当前没有线程使用锁,当前线程就使用CAS的方式获得锁,并返回true
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值