04-Thread--java并发编程基础

Join方法


/**
 * @Auther: match
 * @Date: 2021/2/22 12:21
 * @Description:
 */
public class JoinDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Task1 task1 = new Task1();
        Task2 task2 = new Task2();
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }

    public  static class Task1 implements  Runnable{
    
    

        @Override
        public void run() {
    
    
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("child trheadOne over");
        }
    }
    public  static class Task2 implements  Runnable{
    
    

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

如上代码在主线程里面启动了两个子线程,然后分别调用了它们的join()方法,那么主线程首先会在调用threadOne.join()方法后被阻塞,等待threadOne执行完毕后返回。threadOne执行完毕后threadOne.join()就会返回,然后主线程调用threadTwo.join()方法后再次被阻塞,等待threadTwo执行完毕后返回。

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

        Thread threadOne = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("ThreadOne begin run.");
             
                while (true) {
    
    

                }
            }
        });

        //get main thread
        Thread mainThread = Thread.currentThread();
        Thread threadTwo = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try{
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //此语句会在主线程的threadOne.join()处会抛出InterruptedException异常
                mainThread.interrupt();
            }
        });
        //启动子线程
        threadOne.start();
        // 延时1秒启动线程2
        threadTwo.start();
        try {
    
    
            threadOne.join();
        } catch (InterruptedException e) {
    
    
            System.out.println("main thred:" +e);
        }

    }
}

如上代码在threadOne线程里面执行死循环,主线程调用threadOne的join方法阻塞自己等待线程threadOne执行完毕,待threadTwo休眠1s后会调用主线程的interrupt()方法设置主线程的中断标志,从结果看在主线程中的threadOne.join()处会抛出InterruptedException异常。这里需要注意的是,在threadTwo里面调用的是主线程的interrupt()方法,而不是线程threadOne的。

Sleep方法

public class SleepDemo1 {
    
    
    private static final Lock lock =new ReentrantLock();

    public static void main(String[] args) {
    
    
        Thread threadA = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //获取独占锁
                lock.lock();
                try {
    
    
                    System.out.println("child threadA is in sleep");
                    Thread.sleep(10000);
                    System.out.println("child threadA is in awaked");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    //释放锁
                    lock.unlock();
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //获取独占锁
                lock.lock();
                try {
    
    
                    System.out.println("child threadB is in sleep");
                    Thread.sleep(10000);
                    System.out.println("child threadB is in awaked");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } finally {
    
    
                    //释放锁
                    lock.unlock();
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

如上代码首先创建了一个独占锁,然后创建了两个线程,每个线程在内部先获取锁,然后睡眠,睡眠结束后会释放锁。首先,无论你执行多少遍上面的代码都是线程A先输出或者线程B先输出,不会出现线程A和线程B交叉输出的情况。从执行结果来看,线程A先获取了锁,那么线程A会先输出一行,然后调用sleep方法让自己睡眠10s,在线程A睡眠的这10s内那个独占锁lock还是线程A自己持有,线程B会一直阻塞直到线程A醒来后执行unlock释放锁。下面再来看一下,当一个线程处于睡眠状态时,如果另外一个线程中断了它,会不会在调用sleep方法处抛出异常。

Interrupted

public class InterruptedDemo1 {
    
    
    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();
        //主线程休眠1秒
        Thread.sleep(1000);
        //中断子线程
        System.out.println("main thread interrupt thread");
        thread.interrupt();
        //等待子线程执行完
        thread.join();
        System.out.println("main is over");
    }
}

在如上代码中,子线程thread通过检查当前线程中断标志来控制是否退出循环,主线程在休眠1s后调用thread的interrupt()方法设置了中断标志,所以线程thread退出了循环。

当线程为了等待一些特定条件的到来时,一般会调用sleep函数、wait系列函数或者join()函数来阻塞挂起当前线程。比如一个线程调用了Thread. sleep(3000),那么调用线程会被阻塞,直到3s后才会从阻塞状态变为激活状态。但是有可能在3s内条件已被满足,如果一直等到3s后再返回有点浪费时间,这时候可以调用该线程的interrupt()方法,强制sleep方法抛出InterruptedException异常而返回,线程恢复到激活状态。下面看一个例子。

public class InterruptedDemo2 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread threadone = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(2000000);
                } catch (InterruptedException e) {
    
    
                    System.out.println("threadOne is interrupted while sleeping");
                    return;
                }
            }
        });
        //启动线程
        threadone.start();
        //确保子线程进入休眠状态
        Thread.sleep(1000);
        //打断子线程的休眠,让子线程从sleep函数返回
        threadone.interrupt();
        //等待子线程执行完毕
        threadone.join();
        System.out.println("main thread is over");
    }
}

在如上代码中,threadOne线程休眠了2000s,在正常情况下该线程需要等到2000s后才会被唤醒,但是本例通过调用threadOne.interrupt()方法打断了该线程的休眠,该线程会在调用sleep方法处抛出InterruptedException异常后返回。

死锁

public class DeadLockDemo1 {
    
    
    private static Object resourceA =new Object();
    private static Object resourceB = new Object();
    public static void main(String[] args) {
    
    
        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()+ "waiting 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()+ "waiting get sourceA");
                    synchronized (resourceA){
    
    
                        System.out.println(Thread.currentThread()+"get resourceA");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

Thread-0是线程A, Thread-1是线程B,代码首先创建了两个资源,并创建了两个线程。从输出结果可以知道,线程调度器先调度了线程A,也就是把CPU资源分配给了线程A,线程A使用synchronized(resourceA)方法获取到了resourceA的监视器锁,然后调用sleep函数休眠1s,休眠1s是为了保证线程A在获取resourceB对应的锁前让线程B抢占到CPU,获取到资源resourceB上的锁。线程A调用sleep方法后线程B会执行synchronized(resourceB)方法,这代表线程B获取到了resourceB对象的监视器锁资源,然后调用sleep函数休眠1s。好了,到了这里线程A获取到了resourceA资源,线程B获取到了resourceB资源。线程A休眠结束后会企图获取resourceB资源,而resourceB资源被线程B所持有,所以线程A会被阻塞而等待。而同时线程B休眠结束后会企图获取resourceA资源,而resourceA资源已经被线程A持有,所以线程A和线程B就陷入了相互等待的状态,也就产生了死锁。

resourceA和resourceB都是互斥资源,当线程A调用synchronized(resourceA)方法获取到resourceA上的监视器锁并释放前,线程B再调用synchronized(resourceA)方法尝试获取该资源会被阻塞,只有线程A主动释放该锁,线程B才能获得,这满足了资源互斥条件。

线程A首先通过synchronized(resourceA)方法获取到resourceA上的监视器锁资源,然后通过synchronized(resourceB)方法等待获取resourceB上的监视器锁资源,这就构成了请求并持有条件。

线程A在获取resourceA上的监视器锁资源后,该资源不会被线程B掠夺走,只有线程A自己主动释放resourceA资源时,它才会放弃对该资源的持有权,这构成了资源的不可剥夺条件。

线程A持有objectA资源并等待获取objectB资源,而线程B持有objectB资源并等待objectA资源,这构成了环路等待条件。所以线程A和线程B就进入了死锁状态。

守护线程与用户线程

Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,其实在JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别呢?区别之一是当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意,只要有一个用户线程还没结束,正常情况下JVM就不会退出。

设置守护线程

public class DaemonDemo01 {
    
    
    public static void main(String[] args) {
    
    
        Thread daemonThread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    

            }
        });
        //设置为守护线程
        daemonThread.setDaemon(true);
        daemonThread.start();
    }
}

如果你希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线程结束后子线程继续工作,等子线程结束后再让JVM进程结束,那么就将子线程设置为用户线程

ThreadLocal

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

同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal就可以做这件事情,虽然ThreadLocal并不是为了解决这个问题而出现的。
在这里插入图片描述

ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
在这里插入图片描述

public class ThreadLocalDemo {
    
    

    static void print(String str){
    
    
        //1.1 打印当前线程本地内容中的localVariable变量的值
        System.out.println(str+":"+localVariable.get());
        //1.2 清除当前线程本地内存中的localVariable变量
        localVariable.remove();
    }
    //2. 创建ThreadLocal变量
    static ThreadLocal<String> localVariable = new ThreadLocal<>();

    public static void main(String[] args) {
    
    
        //3.创建线程one
        Thread threadOne = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //3.1 设置线程One中本地变量localVariable的值
                localVariable.set("threadOne local variable");
                //3.2 调用打印函数
                print("threadOne");
                //3.3 打印本地变量值
                System.out.println("threadOne remove after " +" : "+localVariable.get());

            }
        });
        //4.创建线程two
        Thread threadTwo = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //4.1 设置threadTwo中本地变量localVariable的值
                localVariable.set("threadTwo local variable");
                //4.2 调用打印函数
                print("threadTwo");
                //4.3
                System.out.println("threadTwo remove after" + " : " + localVariable.get());
            }
        });
        //5. 启动线程
        threadOne.start();
        threadTwo.start();
    }
}

Thread类中有一个threadLocals和一个inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为null,只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建它们。其实每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。也就是说,ThreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的threadLocals变量里面将其拿出来使用。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。另外,Thread里面的threadLocals为何被设计为map结构?很明显是因为每个线程可以关联多个ThreadLocal变量。

ThreadLocal的set、get及remove方法的实现逻辑

  public T get() {
    
    
  		//1.获取当前线程
        Thread t = Thread.currentThread();
        //2.获取当线程的threadlocals变量
        ThreadLocalMap map = getMap(t);
        //3. 如果threadlocal不为null,则返回对应本地变量的值
        if (map != null) {
    
    
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //3.thraedLocals为空则初始化当前线程的threadLocals成员变量
        return setInitialValue();
    }
 public void set(T value) {
    
    
 		//1.获取当前线程
        Thread t = Thread.currentThread();
         //2.获取当线程的threadlocals变量
        ThreadLocalMap map = getMap(t);
        //3. 若当前map不为空,则放入当前的map,若无,则创建一个ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
public void remove() {
    
    
		 //根据当前线程获取到ThreadLocalMap
         ThreadLocalMap m = getMap(Thread.currentThread());
         //若不为空,则删除
         if (m != null)
             m.remove(this);
     }

每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。

ThreadLocal不支持继承性

public class ThreadLocalDemo02 {
    
    
    //1.创建线程变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
    
    
        //2.设置线程变量
        threadLocal.set("hello world");
        //3.启动子线程
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
            //子线程输出线程变量的值
                System.out.println("thead: "+threadLocal.get());
            }
        });
        thread.start();
        System.out.println("main: " +threadLocal.get());
    }
}

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的;但使用InheritableThreadLocal类可以让子线程访问父线程的ThreadLocal变量。

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

那么在什么情况下需要子线程可以获取父线程的threadlocal变量呢?情况还是蛮多的,比如子线程需要使用存放在threadlocal变量中的用户登录信息,再比如一些中间件需要把统一的id追踪的整个调用链路记录下来。其实子线程使用父线程中的threadlocal方法有多种方式,比如创建线程时传入父线程中的变量,并将其复制到子线程中,或者在父线程中构造一个map作为参数传递给子线程,但是这些都改变了我们的使用习惯,所以在这些情况下InheritableThreadLocal就显得比较有用

猜你喜欢

转载自blog.csdn.net/qq_41729287/article/details/113934097