欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java基础知识-多线程

程序员文章站 2022-05-05 23:13:38
...

Java多线程编程-(1)-线程安全和锁Synchronized概念
参考:https://blog.csdn.net/xlgen157387/article/details/77920497#comments
总结:
1、线程创建通过Thread 或者 实现Runable接口创建,thread.start启动。

//继承Thread,重写run()方法
public class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println(this.currentThread().getName());
        }
    }
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); //线程启动的正确方式
    }
}

//实现Runable接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("123");
    }
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable, "t1");
        thread.start();
    }
}

2、线程安全(数据访问保护),多条线程访问某一个类(对象或方法)时,出现竞争修改,此时引入锁synchronized修饰符
3、一个对象有一把锁!多个线程多个锁!
4、static修改的方法或者变量,在该类的所有对象是具有相同的引用的,这样的话,无论实例化多少对象,调用的都是一个方法
5、同步:synchronized 共享 异步:asynchronized 独立


Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性
参考:https://blog.csdn.net/xlgen157387/article/details/78005352
锁概念:http://www.cnblogs.com/pureEve/p/6421273.html
总结:
1、可重入锁:自己可以获取自己的内部锁,避免死锁。
2、Synchronized 出现异常时,锁自动释放
3、Synchronized 将任意对象作为监视器
4、单例模式-双重校验锁
5、volatile 禁止指令的重排序优化(让所有线程知道这个属性,避免请求异常)

public class DubbleSingleton {
    private static volatile DubbleSingleton instance;
    public static DubbleSingleton getInstance(){
        if(instance == null){
            try {
                //模拟初始化对象的准备时间...
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //类上加锁,表示当前对象不可以在其他线程的时候创建
            synchronized (DubbleSingleton.class) { 
                //如果不加这一层判断的话,这样的话每一个线程会得到一个实例
                //而不是所有的线程的到的是一个实例
                if(instance == null){ 
                    instance = new DubbleSingleton();
                }
            }
        }
        return instance;
    }
}

Java多线程编程-(3)-线程本地ThreadLocal的介绍与使用
参考:https://blog.csdn.net/xlgen157387/article/details/78114278
总结:
1、所有的线程都使用同一个被public static修饰的变量。
2、ThreadLocal完全不提供锁,而使用以空间换时间的方式,为每个线程提供变量的独立副本,以保证线程的安全。
3、run方法里面 set(object) get(object)
4、ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度

(1)ThreadLocal只是操作Thread中的ThreadLocalMap对象的集合;

(2)ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLocalMap变量;

(3)线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的;

(4)使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key来存储value值;

(5) ThreadLocal模式至少从两个方面完成了数据访问隔离,即纵向隔离(线程与线程之间的ThreadLocalMap不同)和横向隔离(不同的ThreadLocal实例之间的互相隔离);

(6)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;

(7)线程死亡时,线程局部变量会自动回收内存;

(8)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量,key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;

(9)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;


Java多线程编程-(4)-线程间通信机制的介绍与使用
参考:https://blog.csdn.net/xlgen157387/article/details/78195817
总结:
1、等待/通知机制:wait/notify线程间通信,类似银行取号叫号
2、object 去执行wait()或者notify()方法
一、wait方法

(1)方法wait()的作用是使当前执行代码的线程进行等待,该方法会将该线程放入”预执行队列“中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。

(2)在调用wait()之前,线程必须获得该对象级别锁,这是一个很重要的地方,很多时候我们可能会忘记这一点,即只能在同步方法或同步块中调用wait()方法。

(3)还需要注意的是wait()是释放锁的,即在执行到wait()方法之后,当前线程会释放锁,当从wait()方法返回前,线程与其他线程竞争重新获得锁。

二、notify方法

(1)和wait()方法一样,notify()方法也要在同步块或同步方法中调用,即在调用前,线程也必须获得该对象的对象级别锁。

(2)该方法是用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。

(3)这里需要注意的是,执行notify方法之后,当前线程不会立即释放其拥有的该对象锁,而是执行完之后才会释放该对象锁,被通知的线程也不会立即获得对象锁,而是等待notify方法执行完之后,释放了该对象锁,才可以获得该对象锁。

(3)notifyAll()通知所有等待同一共享资源的全部线程从等待状态退出,进入可运行状态,重新竞争获得对象锁。

三、wait()/notify()方法总结

(1)wait()和notify()方法要在同步块或同步方法中调用,即在调用前,线程也必须获得该对象的对象级别锁。

(2)wait方法是释放锁,notify方法是不释放锁的;

(3)notify每次唤醒wait等待状态的线程都是随机的,且每次只唤醒一个;

(4)notifAll每次唤醒wait等待状态的线程使之重新竞争获取对象锁,优先级最高的那个线程会最先执行;

(5)当线程处于wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常;


Java多线程编程-(5)-使用Lock对象实现同步以及线程间通信
参考:https://blog.csdn.net/xlgen157387/article/details/78197583

public class Run {

    public static void main(String[] args) {

        Lock lock = new ReentrantLock();

        new Thread(() -> runMethod(lock, 0), "thread1").start();
        new Thread(() -> runMethod(lock, 5000), "thread2").start();
        new Thread(() -> runMethod(lock, 1000), "thread3").start();
        new Thread(() -> runMethod(lock, 5000), "thread4").start();
        new Thread(() -> runMethod(lock, 1000), "thread5").start();
    }

    private static void runMethod(Lock lock, long sleepTime) {
        lock.lock();
        try {
            Thread.sleep(sleepTime);
            System.out.println("ThreadName:" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
运行结果:
ThreadName:thread1
ThreadName:thread2
ThreadName:thread3
ThreadName:thread4
ThreadName:thread5

Java基础知识-多线程
使用Lock对象和Condition实现等待/通知实例

主要方法对比如下:

(1)Object的wait()方法相当于Condition类中的await()方法;
(2)Object的notify()方法相当于Condition类中的signal()方法;
(3)Object的notifyAll()方法相当于Condition类中的signalAll()方法;
Java基础知识-多线程

HashTable 同步,线程安全
ConcurrentHashMap 并发
ReentrantReadWriteLock有两个锁:
一个是与读相关的锁,称为“共享锁”;另一个是与写相关的锁,称为“排它锁”
可以并发读锁,其他互斥锁,需要同步。


Java多线程编程-(6)-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier
参考:https://blog.csdn.net/xlgen157387/article/details/78218736
总结:
1、多线程均执行完(CountDownLatch计数减1),7龙珠(7线程完成)
2、CountDownLatch,并行,完成等待,死锁检测
3、CyclicBarrier 多屏障点,可重置,7法师等待,7龙珠等待


Java多线程编程-(7)-使用线程池实现线程的复用和一些坑的避免
参考:https://blog.csdn.net/xlgen157387/article/details/78253096
Java基础知识-多线程
Java基础知识-多线程
总结:


    //该方法返回一个固定线程数量的线程池
    public static ExecutorService newFixedThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //submit(Runnable task)方法提交一个线程
        return executorService;
    }
    //该方法返回一个只有一个现成的线程池
    public static ExecutorService newSingleThreadExecutor() {
        ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        return executorService;
    }
    //返回一个可以根据实际情况调整线程数量的线程池
    public static ExecutorService newCachedThreadPool() {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 2, 0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        for (int i = 0; i < 10; i++) {
            int index = i;
            pool.submit(() -> System.out.println("i:" + index +
                    " executorService"));
        }
        pool.shutdown();
        return pool;
    }
    //该方法和newSingleThreadExecutor的区别是给定了时间执行某任务的功能,可以进行定时执行等
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
//        1、corePoolSize 核心线程池大小;
//        2、maximumPoolSize 线程池最大容量大小;
//        3、keepAliveTime 线程池空闲时,线程存活的时间;
//        4、TimeUnit 时间单位;
//        5、ThreadFactory 线程工厂;
//        6、BlockingQueue任务队列;
//        7、RejectedExecutionHandler 线程拒绝策略;
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(10),
                new ThreadFactory() { //自定义ThreadFactory
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName(r.getClass().getName());
                        return thread;
                    }
                },
                new ThreadPoolExecutor.AbortPolicy()); //自定义线程拒绝策略

        for (int i = 0; i < 10; i++) {
            int index = i;
            executorService.submit(() -> System.out.println("i:" + index));
        }

        executorService.shutdown();
        ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executorService;
        return scheduledExecutorService;
    }

    //在4的基础上可以指定线程数量
    public static ScheduledExecutorService newScheduledThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        //错误信息会被内部补获()
        executorService.submit("Runnable task");
        //错误信息不会被内部补获(无返回值,无法判断任务是否被线程池执行成功)
        executorService.execute("Runnable task");
        //错误信息会被内部补获(有返回值,判断任务是否被线程池执行成功,判断异常)
        Future future = executorService.submit("Runnable task");
        future.get();//阻塞当前线程直到任务完成
        return null;
    }

Java多线程编程-(8)-多图深入分析ThreadLocal原理
参考:https://blog.csdn.net/xlgen157387/article/details/78297568

Java多线程编程-(9)-ThreadLocal造成OOM内存溢出案例演示与原理分析
参考:https://blog.csdn.net/xlgen157387/article/details/78298840
总结:
1、综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?

答案就是:每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
注意:
并不是所有使用ThreadLocal的地方,都在最后remove(),他们的生命周期可能是需要和项目的生存周期一样长的,所以要进行恰当的选择,以免出现业务逻辑错误!但首先应该保证的是ThreadLocal中保存的数据大小不是很大!

2、那么我们修改最开始的代码为:
取消注释:threadLocal.remove(); 结果不会出现OOM,可以看出堆内存的变化呈现锯齿状,证明每一次remove()之后,ThreadLocal的内存释放掉了!线程池中的线程的数量持续增加!


Java多线程编程-(10)-单例模式几种写法的错与对
参考:https://blog.csdn.net/xlgen157387/article/details/78310385
总结:推荐写法

public class Singleton {

    private static class SingletonHolder {
        private static final Singleton instance= new Singleton();
    }

    private Singleton (){

    }

    public static final Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力
参考:https://blog.csdn.net/xlgen157387/article/details/78327228