炙热的我们-Java基础篇(4.4)

4、Java并发编程

→ ThreadLocal

ThreadLocal是什么?

从名字可以看到ThreadLocal是线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

使用场景:
1、进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,session会话管理。

ThreadLocal怎么用?
验证一下:

public class ThreadLocalTest{
	public static void main(String[] args){
		// 创建对象
		ThreadLocal<String> local = new ThreadLocal();
		// 新建一个随机数类
		Random random = new Random();
		// 使用Java8的Stream新建5个线程
		IntStream.range(0,5).forEach(a -> new Thread(() -> {
			//为每一个线程设置相应的local值	
			local.set(a + " " + random.nextInt(10));
			System.out.println("线程和local值分别是:" + local.get());
			try{
				TimeUnit.SECONDS,sleep(1);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}).start());
	} 
}
/* 线程和local值分别是:0  6
   线程和local值分别是:1  4
   线程和local值分别是:2  3
   线程和local值分别是:4  9
   线程和local值分别是:3  5*/

从结果我们可以看到,每一个线程都有各自的local值,我们设置了一个休眠时间,就是为了另外一个线程也能够及时的读取当前的local值。

所以ThreadLocal在数据库连接场景中常用,比如一个客户端频繁的使用数据库,就需要建立多次连接和关闭,我们的服务器可能吃不消,这时候最好使用ThreadLocal,因为ThreadLocal在每个线程中对连接会创建一个副本,且在线程内部任何时候都可以使用,线程之间互不影响,这样就不存在线程安全问题,也不会严重影响程序执行性能。

ThreadLocal源码分析
(1)每个Tread维护着一个ThreadLocalMap的引用。
(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来存储。
(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap。
(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中。
(5)在进场get之前,必须先进行set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法。
(6)ThreadLocal本身并不存储值,它只是作为一个key让线程从ThreadLocalMap中获取value。

对于ThreadLocal来说关键就是内部的ThreadLocalMap。

ThreadLocal注意要点——内存泄漏
ThreadLocal模型
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。

1、Thread中有一个map,就是ThreadLocalMap

2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
.

→ 写一个死锁的程序

写一个简单的死锁程序:

public class LockDemo{
    
    public static void main(String[]  args){
        new Thread(()-> {
            try{
                System.out.println("Thread1 is running");
                synchronized(LockDemo.class){
                    System.out.println("thread is block obj1");
                    
                    Thread.sleep(3000);
                    synchronized(Object.class){
                        System.out.println("thread is block ojb2");
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }).start();
        
        new Thread(()-> {
            try{
                System.out.println("Thread2 is running");
                synchronized(Object.class){
                    System.out.println("thread2 is block obj1");
                    
                    Thread.sleep(3000);
                    synchronized(LockDemo.class){
                        System.out.println("thread2 is block obj2");
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }).start();
    }
}

结果分析:当程序开始执行时,创建两个线程,分别执行不同的synchronized 方法块;并且都在Thread.sleep(1000);处停下来,此时当线程1先sleep结束后;准备用Main.class开 synchronized 这把锁进入 synchronized (Main.class) 方法块,但是发现线程2还没有苏醒,所以 线程1就等着线程2执行完毕,释放出 Main.class 钥匙,这时候 线程2苏醒了,准备拿String.class 这把钥匙,去开synchronized 这把锁,进入synchronized (String.class) 方法块;但是发现线程1 也在等着 线程2释放 Main.class钥匙,所以就导致线程1等着线程2 释放 Main.class钥匙, 线程2等着线程1 释放 String.class 这把钥匙,所以这个程序就僵持住了,成了一个死锁的程序。

【参考链接】https://blog.youkuaiyun.com/weixin_41528317/article/details/83063994
.

→ 写代码来解决生产者消费者问题

一个示例demo。
资源:

/**
 * 资源
 */
class Clerk{
    // 当前资源数量
    private int product = 0;
    // 创建锁对象
    private Lock lock = new ReentrantLock();
    private Condition = new lock.newCondition();
    
    // 生产资源
    public void get(){
        lock.lock();
        try{
            // 资源满了就进入阻塞状态  jdk建议使用while循环  避免虚假唤醒  所以要再次确认
            while(product >=1){
                System.out.println("产品已满");
                try{
                    System.out.println("生产者进入等待");
                    condition.await();
                }catch(InterruptedException e){
                    e.printStckTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+ ":" + ++product);
            condition.singleAll();
        }finally{
            lock.unlock();
        }
    }
    
    // 消费资源
    public void sale(){
        // 开启锁
        lock.lock();
        try{
            while(product <= 0){
                System.out.println("缺货");
                try{
                    condition.await();
                }catch(InterruptedException e){
                    e.printStckTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+ ":" + --product)
            condition.singleAll();
        }finally{
            lock.unlock();
        }
    }
}

生产者:

/**
 * 生产者
 */
class Producer implements Runnable{
    private Clerk clerk;
    public Producer (Clerk clerk){this.clerk = clerk;}
    
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.get();
        }
    }
}

消费者:

class Consumer implements Runnable{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

测试类:

public class TestProductorAndConsumer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(producer, "生产者A").start();
        new Thread(consumer, "消费者B").start();

        new Thread(producer, "生产者C").start();
        new Thread(consumer, "消费者D").start();
    }
}

运行结果:
缺货
缺货
生产者C:1
产品已满
生产者进入等待
消费者B:0
缺货
缺货
生产者A:1
消费者B:0
缺货
缺货
生产者C:1
消费者B:0
缺货
缺货
生产者A:1
消费者B:0
缺货
缺货
生产者C:1
消费者B:0
缺货
生产者A:1
消费者D:0
缺货
生产者C:1
消费者D:0
缺货
生产者A:1
消费者D:0
缺货
生产者C:1
消费者D:0
缺货
生产者A:1
消费者D:0

→ 并发包

Thread、Runnable、Callable、ReentrantLock、ReentrantReadWriteLock、Atomic*、Semaphore、CountDownLatch、ConcurrentHashMap、Executors

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值