线程池(二)

1.实现时间格式化

实现1000个日期的时间格式化

package threadpool_5_26;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo04 {
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
                10,60, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));

        for (int i = 1; i < 1001; i++) {
            final int k = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Date date = new Date(k * 1000);
                    myFormatTime(date);
                }
            });
        }
    }

    public static void myFormatTime(Date date){

        String ret = simpleDateFormat.format(date);
        System.out.println("时间:"+ret);
    }
}

运行结果:
在这里插入图片描述
结果分析:

SimpleDateFormat是线程不安全的

源码如下:
在这里插入图片描述
分析:

  1. 线程 1 执⾏了 calendar.setTime(date) ⽅法,将⽤户输⼊的时间转换成了后⾯格式化时所需要的时 间;
  2. 线程 1 暂停执⾏,线程 2 得到 CPU 时间⽚开始执⾏;
  3. 线程 2 执⾏了 calendar.setTime(date) ⽅法,对时间进⾏了修改;
  4. 线程 2 暂停执⾏,线程 1 得出 CPU 时间⽚继续执⾏,因为线程 1 和线程 2 使⽤的是同⼀对象,⽽时 间已经被线程 2 修改了,所以此时当线程 1 继续执⾏的时候就会出现线程安全的问题了。

正常情况下:
在这里插入图片描述
非线程安全情况下:
在这里插入图片描述

2.解决线程不安全

2.1加锁机制(synchronized,lock)

缺点:
需要加锁排队执行,消耗时间较长

2.2使用局部变量

缺点:
每次执行任务都需要重新创建私有变量

2.3使用ThreadLocal

以1000个任务10个线程的示例来说,使用ThreadLocal就是创建10个SimpleDateFormat对象

3.ThreadLocal的基本使用

3.1五个方法
  • get():返回此线程局部变量的当前线程副本中的值。
  • set(T value): 将此线程局部变量的当前线程副本中的值设置为指定值。
  • remove():移除此线程局部变量当前线程的值。
  • initialValue():初始化方法,实例方法
  • withInitial:初始化方法,静态方法
3.2get,set,remove
package threadpool_5_27;

import java.util.concurrent.ThreadFactory;

public class Demo01 {

    private static ThreadLocal<String> threadLcal = new ThreadLocal<>();

    public static void main(String[] args) {

        //定义公共的任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String tname = Thread.currentThread().getName();
                System.out.println(tname +" 设置值:" +tname);
                try {
                    threadLcal.set(tname);

                    //执行ThreadLocal打印
                    printTreadLocal();
                }finally {
                    threadLcal.remove();
                }
            }
        };

        Thread t1 = new Thread(runnable,"线程1");
        t1.start();

        Thread t2 = new Thread(runnable,"线程2");
        t2.start();
    }

    private static void printTreadLocal() {
        String ret = threadLcal.get();
        System.out.println(Thread.currentThread().getName()+" 中取值"+ret);
    }
}

运行结果:
在这里插入图片描述

3.3initialValue方法
package threadpool_5_27;

import java.util.Random;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo03 {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal(){
        @Override
        protected Integer initialValue() {
            int num = new Random().nextInt(10);
            System.out.println("执行initialValue方法: "+num);
            return num;
        }
    };

    public static void main(String[] args) {
        //创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                1,60, TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(1000));


        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                int result = threadLocal.get();
                System.out.println(Thread.currentThread().getName()+" 得到结果1:"+result);
            }
        });

        threadPoolExecutor.submit(new Runnable() {
            @Override
            public void run() {
                int result = threadLocal.get();
                System.out.println(Thread.currentThread().getName()+" 得到结果2:"+result);
            }
        });
    }
}

运行结果:
在这里插入图片描述

3.4withInitial方法:
package threadpool_5_28;

import java.util.function.Supplier;

public class Demo01 {

    //创建并初始化ThreadLocal
    private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
        @Override
        public String get() {
            System.out.println("执行了withInitial方法!");
            return Thread.currentThread().getName() + ": Java";
        }
    });


    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                String ret = threadLocal.get();
                System.out.println(ret);
            }
        },"线程1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                String ret = threadLocal.get();
                System.out.println(ret);
            }
        });
        t2.start();
    }
}

在这里插入图片描述
面试题:ThreadLocal的初始化方法在什么情况下不执行?
答:ThreadLocal在执行get方法的时候,才会去判断并调用初始化方法

3.5使用ThreadLocal来实现1000个日期的时间格式化

代码示例:

package threadpool_5_28;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo05 {

    //创建并初始化ThreadLocal
    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue() {
            System.out.println("执行了initial方法");
            return new SimpleDateFormat("mm:ss");
        }
    };

    public static void main(String[] args) {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
                10,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));

        for (int i = 1; i < 1001; i++) {
            int k = i;
            threadPoolExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    Date date = new Date(1000*k);
                    myDateTime(date);
                }
            });
        }
    }

    public static void myDateTime(Date date){
        String ret = threadLocal.get().format(date);
        System.out.println("时间:"+ret);
    }
}

运行结果:
在这里插入图片描述
ThreadLocal的使用场景:
1.解决线程安全问题
2.实现线程级别的数据传递(可以实现一定程度的解耦)

4.ThreadLocal缺点

4.1不可继承性

主线程设置了值,子线程访问不了(子线程不能继承主线程的私有变量)
代码示例:

package threadpool_5_29;

public class ThreadLocalDemo01 {

    //创建threadlocal
    private static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) {

        threadLocal.set("Java");

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());
            }
        });
        t1.start();

    }
}

运行结果:

Thread-0 null

解决方案:
使用InheritableThreadLocal这个类

代码示例:

package threadpool_5_29;

public class ThreadLocalDemo02 {

    private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal();

    public static void main(String[] args) {

        inheritableThreadLocal.set("Java");

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" "+inheritableThreadLocal.get());
            }
        });
        t1.start();
    }
}

运行结果:

Thread-0 Java

注意事项:
即使使用InheritableThreadLocal也不能实现并列线程之间的数据传输(数据设置和获取)

4.2脏读

脏读定义
在一个线程中读取到了不属于自己的信息
原因:
线程池复用了线程,和这个线程相关的静态属性也复用,所以导致了脏读

代码示例:

package threadpool_5_29;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo05 {

    private  static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static class MyThreadLocal extends Thread{
        private static boolean flag = false;

        @Override
        public void run() {
            String name = this.getName();
            if(!flag){
                threadLocal.set(name);
                System.out.println(name+" 设置了 "+name);
                flag = true;
            }
            System.out.println(name+" 得到了 "+threadLocal.get());
        }

    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,
                1,0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(100));

        for (int i = 0; i < 2; i++) {
            threadPoolExecutor.submit(new MyThreadLocal());
        }
    }
}

运行结果:
在这里插入图片描述

解决方案:
1.避免使用静态变量
2.使用完之后,执行remove操作

4.3内存溢出

什么是内存溢出:
当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就是内存溢出

代码示例:

package threadpool_5_29;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo06 {

    private static ThreadLocal<MyThreadLocal> threadLocalThreadLocal = new ThreadLocal<>();

    static class MyThreadLocal{

        private byte [] bytes = new byte[1*1024*1024];
    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));


        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    MyThreadLocal myThreadLocal = new MyThreadLocal();
                    threadLocalThreadLocal.set(myThreadLocal);
                    System.out.println(Thread.currentThread().getName()+" 设置了值");
                }
            });
        }
    }
}

运行结果:
在这里插入图片描述

内存溢出的原因:
1.线程池是长生命周期
2.Thread—>ThreadLocalMap—>Entry key ,value(1mb资源)(垃圾回收器就不会回收value资源)

垃圾回收可回收的资源相关的4种类型:
1.强引用:Object object = new Object(),即使发生OOM也不会进行垃圾回收
2.软引用:它的引用关系仅次于强引用。如果内存够用,那么垃圾回收器不会考虑回收此引用,将要发生此OOM的时候才会回收此引用
3.弱引用:不管内存够不够用,下一次回收都会将此引用的对象回收掉
4.虚引用:创建即回收,它可以触发一个垃圾回收的回调

为什么ThreadLocal会将key设置为弱引用?
答:为了最大程度的避免OOM

内存溢出解决方法:
调用remove方法

package threadpool_5_29;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo08 {

    private static ThreadLocal<MyThreadLocal> threadLocalThreadLocal = new ThreadLocal<>();

    static class MyThreadLocal{

        private byte [] bytes = new byte[1*1024*1024];
    }

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,
                0, TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));


        for (int i = 0; i < 5; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try{
                        MyThreadLocal myThreadLocal = new MyThreadLocal();
                        threadLocalThreadLocal.set(myThreadLocal);
                        System.out.println(Thread.currentThread().getName()+" 设置了值");
                    }finally {
                        //移除变量,防止OOM
                        threadLocalThreadLocal.remove();
                    }

                }
            });
        }
    }
}

运行结果:
在这里插入图片描述

5.链表与红黑树

HashMap与ThreadLocalMap解决冲突:
HashMap:链表法(链表+红黑树)
ThreadLocalMap:开放寻址法

链表升级为红黑树的条件:
(1)链表长度大于8
(2)数组长度大于64

红黑树降级为链表:
链表长度小于6

为什么从链表升级为红黑树
加速查询性能

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值