Java高并发编程实战(一)

线程池

线程和进程的区别是什么?

线程的生命周期

线程池的组成部分

  • 任务队列
  • 拒绝策略(抛出异常,直接丢弃,阻塞,临时队列)
  • 初始大小 init(最少线程个数) min
  • 活跃大小 active
  • 最大线程个数 max

关系:min<=active<=max

自定义线程池


设计模式引出的多线程问题

  • 饿汉式单例设计
public class SingletonObject1 {
    /**
     * can't lazy load
     */
    private static final SingletonObject1 instance = new SingletonObject1();

    private SingletonObject1() {
    }
    public static SingletonObject1 getInstance(){
        return instance;
    }
}
  • 懒加载单例设计模式
public class SingletonObject2 {
    private static SingletonObject2 instance;

    private SingletonObject2() {
    }

    public static SingletonObject2 getInstance() {
        if (instance == null) {
            instance = new SingletonObject2();
        }
        return instance;
    }
}
  • 同步的懒加载单例设计模式
public class SingletonObject3 {
    private static SingletonObject3 instance;

    private SingletonObject3() {
    }

    public synchronized static SingletonObject3 getInstance() {
        if (instance == null) {
            instance = new SingletonObject3();
        }
        return instance;
    }
}
  • DCL(Double Check Lock)
- public class SingletonObject4 {
    private static SingletonObject4 instance;

    private SingletonObject4() {
    }

    public static SingletonObject4 getInstance() {
        if (instance == null) {
            synchronized (SingletonObject4.class){
                if (instance == null){
                    instance = new SingletonObject4();
                }
            }
        }
        return instance;
    }
}

会出现空指针异常的问题,因为虽然成功的保证了单例原则,当其余的线程返回这个对象的时候,该类中的实例域可能还没有初始化完成

  • volatile - DCL
public class SingletonObject5 {
    private static volatile SingletonObject5 instance;

    private SingletonObject5() {
    }

    public static SingletonObject5 getInstance() {
        if (instance == null) {
            synchronized (SingletonObject5.class){
                if (instance == null){
                    instance = new SingletonObject5();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类-单例
public class SingletonObject6 {
    private static volatile SingletonObject6 instance;

    private SingletonObject6() {
    }

    private static class InstanceHolder {
        private static final SingletonObject6 instance = new SingletonObject6();
    }

    public static SingletonObject6 getInstance() {
        return InstanceHolder.instance;
    }
}
  • 枚举-单例
   public class SingletonObject7 {
    private SingletonObject7() {
    }

    private enum Singleton {
        INSTANCE;

        private final SingletonObject7 instance;

        Singleton() {
            instance = new SingletonObject7();
        }

        public SingletonObject7 getInstance() {
            return instance;
        }
    }

    public static SingletonObject7 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    public static void main(String[] args) {
        Thread[] thread = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(() -> {
                System.out.println(SingletonObject7.getInstance());
            });
            thread[i].start();
        }
    }
}

wait set

public class WaitSet {
    private static final Object LOCK = new Object();

    /**
     * 1、所有的对象都会有一个wait set,用来存放调用了该对象wait方法之后进入BLOCK状态的线程
     * 2、线程被notify之后,不一定立即得到执行
     * 3、线程从wait set中被唤醒顺序不一定是 FIFO
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        IntStream.rangeClosed(1, 10).forEach(i -> new Thread(String.valueOf(i)) {

            @Override
            public void run() {
                synchronized (LOCK) {
                    try {
                        Optional.of(Thread.currentThread().getName() + " will come to wait set.").ifPresent(System.out::println);
                        LOCK.wait();
                        Optional.of(Thread.currentThread().getName() + " will leave to wait set.").ifPresent(System.out::println);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start());

        Thread.sleep(3000);

        IntStream.rangeClosed(1, 10).forEach(i -> {
            synchronized (LOCK) {
                LOCK.notify();
            }
        });
    }
}

并发编程中三个比较重要的概念

  • 原子性 Atom
    保证一个操作或者多个操作要么都成功,要么都失败,中间不能由于任何的因素中断

  • 可见性

  • 有序性(顺序性)
    重排序只要求最终一致性
    在这里插入图片描述

JMM怎么保证原子性

对基本数据类型的变量读取和赋值是保证了原子性的(long,double在32位的操作系统时可能不能保证原子性)

a =10 ->原子性
b=a->不满足:1.read a; 2.assign b;
c++,c=c+1;->不满足:1.read c; 2.add; 3.assign to c;

JMM怎么保证可见性

使用volatile关键字保证可见性

JMM怎么保证有序性

happens-before relationship

  • 代码的执行顺序,编写在前面的发生在编写在后面的(多线程不能保证->指令重排序)
  • unlock必须发生在lock之后
try{
	lock.lock();
} catch(Exception e){
	lock.u nlock();	
}
synchronized(obj){ ---1---
}

--unlock--2---
12之前
  • volatile修饰的变量,对一个变量的写操作先行发生于对这个变量的读操作
  • 传递规则,操作A先于B,B先于C,那么A肯定先于C
  • 线程启动规则,start方法肯定先于线程run()
  • 线程中断规则,interrupt这个动作,必须发生在捕获该动作之后
  • 对象销毁规则,初始化必须发生在finalize之前
  • 线程终结规则,所有操作必须发生在线程死亡之前

volatile关键字

  • 一旦一个共享变量被volatile修饰,具备两层语义:
    1、保证了不同线程间的可见性(线程不经过本地内存,都从主内存中读写值)
    2、禁止对其进行重排序,也就是保证了有序性

3、并未保证原子性

public class VolatileTest2 {
    private static int INIT_VALUE = 0;

    private final static int MAX_LIMIT = 50;

    public static void main(String[] args) {
        new Thread(() -> {
            while (INIT_VALUE < MAX_LIMIT) {
                System.out.println("T1->" + (++INIT_VALUE));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "ADDER-1").start();

        new Thread(() -> {
            while (INIT_VALUE < MAX_LIMIT) {
                System.out.println("T2->" + (++INIT_VALUE));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "ADDER-2").start();
    }
}

Volatile关键字
1.保证重排序的不会把后面的之类放到屏障的前面,也不会把前面的放到后面
2.强制对缓存的修改操作立刻写出主内存
3.如果是写操作,它会导致其他CPU中的缓存失效

Volatile的使用场景

  • 状态栏标记
  • 屏障前后的一致性
  • 缓存一致性协议
  • 指令重排序
  • happen-before规则
  • 并发编程的三要素
  • volatile关键字的作用

不可变对象

  • 不可变对象一定是线程安全的:里面的任何属性或者引用类型的属性都不能被修改
  • 可变对象可能是安全的:StringBuffer

Future 设计模式

  • Future -> 代表的是未来的一个凭据
  • FutureTask -> 将你的调用逻辑进行了隔离
  • FutureService -> 桥接Future和FutureTask

ThreadLocal

用于保存线程私有的数据,不同线程之间看不到彼此的ThreadLocal中保存的数据,始终以当前线程(Thread.currentThread())作为key

  • ThreadLocal没放值进去时get出来为null
    ThreadLocal赋初值,重写ThreadLocal的initialValue()方法;

模拟ThreadLocal的工作原理

public class ThreadLocalSimulator<T> {

    private final Map<Thread, T> storage = new HashMap<>();

    public void set(T t) {
        synchronized (this) {
            Thread key = Thread.currentThread();
            storage.put(key, t);
        }
    }

    public T get() {
        Thread key = Thread.currentThread();
        T value = storage.get(key);
        return null == value ? initialValue() : value;
    }

    private T initialValue() {
        return null;
    }
}

ThreadLocal应用-上下文设计模式

public class Context {
    private String name;

    protected String cardId;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public String getCardId() {
        return cardId;
    }
}


public class ActionContext {
    private static final ThreadLocal<Context> threadLocal = ThreadLocal.withInitial(Context::new);

    private static class InnerActionContext {
        private static ActionContext actionContext = new ActionContext();
    }

    public static ActionContext getActionContext() {
        return InnerActionContext.actionContext;
    }

    public Context getContext() {
        return threadLocal.get();
    }
}

public class ExecutionTask implements Runnable {

    private QueryFromDBAction queryAction = new QueryFromDBAction();

    private QueryFromHttpAction httpAction = new QueryFromHttpAction();

    @Override
    public void run() {
        queryAction.execute();
        httpAction.execute();

        Context context = ActionContext.getActionContext().getContext();
        System.out.println("The Name is " + context.getName() + " and CardId is " + context.getCardId());
    }
}

public class QueryFromDBAction {

    public void execute() {
        try {
            Thread.sleep(1000L);
            String name = "Alex" + Thread.currentThread().getName();
            ActionContext.getActionContext().getContext().setName(name);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class QueryFromHttpAction {
    public void execute() {
        Context context = ActionContext.getActionContext().getContext();
        String cardId = getCardId(context.getName());
        context.setCardId(cardId);
    }

    private String getCardId(String name) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "23456789" + name;
    }
}

public class ContextTest {
    public static void main(String[] args) {
        IntStream.range(0, 100).forEach(i -> {
            new Thread(new ExecutionTask()).start();
        });
    }
}

生产者-消费者设计模式

Count-Down设计模式

自定义ClassLoader

  • 类加载器的委托是优先给 父类加载器先去尝试加载
  • 父加载器和子加载器其实是一种包装关系或包含关系

总结:
在这里插入图片描述
定义类加载:加载类的加载器
初始类加载器:委托过程中所设计的类加载器
打破双亲委托机制,重写loadClass方法,改变类加载的顺序

import java.io.*;

/**
 * @author LiSheng
 * @date 2020/7/1 12:35
 */
public class MyClassLoader extends ClassLoader {
    private final static String DEFAULT_DIR = "F:\\app\\classloader1";

    private String dir = DEFAULT_DIR;

    private String classLoaderName;

    public MyClassLoader() {
        super();
    }

    public MyClassLoader(String classLoaderName) {
        this.classLoaderName = classLoaderName;
    }

    public MyClassLoader(String classLoaderName, ClassLoader parent) {
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String classPath = name.replace(".", "/");
        File classFile = new File(dir, classPath + ".class");
        if (!classFile.exists()) {
            throw new ClassNotFoundException("The class " + name + " not found.");
        }

        byte[] classBytes = loadClassBytes(classFile);
        if (null == classBytes || classBytes.length == 0) {
            throw new ClassNotFoundException("load the class " + name + "failed");
        }
        return this.defineClass(name, classBytes, 0, classBytes.length);
    }

    //破坏双亲委托机制,重写loadclass方法,改变类加载的顺序
    @Override
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class<?> clazz = null;

        if (name.startsWith("java.")) {
            try {
                ClassLoader system = ClassLoader.getSystemClassLoader();
                clazz = system.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        resolveClass(clazz);
                        return clazz;
                    }
                }
            } catch (Exception e) {
                //ignore
            }
        }

        try {
            clazz = findClass(name);
        } catch (Exception e) {

        }
        if (clazz == null && getParent() != null) {
            getParent().loadClass(name);
        }
        return clazz;
    }


    private byte[] loadClassBytes(File classFile) {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             FileInputStream fis = new FileInputStream(classFile)) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getDefaultDir() {
        return DEFAULT_DIR;
    }

    public String getDir() {
        return dir;
    }

    public String getClassLoaderName() {
        return classLoaderName;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }

    public void setClassLoaderName(String classLoaderName) {
        this.classLoaderName = classLoaderName;
    }
}

类加载器的命名空间

  • 每个类的加载器都有子的命名空间,命名空间由该加载器及其父加载器所加载的类组成
  • 在同一个命名空间中,不会出现完整的名字

运行时包

  • 父加载器看不到子加载器加载的类
  • 不同命名空间下的类加载器之间的类互相不可访问

类的卸载以及classLoader的卸载

  • 该类的所有实例已经被GC
  • 加载该类的ClassLoader实例已经被GC
  • 该类的java.lang.Class对象没有在任何地方被引用
  • GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章