文章目录
回顾:
ThreadLocal使用方法
1.set() : 将变量存放到线程中
2.get() : 从线程中取得私有变量
3.remove() : 从线程中移除私有变量(脏读,内存溢出)在任何场景下都有意义
4.initialValue:初始化
5.withinitialValue:初始化
ThreadLocal使用场景
1,线程安全问题
2,线程级别的数据传递
ThreadLocal缺点:
1.不能实现父子线程间的数据传递
2.脏数据---->ThreadLocal +线程池(复用)
3.内存溢出
分析内存溢出:
线程池是长生命周期的,而线程是执行完任务就结束了(线程相关的资源都会释放掉)
HashMap和ThreadLocalMap区别:
HashMap使用的是链表法,ThreadLocalMap使用的是开放寻址法。
开放寻址法:hash之后得到位置i,然后判断i是否为空,如果为空直接插入,不为空一直向后查找,直到找到为空的位置插入
为什么这样实现?
开放寻址法它的特点和使用场景是数据量比较少的情况下性能更好,而HashMap里面存储的数据通常情况下是比较多,这个时候使用开放寻址法效率就比较低了。
链表---->红黑树
升级:
1.链表长度大于8;
2.数组长度大于64;
降级:链表长度小于6的时候
Thread----->ThreadLocalMap------>Entry[ ]------->key , value(value是强引用的)
ThreadLocal(横店本地人) ThreadLocal(剧本扮演者)
Thread(数组)---->ThreadLocalMap (本地人的房子) ---->Entry(剧本)---->key , value(剧本的实际角色)
Java引用类型4种类型:
1.强引用Object Obj = new Object();即使发生OOM也不会进行垃圾回收。
2.软引用,它的引用关系仅次于强引用。如果内存够用,那么垃圾回收不会考虑回收次引用,将要发生OOM的时候才会回收此引用。
3.弱引用:不管内存够不够用,下一次垃圾回收都会将此引用的对象回收掉。
4.虚引用:创建既回收,它可以触发一个垃圾回收的回调。
为什么将ThreadLocal中的key设置为弱引用?
答:为了最大程度的避免OOM
为什么会发生OOM?
ThreadPool长生命周期的,----->thread不释放,----->thread拥有ThreadLocalMap不释放------>Entry[ ]不释放----->Entry里面有------->key , value(value强引用)即使发生OOM也不会进行垃圾回收。
解决Thread Local的内存溢出?
使用remove()。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadLocalMain83 {
//1m大小的对象
static class OOMObject{
private byte[] bytes=new byte[1*1024*1024];
}
static ThreadLocal<OOMObject> threadLocal=new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor=new ThreadPoolExecutor(10,
10,0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
for (int i = 0; i <5 ; i++) {
int finalI=i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
OOMObject oomObject = new OOMObject();
//set threadLocal
System.out.println("任务:" + finalI + "执行了");
threadLocal.set(oomObject);
//不用对象了
oomObject = null;
}finally {
//解决内存溢出问题
threadLocal.remove();
}
}
});
Thread.sleep(200);
}
}
}
提升程序的性能:
1.多线程
2.单例模式-------------->设计模式:
- 1.单例模式(手写)
手写代码:
链表相关:反转 简单的排序算法 死锁 单例模式 - 2.工厂模式(简单工厂,抽象工厂)
- 3.模板模式
单例模式:整个程序的运行中只存储一个对象
单例模式:饿汉方式(上来直接创建对象), 线程安全的
懒汉方式
饿汉方式:上来直接创建对象
线程安全的,不用加锁也是安全的
public class ThreadMain84 {
static class Singleton {
// 1.创建私有的构造函数(防止其他类直接创建)
private Singleton() {
}
// 2.定义私有变量(线程安全)
private static Singleton singleton = new Singleton();
// 3.提供公共的获取实例的方法
public static Singleton getInstance() {
return singleton;
}
}
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton);
}
}
饿汉缺点分析:程序启动之后就会创建,但是创建完了之后又可能不会使用,从而浪费了系统资源。
懒汉方式
懒汉方式:当程序启动之后,并不会进行初始化,而是在什么时候调用什么时候初始化。
非安全的单例模式-------懒汉方式
/**
* 懒汉方式 v1 不安全
*/
public class ThreadMain85 {
static class Singleton{
//1,创建一个私有的构造方法(方法其他地方直接初始化)
private Singleton(){
}
//2.创建一个私有类对象
private static Singleton singleton=null;
//3.提供统一访问入口(方法)
public static Singleton getInstance(){
if(singleton==null){
//第一次访问
singleton=new Singleton();
}
return singleton;
}
public static void main(String[] args) {
//创建第一个对象
Singleton s1=Singleton.getInstance();
//创建第二个对象
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
}
}
}
线程不安全的解决方案:
方案一:性能不佳
方案二: 双重效验锁
/**
* 懒汉方式 v3
*/
public class ThreadMain88 {
static class Singleton{
//1,创建一个私有的构造方法(方法其他地方直接初始化
private Singleton(){
}
//2.创建一个私有类对象(volatile)
private static volatile Singleton singleton=null;
//3.提供统一访问入口(方法)
public static Singleton getInstance() {
if(singleton==null) {
synchronized (Singleton.class) {
if (singleton == null) {
//第一次访问
singleton = new Singleton();
}
}
}
return singleton;
}
private static Singleton s1=null;
private static Singleton s2=null;
public static void main(String[] args) throws InterruptedException {
//创建新线程执行任务
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s1= Singleton.getInstance();
}
});
t1.start();
//使用主线程执行任务
s2= Singleton.getInstance();
//等待t1执行完
t1.join();
System.out.println(s1==s2);
}
}
}
有问题:
1.先在内存中开辟空间(买房)
2.初始化(装修)
3.将变量singleton指向内存区域(入住)
CPU指令优化(指令重排序):所以一定要加 volatile
指令重排序(前)1------->2-------->3
指令重排序(后)1------->3-------->2
自定义阻塞队列:
生产者消费者模型——————生产者生产数据,消费者消费生产者生产的数据。
生产数据------添加数据------当数据量满了之后,不要尝试给队列添加数据了,而是阻塞等待------sleep(time)不适合wait()wait() / notify /notifyAll
消费者消费数据------取出数据-------当队列为空的时候,就阻塞等待
自定义阻塞队列------->链表 数组
/**
* 自定义阻塞队列(使用数组)
*/
import java.util.Random;
public class ThreadMain89 {
static class MyBlockingQueue {
private int[] values;//实际存储数据的数组
private int first;//队首
private int last;//队尾
private int size;//实际队列元素的大小
public MyBlockingQueue(int initial) {
//初始化变量
values = new int[initial];
first = 0;
last = 0;
size = 0;
}
//添加元素(队尾)
public void offer(int val) {
synchronized (this) {
//判断边界值
if (size == values.length) {
//队列已满
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//添加元素到队尾
values[last++]=val;
size++;
// 判断是否为最后一个元素
if(last==values.length){
last=0;
}
// 尝试唤醒消费者
this.notify();
}
}
//查询方法
public int poll() {
int result = -1;
synchronized (this) {
//判断边界值
if (size == 0) {
//队列为空,阻塞等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取元素
result=values[first++];
size--;
//判断是否为最后一个元素
if(first==values.length){
first=0;
}
//尝试唤醒生产者
this.notify();
}
return result;
}
}
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue=new MyBlockingQueue(100);
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//每隔500毫秒生产一条数据
while (true){
int num=new Random().nextInt(10);
System.out.println("生产了随机数:"+num);
myBlockingQueue.offer(num);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
//创建消费者
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
while (true){
int result=myBlockingQueue.poll();
System.out.println("消费量数据:"+result);
}
}
});
t2.start();
}
}