程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

多线程学习9

发布于2021-05-29 20:52     阅读(1480)     评论(0)     点赞(25)     收藏(4)


回顾:

ThreadLocal使用方法
1.set(T) : 将变量存放到线程中
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();即使发生00M也不会进行垃圾回收。
2.软引用,它的引用关系仅此于强引用。如果内存够用,那么垃圾回收不会考虑回收次引
用,将要发生00M的时候才会回收此引用。
3.弱引用:不管内存够不够用,下一次垃圾回收都会将此引用的对象回收掉。
4.虚引用:创建既回收,它可以触发一个垃圾回收的回调。

为什么将ThreadLocal中的key设置为弱引用?

答:为了最大程度的避免OOM
为什么会发生OOM?
ThreadPool长生命周期的,----->thread不释放,----->thread拥有ThreadLocalMap不释放------>Entry[ ]不释放----->Entry里面有------->key , value(value强引用)即使发生00M也不会进行垃圾回收。

解决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();
    }
}

原文链接:https://blog.csdn.net/qq_54850622/article/details/117262232



所属网站分类: 技术文章 > 博客

作者:niceboty

链接:http://www.javaheidong.com/blog/article/207452/43e937db0e8cd324283e/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

25 0
收藏该文
已收藏

评论内容:(最多支持255个字符)