JAVA线程

线程的创建方式

继承Thread类

public class ThreadTest extends Thread {
    @Override
    public void run() {
        System.out.println("我是继承了Thread创建的线程");
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        threadTest.start();
    }
}

实现Runnable接口

public class RunnableTest implements java.lang.Runnable {
    @Override
    public void run() {
        System.out.println("我是实现Runnable接口创建的线程");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableTest());
        thread.start();
    }
}

实现Callable接口

public class CallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int a = 0;
        for (int i = 0; i < 100; i++) {
            a=a+i;
        }
        System.out.println("执行Callable");
        return a;
    }

    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        FutureTask<Integer> task = new FutureTask<>(callableTest);

        Thread thread = new Thread(task);
        thread.start();

        try {
            Integer integer = task.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

下面我们来具体的聊聊这三种方式:

  • 继承Thread类:一般我们在实际开发中很少用到这种方法,因为我们的Java是单继承的,所以如果我们这个类还需要继承其他类的时候就傻了。
  • 实现Runnable接口:一般推荐使用这种方法,因为java类可以实现多个接口,但是这种方法不带返回值。
  • 实现Callable接口:Callable接口可以有返回值,但是操作比Runnable稍微复杂点,在提交任务的时候需要用到我们的FutureTask去包裹我们的Callable用来接收它的返回值。 Future的get方法以获取结果时,当前线程就会阻塞,直到call()方法结束返回结果

实现 Runnable 接口比继承 Thread 类所具有的优势 :

  1. 适合多个相同的程序代码的线程去处理同一个资源
  2. 可以避免 java 中的单继承的限制
  3. 增加程序的健壮性,代码可以被多个线程共享 ,代码和数据独立
  4. 线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类
  5. runnable 实现线程可以对线 程进行复用,因为 runnable 是轻量级的对象,重复 new 不会耗 费太大资源,而 Thread 则不然,它是重量级对象,而且线程执行完就完了,无法再次利用

线程的状态

线程的状态主要为五种:新建,就绪,运行,阻塞,死亡

**新建:**当我们new 一个线程时,线程的状态为新建

**就绪:**当我们的线程调用了start()方法时候

**运行:**当线程获取到了CPU的使用权就会开始执行

***阻塞:**阻塞的情况有三种:

  1. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池,并且wait()方法会释放锁
  2. 同步阻塞:运行的线程在获取对象的同步锁的时候,如果该同步锁被其他线程占用,那么JVM会把该线程放入锁池中。
  3. 其他阻塞:运行的线程执行了sleep(),join()方法,或者发出I/O请求时,JVM会把该线程设置为阻塞状态。当sleep()状态超时、或者是join()等待的线程终止,或者是I/O处理完毕,线程会重新进入就绪状态。

**死亡:**线程执行完毕或者是因为异常退出了run()方法

下面上图来理解记忆:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N5hqSsMR-1670407105266)(C:\Users\22924\Desktop\java面试相关\Java知识\java基础\线程状态.jpg)]

一般线程和守护线程的区别

​ 所谓守护线程,就是我们程序在运行的过程中,在我们后台提供一种通用服务的线程。我们JVM中的垃圾回收线程就是一个守护线程。守护线程什么时候结束呢,当我们的程序结束时。所有的非守护线程也结束了 ,只有当所有非守护线程结束后,守护线程才会结束。

使用守护线程的注意点:

  • thread.setDaemon(true) 必须在thread.start()之前设置,否则会抛出 IllegalThreadStateException 异常 。
  • 在Daemon线程中产生的新线程也是Daemon的
  • 守护线程在任何时候都不应该去访问固有资源,因为它在任何情况下都有可能会中断。

sleep wait yield notify notifyAll join

  1. sleep与wait的区别
    • sleep是线程类Thread的方法导致线程暂停执行时间,把执行机会给其他线程,监控状态依旧会保持,时间到后就会恢复,并且不会释放锁。
    • wait()方法是Object()类的方法,会释放锁使线程进入等待队列。只有调用notify或者是notifyAll后,线程才会进入锁池等待获取锁。
    • wait、notify、notifyAll只能在同步方法,同步代码块中使用,sleep却可以在任何地方使用
    • sleep必须捕获异常,wait、notify、notifyAll不用捕获异常。因为sleep过后,线程不会释放他所持有的锁,他不会影响其他线程,但是其他对象有可能会调用该线程的interrupt(), 产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程 就会异常终止,进入 TERMINATED 状态。
    • wait方法也同样有可能在wait的过程中被其他对象调用自身的interrupt()方法。

注意: sleep是一个静态方法 所以调用是通过Thread类来直接调用的,只能够使当前线程sleep,不可以使用对象来调用sleep。

  1. yield join notify notifyAll
  • yield()方法使停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会,如果没有的话, 那么 yield() 方法将不会起作用,并且由可执行状态后马上又被执行。 (线程优先级可以用 setPriority(int newPriority) 方法设置)
  • join方法是用于某一个线程执行的时候去调用另外一个线程,等待另一个线程结束后再执行当前线程。
  • notify 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程 等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管 理的实现。
  • notifyAll 会唤醒所有等待 ( 对象的 ) 线程,尽管哪一个线程将会第一个处理取决于操作系 统的实现。
  • LockSupport 比wait notify/notifyAll更加灵活, LockSupport.park():阻塞当前线程
    LockSupport.unpark(Thread t):唤醒特定线程

如何中断线程

  • 使用退出标志位,使线程正常退出,也就是线程run方法完成后线程终止
  • 通过return退出run方法
  • 通过对有些状态中断抛异常Thread.interrupt()中断 (当线程在在阻塞状态下,别的线程调用该线程的interrupt()方法可以使线程异常结束)
  • 使用stop方法强制终止线程

中断线程可能出现的问题 :

使用 Thread.interrupt() 并不能使得线程被中断,线程还是会执行。最靠谱的方法就是 设置一个全局的标记位,然后再 Thread 中去检查这个标记位,发现标记位改变则中断线程。

什么是死锁,如何避免

死锁的意思

多个进程或线程互相等待对方的资源,在得到新的资源之前不会释放自己的资源,这样就形成了循环等待,这种现象被称为死锁 。在没有外力的作用死锁永远不会退出。

避免死锁

死锁有四个形成条件:互斥,请求保持,不可剥夺,循环等待。破环其中任何一个条件,就不会形成死锁。

  • 互斥:该资源任一时刻只由一个线程占用。

  • 请求保持:一个线程因为请求资源被阻塞,对自己已经获得的资源不放。

  • 不可剥夺:若干进程或者线程之间形成一种头尾相接的循环资源等待。

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

多线程下抛出异常怎么办

再单线程下面,我们线程的异常可以用try…catch捕获,但是在我们多线程下,线程抛出的异常用try…catch不能捕获,所以就得借助我们的UncaughtExceptionHandler接口,我们创建一个类去实现UncaughtExceptionHandler接口,重写里面的uncaughtException方法,最后在线程启动前通过 thread.setUncaughtExceptionHandler将该类的对象设置进去。

public class UnCatchExceptionTest {
    public static void main(String[] args) {

        Thread thread = new Thread(new java.lang.Runnable() {
            @Override
            public void run() {
                System.out.println(2 / 1);
                System.out.println(2 / 0);
                System.out.println(2 / 2);
            }
        });
        thread.setUncaughtExceptionHandler(new MyUnCatchException());
        thread.start();
    }

    static class MyUnCatchException implements Thread.UncaughtExceptionHandler{

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println(t.getName()+"异常信息是:"+e.getMessage());
        }
    }
}

线程通信的方式

  1. 同步
    同步是指多个线程通过 synchronized 关键字这种方式来实现线程间的通信。
  2. wait/notify 机制
Condition类

condition类的创建依赖于Lock所以 必须被 lock.lock()和lock.unLock()包裹使用。通过Lock…newCondition();创建

Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Object的wait();会释放所占有的锁

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

LockSupport

总结一下LockSupport的使用要点:

线程调用park阻塞自己(类似sleep),直到中断、超时或者其它线程调用unpark;
线程调用park之前,如果其它线程调用了unpark,则会立即返回;
线程唤醒以后,线程并不知道是什么原因被唤醒,需要去判断时间、线程状态(Thread.interrupted());
线程start之前,unpark没有用,线程start之后park仍然会阻塞;
线程调用park阻塞不会释放线程占用的资源(锁);
它里面实际是维护了一个permit许可 默认为0,当调用park()后会判断是否有许可,没有的话就会阻塞线程。当调用unpark()会将许可置为1,此时park()判断有许可就会去消费许可 并且改为0。所以先调用unpark()再调用park()不会阻塞线程。

线程池的创建方式

线程池的创建往大的说有两种方式,

第一种是通过我们的Executor去创建JAVA帮我设置好的线程池

第二种方式是通过ThreadPoolExecutor去自定义线程池。

通过Executor创建线程池

Executors.newFixedThreadPool:创建固定大小的线程池。

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
// LinkedBlockingQueue队列大小为Integer.MAX_VALUE 容量过大,可能会堆积大量的任务,从而造成OOM(内存溢出)

Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
//缺点:该线程池允许创建的最大线程数量为Integer.MAX_VALUE,可能会创建出大量线程,导致OOM(内存溢出)
//SynchronousQueue同步队列,容量为0
//特性:可缓存的线程池。注意到60L存活时间,即60s空闲的线程将会被销毁,直到线程池为0.
//缺点:做到了动态地新增线程。却没有考虑到内存。

Executors.newSingleThreadExecutor: 该方法创建了只有一个线程的线程池,如果提交的任务没有空闲的线程去处理,就会被放入阻塞队列中

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。

 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
//service.schedule(new Task(), 10, TimeUnit.SECONDS);
 
//service.scheduleAtFixedRate(new Task(), 10, 10, TimeUnit.SECONDS);
 
//service.scheduleWithFixedDelay(new Task(), 10, 10, TimeUnit.SECONDS); 

Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。

 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
/**DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。*/

Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。

自定义线程池

ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置 7 个参数。

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
corePoolSize:核心线程数
maximumpoolSize:最大线程数
keepAliveTime:最大存活时间
unit :时间单位
workQueue :任务队列
threadFactory :线程工厂
handler :拒绝策略

四种拒绝策略

AbortPolicy: 直接丢弃任务 然后抛出 RejectedExecutionException 异常

DiscardPolicy:直接丢弃任务什么也不做

DiscardOldestPolicy: 丢弃最早进入队列的线程,然后重新提交拒绝任务

CallerRunsPolicy: 由调用线程处理该任务(谁提交谁负责)

自定义线程池默认的拒绝策略是AbortPolicy。

线程池执行流程

请添加图片描述

线程池的关闭

线程池的关闭用到两个方法,shutdown和shutdownNow,都是位于ThreadPoolExecutor里面的

shutdown:它会将线程池状态切换成Shutdown,此时是不会影响对阻塞队列中任务执行的,会将剩余的任务全部执行完,但是会拒绝执行新加进来的任务,同时会回收闲置的Worker;

shutdownNow:先将线程池状态设置为STOP,然后拒绝所有提交的任务。最后中断左右正在运行中的worker,同时会回收所有的Worker;

线程池执行任务的方法

线程池可以通过两个方法来执行任务:submit()和execute()

  • execute()执行一个没有返回值的任务 Runnable()任务
  • submit()字面意思是提交,它有返回值,既可以提交Callable任务,也可以提交Runnable任务。

当提交的为Callable任务的时候,返回一个Future对象包装的结果,通过future.get()方法获取结果

submit(Callable task)能获取到它的返回值,通过future.get()获取(阻塞直到任务执行完)。一般使用FutureTask+Callable配合使用(IntentService中有体现)。

submit(Runnable task, T result)能通过传入的载体result间接获得线程的返回值。

submit(Runnable task)则是没有返回值的,就算获取它的返回值也是null。

Future.get方法会使取结果的线程进入阻塞状态,直到线程执行完成之后,唤醒取结果的线程,然后返回结果

Threadlocal

含义

ThreadLocal叫做线程变量,意思就是ThreadLocal填充的变量属于当前线程的,该变量对于其他线程而言是隔离的。是当前线程独有的变量,ThreadLocal变量在每个线程中都创建了一个副本,每个线程可以访问自己的内部的副本变量。

thread,threadLocal,threadLocalMap三者之间的关系

Thread持有一个ThreadLocalMap(其实就是一个Entry类型的数组),一个ThreadLocalMap里面包含了多个key ThreadLocal,ThreadLocal在ThreadLocalMap的key里面是弱引用。ThreadLocalMap是ThreadLocal里面的一个内部类,这样设计的好处是隐藏实现,使使用者的操作变得简单。

ThreadLocalMap 是一个线程本地的值,它所有的方法都是private 的,也就意味着除了ThreadLocal 这个类,其他类是不能操作ThreadLocalMap 中的任何方法的,这样就可以对其他类是透明的。同时这个类的权限是包级别的,也就意味着只有同一个包下面的类才能引用ThreadLocalMap 这个类,这也是Thread 为什么可以引用ThreadLocalMap 的原因,因为他们在同一个包下面。

虽然Thread 可以引用ThreadLocalMap,但是不能调用任何ThreadLocalMap 中的方法。这也就是我们平时都是通过ThreadLocal 来获取值和设置值ThreadLdocalMap 什么时候开始和Thread 进行绑定的呢

ThreadLdocalMap 什么时候开始和Thread 进行绑定的呢

在第一次调用ThreadLocal set() 方法的时候开始绑定的,来我们看下set 方法的源码

 public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else//如果为空就新建一个绑定当前线程
            createMap(t, value);
    }

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

ThreadLocal 内存泄漏
ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收。

那么如何避免内存泄漏呢?

在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收

.\bin\windows\kafka-topics.bat --list --bootstrap-server localhost:9092

.\bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test1

线程三剑客

CountDownLatch

CountDownLatch和join有点类似,它里面有一个计数器,通过每次countDown()方法减去一,当countDownLatch.getCountDown()==0的时候,await()后面的程序才得以执行。

它与join的不同点在于。join只有当当前join的线程执行完,当前的线程才能执行,否则便一直wait.

public class Worker extends Thread {
        //工作者名
            private String name;
        //工作时间
        private long time;        
        private CountDownLatch countDownLatch;        
        public Worker(String name, long time, CountDownLatch countDownLatch) {
            this.name = name;
            this.time = time;
            this.countDownLatch = countDownLatch;
        }        
        @Override
        public void run() {
            // TODO 自动生成的方法存根
            try {
                System.out.println(name+"开始工作");
                Thread.sleep(time);
                System.out.println(name+"工作完成,耗费时间="+time);
                countDownLatch.countDown();
                System.out.println("countDownLatch.getCount()="+countDownLatch.getCount());
            } catch (InterruptedException e) {
                // TODO 自动生成的 catch 块
                e.printStackTrace();
            }    
        }
    }
public class Test {     
        public static void main(String[] args) throws InterruptedException {
            // TODO 自动生成的方法存根
     
            CountDownLatch countDownLatch = new CountDownLatch(2);
            Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);
            Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);
            Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);
            
            worker0.start();
            worker1.start();
            
            countDownLatch.await();
            System.out.println("准备工作就绪");
            worker2.start();        
        }
    }


运行结果:
worker1开始工作
worker0开始工作
worker0工作完成,耗费时间=3174
countDownLatch.getCount()=1
worker1工作完成,耗费时间=3870
countDownLatch.getCount()=0
准备工作就绪
worker2开始工作
worker2工作完成,耗费时间=3992
countDownLatch.getCount()=0
CyclicBarrier

位于java.util.concurrent包下,字面意思回环栅栏,通过它可以实现让一组线程等待直到某个状态后再全部同时执行,叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以重新使用。我们把这个状态称为barrier。当调用await()方法以后,线程就处于barrier状态

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程"+Thread.currentThread().getName());
            }
        });
        for(int i=0;i<N;i++) {
            new Writer(barrier).start();
        }
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}
运行结果:
线程Thread-0正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
当前线程Thread-0
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...

Process finished with exit code 0
Semaphore

信号量,可以控制同时访问的线程个数,通过acquire()获取一个许可,如果没有就等待,而release释放一个许可

public static void main(String[] args) {
        int N = 8;            //工人数
        Semaphore semaphore = new Semaphore(5); //机器数目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }

    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一个机器在生产...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"释放出机器");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

运行结果:
工人0占用一个机器在生产...
工人4占用一个机器在生产...
工人3占用一个机器在生产...
工人2占用一个机器在生产...
工人1占用一个机器在生产...
工人3释放出机器
工人0释放出机器
工人1释放出机器
工人4释放出机器
工人2释放出机器
工人7占用一个机器在生产...
工人6占用一个机器在生产...
工人5占用一个机器在生产...
工人7释放出机器
工人6释放出机器
工人5释放出机器

Process finished with exit code 0

三剑客总结

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限

volatile 关键字

volatile 只能用于成员变量, volatile变量可以保证下一个读取操作会在前一个写操作之后发生。线程都会直接从内存中读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

volatile并不保证原子性,只有一致性和可见性。

public class VolatileTest {
    boolean flag = true;
    public void updateFlag() {
        this.flag = false;
        System.out.println("修改flag值为:" + this.flag);
    }

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        new Thread(() -> {
            while (test.flag) {
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }, "Thread1").start();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                test.updateFlag();
            } catch (InterruptedException e) {
            }
        }, "Thread2").start();
    }
}
结果:
    修改flag值为:false
    我们可以看到虽然线程Thread2已经把flag 修改为false了,但是线程Thread1没有读取到flag修改后的值,线程一直在运行

.println(“修改flag值为:” + this.flag);
}

public static void main(String[] args) {
    VolatileTest test = new VolatileTest();
    new Thread(() -> {
        while (test.flag) {
        }
        System.out.println(Thread.currentThread().getName() + "结束");
    }, "Thread1").start();

    new Thread(() -> {
        try {
            Thread.sleep(2000);
            test.updateFlag();
        } catch (InterruptedException e) {
        }
    }, "Thread2").start();
}

}
结果:
修改flag值为:false
我们可以看到虽然线程Thread2已经把flag 修改为false了,但是线程Thread1没有读取到flag修改后的值,线程一直在运行


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

末、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值