• JAVA多线程知识点


    JAVA多线程知识点汇总

    1.创建多线程有几种方式?

    • 继承Thread类
    • 实现Runnable接口
    • 应用程序可以使用Executor框架来创建线程池

    2.线程状态:

      新建-就绪-运行-阻塞-死亡。

    3.同步方法和同步代码块的区别是什么?

    • 同步方法默认用this或者当前类class对象作为锁;

    • 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生问题的部分代码而不是整个方法;

    4.死锁概念:

      两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行,结果就是这些线程都陷入了无限的等待中。

      多线程产生死锁的四个必要条件:破坏其中一个既可避免死锁或解决死锁。

    • 互斥条件:一个资源每次只能被一个进程使用。

    • 保持和请求条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。

    • 不可剥夺性:进程已获得资源,在未使用完成前,不能被剥夺。

    • 循环等待条件(闭环):若干进程之间形成一种头尾相接的循环等待资源关系。

    5.volatile关键字

      它保证的是有序性和可见性!!!注意没有原子性!!!!只修饰变量,是轻量级实现。这也是和synchronized主要区别。用volatile修饰的变量,每次读取时都会获取修改后(最新)的值进行操作(即可见性)。读取数值时必须重新从主内存加载,并且read load是连续的。修改共享变量后,必须马上同步回主内存,并且存储和写入是连续的。但是没有锁机制无法做到线程安全。更适合用于一个修改者,多个使用者,如状态标识,数据定期发布场景。

      代码样例:不加volatile,其他线程读取不到当前线程的最新值

    public class VolatileKey extends Thread {
        volatile boolean flag = false;
    
        public void run() {
            while (!flag) {
                System.out.println("run");
            }
    
            System.out.println("stop");
        }
    
        public static void main(String[] args) throws Exception {
            VolatileKey vt = new VolatileKey();
            vt.start();
            Thread.sleep(2000);
            vt.flag = true;
        }
    }

      造成这种问题的原因主要是JVM对内存分配的优化,不加volatile时,线程会保存副本,而不是每次都从主内存获取。而volatile限制线程不进行内部缓存和重排,既而解决掉可见性问题。

    6、CountDownLatch

    CountDownLatch是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。

    CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。当计数器为0时执行await()方法后的代码。

    用法:

    1.在线程结束时调用.countDown()方法。

    2.在主线程合适位置调用.await()方法。

    样例代码:

    public class CountDownLatchTest {
        public static void main(String[] args) {
            final CountDownLatch latch = new CountDownLatch(2);
            System.out.println("主线程开始执行…… ……");
            //第一个子线程执行
            ExecutorService es1 = Executors.newSingleThreadExecutor();
            es1.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                        System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    latch.countDown();
                }
            });
            es1.shutdown();
    
            //第二个子线程执行
            ExecutorService es2 = Executors.newSingleThreadExecutor();
            es2.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
                    latch.countDown();
                }
            });
            es2.shutdown();
            System.out.println("等待两个线程执行完毕…… ……");
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("两个子线程都执行完毕,继续执行主线程");
        }
    }

    7、CyclicBarrier 

    作用是设置栅栏阻挡所有线程,当所有线程都完成后才进行后续操作。可循环利用,且提供reset方法重置。

    用法:

    1.在线程内调用.await()方法,CyclicBarrier会在所有线程都将await前的任务完成时,才继续执行后面的代码(本线程内的)。可以在一个方法内多次调用.await()。

    2.和countDownLatch的区别:countDownLatch是减法计数器cyclicBarrier是加法计数器。cyclicBarrier可复用,即一个函数可调用多次.await()方法,且提供rest()函数进行主动重新计数。countDownLatch是一次性的。

    8、Semaphore

    Semaphore用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。还可以用来实现某种资源池限制,或者对容器施加边界。比如限制最大5个人同时访问。

    用法:

    1.Semaphore semp = new Semaphore(5);//创建通行5个线程

    2.semp.acquire();//发放通行证

    3.semp.release();//释放资源

    当同时访问线程数大于等于5个时,会阻塞,达到限流目的。

    9、有关yield()

    功能为:暂停当前正在执行的线程对象,并执行其他线程。

    注意:yield()让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会,但是不保证一定让其他线程执行,因为有可能会被再次选中。

    所以yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

    10.简单介绍线程未捕获异常处理

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    
    /**
     *异常线程
     */
    class ExceptionThread implements Runnable {
        @Override
        public void run() {
            System.out.println("UncaughtExceptionHandler " + Thread.currentThread().getUncaughtExceptionHandler());
            System.out.println("thread " + Thread.currentThread());
            throw new RuntimeException();
        }
    }
    
    /**
     * 未捕获异常处理。
     */
    class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("catch thread = " + t);
            System.out.println("catch ex = " + e);
        }
    }
    
    /**
     *利用线程池获取线程时使用的工厂类
     */
    class MyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            System.out.println("runnable " + r);
            Thread thread = new Thread(r);
            //设置未捕获异常处理类
            thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
            return thread;
        }
    }
    
    public class ExceptionThreadMain {
        public static void main(String[] args) {
            ExecutorService ex = Executors.newCachedThreadPool(new MyThreadFactory());//指定工厂
            ex.execute(new ExceptionThread());
            ex.shutdown();
        }
    }

    运行结果可对比查看

    runnable java.util.concurrent.ThreadPoolExecutor$Worker@6d6f6e28[State = -1, empty queue]
    UncaughtExceptionHandler util.threadutil.MyUncaughtExceptionHandler@3986e8ee
    thread Thread[Thread-0,5,main]
    catch thread = Thread[Thread-0,5,main]
    catch ex = java.lang.RuntimeException

    JAVA中异常总基类是Throwable,其大部分异常都继承自Exception(如IOException和RuntimeException及其子类)和Error类(如AWTError的StackOverFlowError)

    11、Thread、ThreadLocal、ThreadLocalMap

    ThreadLocal主要是做到线程间数据隔离,从而达到多线程安全的效果,与Synchronized完全不同。

    ThreadLocalMap的代码位置在ThreadLocal里,并且是一个静态内部类。

    而每一个Thread里都会有ThreadLocalMap实例,所以TheadLocal里保存变量的集合就是此map。

    由于ThreadLocalMap里的entry里的Key部分采取的弱引用,如果ThreadLocal没有强引用指向它,则不会阻止GC回收ThreadLocalMap里Entry的Key部分,但是Value部分却是强引用。就会导致内存泄漏。

    所以推荐ThreadLocal采用静态声明或每次用完都调用一次remove()函数。

    12、线程杂谈

    1. 如何在多线程之间共享数据:全局变量,静态变量,或共享对象。
    2. 共享变量必须放在主内存中,因为每个线程都有自己的内存区,也只能操作自己的内存区,所以解决可见性问题是从主内存读取到工作内存,操作后写回到主内存中。
    3. 被共享的变量(对象)放到堆、方法区里(主内存)。也就能和jvm的内存分配联系起来。方法区也称"永久代",它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。而堆内存存放大多数对象,因为堆内存只有一个,所以是线程共享的。也正是因为如此(从主内存中读,再写回主内存),带来了线程安全问题。
    4. java同步交互协议(从主内存读到工作内存协议)有八个原子操作(注意,操作是原子的,但每个原子操作之间没有原子性)

    (1)锁定-主内存变量锁定,线程独享。

    (2)解锁-解锁变量,其他线程可访问。

    (3)读取-从主内存读取到工作内存。

    (4)载入-将读取的主内存值保存到工作内存的副本中。

    (5)使用-工作内存使用读取的值。

    (6)赋值-工作内存操作读取的值。

    (7)存储-从工作内存的值传送给主内存。

    (8)写入-将存储的值写入到主内存中。

    13、常用的几种线程池

      1.newSingleThreadExecutor

    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代再次执行它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

      2.newFixedThreadPool

    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,会新分配线程再次执行。

      3.newCachedThreadPool

    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

      4.newScheduledThreadPool

      创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

  • 相关阅读:
    phpStudy for Linux (lnmp+lamp一键安装包)
    eq,neq,gt,lt等表达式缩写
    lnmp环境的使用
    lnmp环境的搭建
    箭头函数中的this
    Vue中实现路由懒加载及组件懒加载
    Vue项目中实现路由按需加载(路由懒加载)的3中方式:
    判断数据类型的方式以及各自的优缺点
    最近工作中踩的坑
    7种方法实现CSS左侧固定,右侧自适应布局
  • 原文地址:https://www.cnblogs.com/chxwkx/p/14629814.html
Copyright © 2020-2023  润新知