java并发编程之美第一章并发编程基础(读书笔记)

news2024/11/26 15:01:26

1–50面

java并发编程基础

什么是线程

进程:

是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位

线程:

是进程的一次执行路径,一个进程至少有一个线程,进程中的多个线程共享进程的资源.

线程是CPU分配的基本单位

栈:

每个线程都有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,其他线程无法访问,除此之外栈还可以用来存放线程的调用栈帧.

堆:

堆是一个进程中最大的一个内存,堆是被进程中的所有线程共享的,是进程创建时分配的,堆里面主要存放使用new操作创建的对象实例.

方法区:

存放JVM加载的类,常量及静态变量等信息,也是线程共享的.

线程的创建与运行

java一共三种创建线程的方式

    • 实现Runnable接口的run方法
    • 继承Thread类并重写run方法
    • 使用FutureTask方式
/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发.线程的创建
 * @date 2023/4/2 15:53
 *
 *
 *
 *
 *
 *
 *
 *
 * 1.使用继承的好处是方便传参,可以在子类里面添加成员方法
 *   通过set或者构造函数进行参数传递
 * 2.使用Runnable则只能使用主线程里面被声明的final变量.不好的地方是java不支持多继承
 * 3.继承Thread那么子类不能再继承其他类 而Runnable没有这个限制
 * 4.前两个方法都不支持返回参数,最后的Futuretask方式可以
 * 
 */
public class ThreadTest {


    /**
     * 第一种实现多线程的方式
     *
     * 优点:
     *      在run()方法内获取当前线程不需要使用Thread.currentThread()方法,直接使用this就可以获取当前线程
     * 缺点:
     *      java不支持多继承,如果继承Thread就不能继承其他父类
     *      任务和代码没分离,当多个线程执行一样的任务时候需要多份认任务代码
     *      任务方法没有返回值
     */
    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("I am a Thread");
        }
    }


    /**
     * 第二种创建线程的方法
     *
     * 优点:
     *  任务跟代码分离开
     * 缺点:
     *  任务方法没有返回值
     */
    public static class RunableTask implements Runnable{
        @Override
        public void run() {
            System.out.println("I am a Thread2");
        }
    }



    public static class CallTask implements Callable<String>{
        @Override
        public String call() throws Exception {
            return "hello Thread";
        }
    }



    public static void main(String[] args) throws InterruptedException{
        /**
         *创建线程的时候其实还没有成功启动,只有执行了线程的start方法的时候才算真正启动成功
         * 用操作系统的角度来看创建线程的时候其实处在了就绪态(已经获取了除了CPU之外的其他资源)
         * 执行start会执行线程的run方法,就获得了cpu从就绪态转到了运行态
         * run方法结束标志该线程运行完成处于终止状态
         *
         */
        MyThread myThread = new MyThread();

        //线程启动
        myThread.start();;

        RunableTask runableTask = new RunableTask();
        new Thread((runableTask)).start();;
        new Thread((runableTask)).start();


        FutureTask<String> futureTask = new FutureTask<>(new CallTask());
        new Thread(futureTask).start();

        try {
            String string = futureTask.get();
            System.out.println(string);
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

线程的通知和等待

wait()函数

当一个线程调用共享线程的wait()方法的时候,该线程就会被组赛挂起,知道发生以下几种才会返回

      1. 其他线程调用了该共享线程的notify()或者notifyAll()
      2. 其他线程调用了该线程的interrupt(),该线程抛出了中断异常返回

注意:调用wait()方法的时候如果没有提前获取该对象的监视器锁,则调用该方法的时候会抛出IllegalMonitorException异常

获取共享变量的监视器锁的方法

执行synchronized同步代码块时候,使用该共享变量作为参数
synchronized (共享变量){
    //代码逻辑
}
调用该共享变量方法的时候,并且该方法使用了synchronized来进行修饰
synchronized void add(int a,int b){
    //代码逻辑
}

虚假唤醒

某个线程没有被其他线程调用notify(),notifyAll()方法进行通知,并且没有被中断,等待超时,就从挂起态变成了运行状态

要避免出现这种虚假唤醒

方法: 不断的测试该线程被唤醒的条件是否满足,不满足则继续等待,满足唤醒条件了就退出循环

synchronized (obj){
    while (条件不满足){
        obj.wait();
    }
}

注意:当前线程调用共享变量的wait()方法后只会释放当前共享线程变量上的锁,如果当前线程还持有其他线程的锁是不会被释放的.

当一个线程调用共享对象的wait()方法被阻塞挂起的时候,如果其他线程中断了该线程,则该线程会抛出InterruptedException异常并返回

/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发
 * @date 2023/4/2 20:16
 */
public class WaitNotifyInterupt {
    static Object object=new Object();
    public static void main(String[] args) throws InterruptedException{
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("--------begin--------");
                synchronized (object){
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        threadA.start();
        Thread.sleep(1000);
        System.out.println("begin interrput threadA");
        threadA.interrupt();
        System.out.println("end interrupt threadA---");
    }

}

wait(long timeout)函数

相比之前的外滩()方法多了一个超时参数

不同点:

如果一个线程调用共享对象的该方法挂起时,没有在指定的超时时间内被其他线程调用该共享线程的notify()和notifyAll()方法唤醒,就会因为超时而返回

如果调用一个负数则会抛出IllegalArgumentException异常

wait(long timeout,int nanos)函数

public final void wait(long timeout,int nanos) throws InterruptedException{
    if(nanos<0){
        throw new IllegalAccessException("timeout value is negative");
    }
    if(nanos<0||nanos>999999){
        throw new IllegalAccessException("nanoscond timeout value out of range");
    }
    if(nanos>0){
        timeout++;
    }
    wait(timeout);
}

notify()函数

会唤醒一个在共享变量上调用的wait()系列方法后被挂起的线程.一个共享变量上可能会有很多个被阻塞的线程在等待,具体唤醒那个线程是随机的

被唤醒的线程不是立马就可以从wait()方法返回并运行,该线程必须在获取了对共享对象的监视器锁后才可以返回

没有获取到监视器锁的话则会抛出IllegalMonitorStateException异常

notifyAll()函数

该方法相比于上一个方法来说,该方法会释放该共享变量上面所有的由于调用wait()系列方法而被挂起的线程

public class NotifyAllTest {
    private static volatile Object resourceA=new Object();

    public static void main(String[] args) throws InterruptedException{
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println("threadA get resocuseA lock");
                    try {
                        resourceA.wait();
                        System.out.println("threadA end wait");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                }
            }
        });
        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println("threadB get resocuseA lock");
                    try {
                        resourceA.wait();
                        System.out.println("threadB end wait");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                }
            }
        });
        Thread threadC=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println("thread begin notify");
                    resourceA.notifyAll();

                }
            }
        });

        threadA.start();
        threadB.start();
        Thread.sleep(1000);
        threadC.start();
        threadA.join();
        threadB.join();
        threadC.join();

        System.out.println("over");
    }
}

注意:在共享变量上调用notifyAll()只会唤醒该方法前调用了wait系列函数而被放入共享变量等待集合里面的线程。而不会唤醒在该方法之后调用wait系列函数。

等待线程6373等待

等待线程执行终止的join方法

在需要等待多个事件完成后才螚继续往下执行的时候就可以使用Thread方法提供的join()方法

该方法是无参无返回值的方法。

/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发.Test
 * @date 2023/4/12 23:31
 */
public class JoinTest {
    public static void main(String[] args) throws InterruptedException{
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("child threadOne over");
            }
        });


        Thread threadTwo =new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("child threanTwo over");
            }
        });

        threadOne.start();
        threadTwo.start();

        System.out.println("wait all child thread over ");

        threadOne.join();
        threadTwo.join();

        System.out.println("all child over");
    }
}

线程A调用线程B的join方法后会被阻塞,当其他线程调用线程A的interrupt()方法中断线程A,则A会抛出一个InterruptedException异常

public class InterruptedExceptionTest {
    public static void main(String[] args) throws InterruptedException{
        Thread threadOne =new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadOne begin run!!!");
                for(; ; ){

                }
            }
        });


        final Thread mainThread=Thread.currentThread();



        Thread threadTwo =new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                mainThread.interrupt();
            }
        });



        threadOne.start();

        threadTwo.start();


        try {
            threadOne.join();
        } catch (InterruptedException e) {
            System.out.println("main thread "+e);
        }
    }
}

让线程睡眠都sleep方法

sleep是Thread的一个静态方法,调用该方法会暂时让出指定时间的执行权,也就是不参与CPU调度,但是该线程所拥有的监视器资源(锁)还是持有不让出的,指定的休眠时间到了就会正常返回,相当于从阻塞态变成了就绪态,参与CPU的调度,如果获取到CPU资源之后就可以进入运行态。但是如果在睡眠期间其他线程调用了该线程的Interrupt()方法中断了该线程,那么该方法调用sleep的时候会抛出InterruptedException异常而返回。

/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发.Test
 * @date 2023/4/12 23:38
 */
public class InterruptedExceptionTest {
    
    //创建一个独占锁
    private static final Lock lock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("child threadA is in sleep");
                    Thread.sleep(1000);
                    System.out.println("child threadA is in awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });


        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("child threadB is in sleep");
                    Thread.sleep(1000);
                    System.out.println("child threadB is in awake");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });


        threadOne.start();

        threadTwo.start();

    }
    
}

让出cpu执行权的yield方法

yield是Thread的一个静态方法,当线程调用yield方法时,就是按时线程调度器当前线程请求让出自己的cpu使用 但是线程调度器可以忽略这个暗示。

使用的时候意味着该线程自己占用都时间片没有使用完的部分不想使用了,暗示线程调度器现在就可以进行下一轮的线程调度。当前线程交出cpu使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,也有可能会调度到刚刚让出cpu的那个线程来获取cpu执行权。

总结:

sleep与yield方法的区别

当线程调用sleep方法时会调用线程会被阻塞挂起指定时间,在这期间线程调度器不会去调度该程。

当调用yield时候,线程会让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器在下一次调度的时候就有可能调度刀当前线程执行。

线程中断

线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

  • void Interrupt() :中断线程 例如当线程a运行时,线程b可以调用线程a的interrupt()方法设置a的中断标志为true并返回。设置标志仅仅是设置标志,线程a并没有被中断,他会继续往下执行,如果线程a调用了wait系列函数,join方法或者sleep等方法被阻塞挂起,这时候若是线程b调用a的interrupt()方法,线程a会在调用这些方法的时候抛出InterruptedException异常
  • boolean isInterrupted() :检测当前线程是否被中断。如果是返回true,否则返回false
public boolean isInterrupted(){
   //传递false,说明不清除中断标志
     return isIntrrrupted( false);

}
  • boolean interrupted() :检测当前线程是否被中断,如果是返回true。不同点该方法发现当前线程被中断,会清楚中断标志,并且该方法是static方法,可以通过Thread直接调用在interrupted()内部是获取当前线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。
public static boolean Interrupted{
//清除中断标志
return currentThread.isInterrupted(true);
}

一段Interrupt优雅退出的例子

@Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()&&more work to do){
                //do more work
            }
        }catch (InterruptedException e){
            //thread was interrupted during sleep or wait
        }finally {
            //cleanup if required 
        }
    }

根据中断标志判断线程是否终止的例子

public static void main(String[] args) throws InterruptedException{
    Thread thread=new Thread(new Runnable() {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread()+"hello");
            }
        }
    });
    
    
    thread.start();
    
    
    Thread.sleep(1000);
    System.out.println("main thread interrupt thread");
		thread.interrupt();
    
    thread.join();
    System.out.println("main is over");
}

当线程为了等待一些特定条件到来,一般会调用sleep函数,wait系列函数或者join函数来阻塞挂起当前线程,如果提前满足由阻塞到激活态的条件,这时候可以调用该线程的interrupt方法,强制sleep方法抛出InterruptedException异常而返回,线程恢复到激活状态

public class InterruptedOrIsInterrupted {
    public static void main(String[] args) throws InterruptedException{
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                for(; ;){

                }
            }
        });


        threadOne.start();

        threadOne.interrupt();

        System.out.println("isInterrupted:"+threadOne.isInterrupted());

//        System.out.println("isInterrupted"+threadOne.interrupted());

        System.out.println("isInterrupted:"+Thread.interrupted());

        System.out.println("isInterrupted:"+threadOne.isInterrupted());

        threadOne.join();
        System.out.println("main thread is over");
    }
}
/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发
 * @date 2023/4/13 9:11
 */
public class InterruptedOrIsInterruptedTwo {
    public static void main(String[] args) throws InterruptedException{
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) {

                }
                System.out.println("threadTwo isInterrupted:" + Thread.currentThread().isInterrupted());
            }
        });



        threadOne.start();

        threadOne.interrupt();


        threadOne.join();
        System.out.println("main thread is over");
    }
}

理解线程上下文切换

多线程里面,线程数一般是大于CPU个数的,而每个CPU同一时刻只能被一个线程所使用,为了让用户感觉到多个线程在同时使用的,CPU的分配采用了时间片轮转的策略.每一个线程分配一个时间片,线程在该分配的时间片内占用CPU执行任务,当线程片用完之后,就会处于就绪状态并让出CPU让其他线程占用使用.

上下文切换的时候需要保存当前线程的执行现场,当再次执行时根据保存的执行现场信息恢复执行现场

切换时机:

    • 当前线程的CPU时间片使用完处于就绪状态
    • 当前线程被其他线程中断的时候

线程死锁

什么是线程死锁

死锁是指两个或者两个以上的线程在执行过程中,因抢夺资源而造成的互相等待的现象,在无外力的作用下,这些线程会一直等待下去而无法运行

产生死锁的四个必要条件

  1. 互斥条件

线程对已经获取到的资源进行排他性使用,资源同时只能由一个线程占用,如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占用的资源被释放

  1. 请求持有条件

指一个线程已经占有至少一个资源,但又提出新的资源请求,而新的资源已经被其他线程占用,所以当先线程被阻塞,但阻塞的同时并并不释放自己已经获得的资源

  1. 不可剥夺条件

指线程获取的资源在自己使用完之前别的线程不能抢占,只有在自己使用完之后释放了才能被其他线程使用

  1. 环路等待条件

指发生死锁的时候必然存在一个线程一个资源的环形链

/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发.Test
 * @date 2023/4/13 9:35
 */
public class DeadLockTest2 {
    private static Object resourceA=new Object();
    private static Object resourceB=new Object();

    public static void main(String[] args) throws InterruptedException{
        Thread threadA=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println((Thread.currentThread() + "get ResourceA"));
                    try{
                        Thread.sleep(1000);
                        
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread()+"wait get sourceB");
                    synchronized (resourceB){
                        System.out.println(Thread.currentThread()+"get ResourceB");
                        
                    }
                };
            }
        });


        Thread threadB=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceB){
                    System.out.println((Thread.currentThread() + "get ResourceB"));
                    try{
                        Thread.sleep(1000);

                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread()+"wait get sourceA");
                    synchronized (resourceB){
                        System.out.println(Thread.currentThread()+"get ResourceA");

                    }
                };
            }
        });
        
        
        
        
    }
}

避免线程死锁

只需要破坏至少一个死锁的必要条件即可,但是目前其实能被破坏的只有请求并持有环路等待条件是可以被破坏的

造成死锁其实也和申请资源的顺序有很大的关系,使用资源的有序性原则就可以破坏避免死锁

举例:对上述代码的线程B进行修改

Thread threadB=new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized (resourceA){
            System.out.println((Thread.currentThread() + "get ResourceB"));
            try{
                Thread.sleep(1000);

            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread()+"wait get sourceA");
            synchronized (resourceB){
                System.out.println(Thread.currentThread()+"get ResourceA");

            }
        };
    }
});

守护线程与用户线程

  • daemon线程(守护线程)
  • user线程(用户线程)

main函数就是属于用户线程JVM启动的时候会调用main线程,但其实还调用了许多其他的守护线程

两者的区别

当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否由守护线程,也就是说守护线程不影响JVM的退出.也就是说只要有一个用户线程没有结束,正常情况下JVM就不会退出

/**
 * @author xingchen
 * @version V1.0
 * @Package com.并发.Test
 * @date 2023/4/13 16:44
 */
public class DaemonTest {
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });

        /**
         * 只需要设置参数为true就开启了守护线程
         * 
         */
        thread.setDaemon(true);
        thread.start();
    }
}

举例理解守护线程和用户线程的区别

public class DaemonOrUserTest {

    /**
     *thread线程里面是一个无限循环,运行之后主线程已经结束,但是jvm还没有退出
     * 说明父线程结束后,子线程还可以继续存在,也就是子线程的生命周期并不受父线程的影响
     * 也说明了在用户线程还存在的情况下JVM进程并不会终止
     *
     *
     *
     *
     * 设置成用户线程之后一旦主线程停止之后,jvm发现已经不存在用户线程了,就会终止JVN进程
     * 如果用户进程已经结束,但是守护线程还说运行,这个时候JVM不需要等待守护线程停止就直接结束JVM进程
     *
     *
     *
     *
     * main线程运行结束之后,JVM会自动启动一个DestroyJavaVm  的线程,该线程等待所有的用户线程结束后
     * 终止JVM进程
     *
     * 
     */
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ;){

                }
            }
        });

        thread.setDaemon(true);
        thread.start();
        System.out.println("main thread is over");
    }



}

总结

如果希望在主线程结束后JVM立马结束,那么在创建进程的时候可以将其设置为守护线程,否则的话就设置为用户线程

ThreadLocal

多线程访问退役个共享变量的时候容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时,为了保证线程安全,一般使用访问者在访问共享变量时候需要进行适当的同步

同步一般情况下使用加锁, 但是这种方式加重了使用者的负担.

那么可以使用创建一个变量后,每一个线程对其访问的时候访问的是自己线程的变量,本节的ThreadLocal就是这个作用.

ThreadLocal

是由JDK提供的,提供了线程本地变量,如果创建了一个ThreadLocal变量,那么访问这个变量的每一个线程都会有这个变量的本地副本

当多个线程操作这个变量的时候,实际操作的是自己本地内存的变量,从而避免了线程安全问题

使用实例

public class ThreadLocalTest {
    static void print(String str){
        System.out.println(str+":"+localVariable.get());
        //localVariable.remove();

    }
    static ThreadLocal<String> localVariable=new ThreadLocal<>();
    public static void main(String[] agrs){
        Thread threadOne=new Thread(new Runnable() {
            @Override
            public void run() {
                localVariable.set("threadOne local variable");
                print("threadOne");
                System.out.println("threadOne remove after"+":"+localVariable.get());
            }
        });
        Thread threadTwo=new Thread(new Runnable() {
            @Override
            public void run() {
                localVariable.set("threadTwo local variable");
                print("threadOne");
                System.out.println("threadTwo remove after"+":"+localVariable.get());
            }
        });



        threadOne.start();
        threadTwo.start();
        

    }
}

实现原理

set方法

public void set(T value){
    Thread t=Thread.currentThread();
    ThreadLoalMap map=getMap(t);
    if(map!=null){
        map.set(this,value);
        
    }else {
        createMap(t,value);
    }
}
ThreadLocalMap getMap(Thread t){
    return t.threadLocals;
}

getMap(t)

  • 方法的作用是获取线程自己的变量threadLocals,threadlocal变量被绑定到线程的成员变量上面
  • 如果个体Map(T)返回值不为空,则把value设置到threadLocals
  • 如果返回值为空则说明第一次调用的set方法,这时候创建当前线程的threadLocals变量

createMap(t,value)

创建当前线程的threadLocals变量

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

T get()方法

public void get(){

    //获取当前线程
    Thread t=Thread.currentThread();
    //获取当前线程的threadLocxals变量
    ThreadLocalMap map=getMap(t);
    //如果ThreadLocals不为空,则返回对应的本地变量
    if(map!=null){
        ThreadLocalMap.Entry e=map.getEntry(this);
    }
    if(e!=null){
        @SuppressWarnings("unchecked")
         T result=(T) e.value;
        return result;
    }
    //为空初始化当前线程的ThreadLocals成员变量
    return setInitialValue;
}

private T setInitialValue(){
    //初始化为null
    T value =initialValue;
    Thread t=Thread.currentThread();
    ThreadLocalMap map=getMap(t);
    //如果当前线程的本地变量不为空
    if(map!=null){
        map.set(this,value);

    }else {
        //如果当前线程的本地变量为空 则创建
        createMap(t,value);

    }
    return value;
}
private T initialValue(){
    return null;
}

void remove()

/**
 * 如果当前线程的threadLocals变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量
 
 */
public void remove(){
    ThreadLocalMap map=getMap(Thread.currentThread());
    if(m!=null){
        m.remove(this);
        
    }
    
}

不支持继承性

public class TestThreadLocal1 {
    public static ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public static void main(String[] args){
        threadLocal.set("hello world");
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread:"+threadLocal.get());
                
            }
        });
        thread.start();
        System.out.println("main:"+threadLocal.get());
    }
}

同一个ThreadLocal变量在父线程中被设置后,在子线程是获取不到的

在子线程里面调用的get方法是当前线程,而调用的set方法是设置的main线程

两者是不同的线程

InheritableThreadLocal类

继承自ThreadLocal,提供了一个特性,让子线程可以访问在父线程中设置的本地变量

public class InheritableThreadLocal<T> extends ThreadLocal{
    protected T childValue(T parentValue){
        return parentValue;
    }
    ThreadLocalMap.getMap(Thread t){
        return t.inheritableThreadLocals;
    }
    void create(Thread t,T firstValue){
        t.inheritableLocals=new TestThreadLocalMap(this,firstValue);
    }
}

总结:

InheritableThreadLocal类重写代码让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get()方法设置变量时,就会创建当前线程的InheritableThreadLocals变量,当父线程创建子线程时,构造函数就会把父线程的InheritableThreadLocals变量里面的本地变量复制一份保存到子线程的InheritableThreadLocals变量里面.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

02_CCC3.0数字钥匙_SPAKE2+执行流程

02_CCC3.0数字钥匙_SPAKE2执行流程Vehicle OEM Server&#xff1a;派生salt、L和w0&#xff1b;这三个参数需要服务器给到车辆端的&#xff0c;所以需要在服务器事先生成。用于与车辆端的做SPAKE2验证。DK Scrypt(pwd, s, Nscrypt, r, p, dkLen)&#xff1b; z0 DK[0 : 320]…

判断环形链表是否有环??返回环形链表的入口点

给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#xff08;…

IDEA插件-MavenHapler

1.安装Maven Helper Maven Helper 是 IntelliJ IDEA 中的一个插件&#xff0c;可以帮助您管理 Maven 依赖项。它可以帮助您更容易地删除不再需要的依赖项&#xff0c;查看依赖项的冲突&#xff0c;以及执行其他有关 Maven 依赖项的操作。 打开 IDEA 设置页面&#xff1a; 在插…

gpu超频超额训练导致电源关机

详细原理参见&#xff1a; 离显卡功耗实标还有多远&#xff1f;峰值功耗与电源关系终结篇 – FCPOWERUP极电魔方 和 【硬件科普】如何合理科学的选择电源功率的大小&#xff1f;_哔哩哔哩_bilibili 本人的1250w电源截图&#xff1a; 分析&#xff1a; 12V输出分了6路&#xff…

游戏逆向_Android读写游戏内容

一、背景 Android外挂的实现&#xff0c;需要涉及相应游戏内容的读写。读写的游戏内容包括代码和数据 针对不同的读写对象&#xff0c;通用的步骤就是寻找对象地址&#xff08;位置&#xff09;→获取相应权限→读写。下面将更详细介绍下相关实现。 二、实现方式 实现方式可…

了解最新的Android开发趋势和技术的秘诀

前言 当前&#xff0c;Android开发市场已经相当成熟&#xff0c;并且在全球范围内都非常活跃。Android是全球最受欢迎的移动操作系统之一&#xff0c;自Android开源以来&#xff0c;它已经改变了移动技术。市场上大量的企业和开发者都在积极地跟进、深入研究和开发Android系统…

大数据Flink进阶(十二):Flink本地模式开启WebUI

文章目录 Flink本地模式开启WebUI 一、在Flink 项目中添加本地模式 WebUI的依赖

2023 Java面试题短期突击攻略,已帮助400+位程序员成功拿到offer

2023春招已经开始一段时间了&#xff0c;很多同学会问Java面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。 国内的互联网面试&#xff0c;恐怕是现存的、最接近科举考试…

让PyTorch训练速度更快,你需要掌握这17种方法

掌握这 17 种方法&#xff0c;用最省力的方式&#xff0c;加速你的 Pytorch 深度学习训练。近日&#xff0c;Reddit 上一个帖子热度爆表。主题内容是关于怎样加速 PyTorch 训练。原文作者是来自苏黎世联邦理工学院的计算机科学硕士生 LORENZ KUHN&#xff0c;文章向我们介绍了在…

利用Chat GPT建立一个To-Do应用程序--我们终于遇到了我们的替代者吗?

海外Udemy、Coursera、Skillshare、Cantrill等平台精品编码课程&#xff0c;请访问 https://www.postcode.vip 我们看到GitHub Copilot在2021年10月发布&#xff0c;整个开发社区都疯了。 有些人声称我们很快就会失去工作&#xff0c;而其他人&#xff0c;像我一样&#xff0…

首家完成并购并进行重新备案公示的企业征信牌照公司-湖南省征信

2023年4月13日&#xff0c;中国人民银行长沙中心支行发布《关于对湖南省征信有限公司企业征信机构变更备案的公示》。内容显示中国人民银行长沙中心支行根据《征信业管理条例》《征信机构管理办法》《企业征信机构备案管理办法》及有关规定&#xff0c;决定受理湖南省征信有限公…

小程序学习四--组件--样式、数据、方法、属性、数据监听、生命周期、插槽、behavior

一、自定义组件 1.创建组件 2.组件引用--局部引用 3.组件引用--全局引用 4.组件和页面的区别 5.修改组件胡样式隔离选项 stypelsolation的可选值 二、自定义组件数据、方法、属性和数据监听 1.data数据 2.methods方法 事件处理函数、自定义方法_ 3.properties属性 页面中调…

JVM 内存结构

文章目录1、程序计数器2、虚拟机栈2.1 、定义2.2、栈内存溢出2.3 、线程运行诊断3、本地方法栈4、堆4.1、定义4.2 、堆内存溢出4.3 、堆内存诊断5、方法区&#xff08;Method Area&#xff09;5.1 、定义5.2、方法区组成5.3 、方法区内存溢出5.4 、运行时常量池5.5 、StringTab…

【JavaEE】TCP网络原理

目录 1.TCP协议定义 2.TCP原理 2.1确认应答机制 2.2超时重传机制 2.3连接管理 2.3.1建立连接&#xff08;三次握手&#xff09; 2.3.2断开连接&#xff08;四次挥手&#xff09; 2.4滑动窗口 2.5流量控制 2.6拥塞控制 2.7延迟应答 2.8捎带应答 2.9面向字节流&…

【STC8A8K64D4开发板】——按键检测

第2-3讲&#xff1a;按键检测 学习目的学习轻触按键和触摸按键硬件电路原理。学习STC8A8K64D4用作输入时相关寄存器的配置。掌握如何读取GPIO状态。掌握编写轻触按键和触摸按键检测程序。 硬件电路设计 IK-64D4开发板上设计了4个轻触按键和一个触摸按键&#xff0c;提供给用户作…

企业级信息系统开发讲课笔记2.3 利用MyBatis实现关联查询

文章目录零、本节学习目标一、查询需求&#xff08;一&#xff09;针对三张表关联查询&#xff08;二&#xff09;按班级编号查询班级信息&#xff08;三&#xff09;查询全部班级信息二、创建数据库表&#xff08;一&#xff09;创建教师表&#xff08;二&#xff09;创建班级…

BUUCTF-WEB-INF/web.xml泄露-SSTI注入

第八周 目录 WEB [RoarCTF 2019]Easy Java WEB-INF/web.xml泄露 WEB-INF/web.xml泄露原因 WEB-INF/web.xml泄露利用方法 解决方法 [BJDCTF2020]The mystery of ip 什么是板块注入 SSTI 为什么会产生 什么是render_template render_template&#xff1a; 我们为什么…

背包问题-动态规划

背包问题 容量有限的背包&#xff0c;给很多商品&#xff0c;商品有相应的体积与价值 01背包问题 一个背包 每个物品只有一个 最终状态方程 dp[i][j]max(dp[i-1][j],dp[i-1][j-w[i]]v[i]) 推导过程 由上一层推导过来 选择拿不拿i 最终代码 #include<iostream> #…

第12届蓝桥杯省赛真题剖析-2020年12月20日Scratch编程中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第124讲。 第12届蓝桥杯省赛举办了两次&#xff0c;这是2020年10月20日举行的第一次省赛中级组试题&#xff0c;比赛仍…

【Java实战篇】Day6.在线教育网课平台

文章目录一、需求&#xff1a;绑定媒资1、需求分析2、库表设计与模型类3、接口定义4、Mapper层开发5、Service层开发6、完善controller层二、需求&#xff1a;课程预览1、需求分析2、实现技术3、模板引擎4、Freemarker入门5、部署网站门户6、接口定义7、接口开发8、编写模板9、…