多线程之线程八大核心基础

第一章、创建线程

1.1、实现多线程的方法到底有几种?

1.2、实现线程的两种方法

1.2.1 实现Runnable接口

package com.threadcoreknowledge.createthreads;


/**
 * 用Runnable方式创建线程
 */
public class RunnableStyle implements Runnable{
    public static void main(String[] args) {
        new Thread(new RunnableStyle()).start();
    }

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }
}

1.2.2 继承Thread类

package com.threadcoreknowledge.createthreads;

/**
 * 用Thread方式实现线程
 */
public class ThreadStyle extends Thread{
    @Override
    public void run() {
        System.out.println("用Thread类创建线程");
    }

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

1.2.3 两种方法的对比

结论:实现Runnable接口更好

  • 耦合性低:run方法应该和Thread解耦的。run方法专注于业务,Thread专注于创建线程。
  • 资源节约:Thread类创建线程一定要创建和销毁,而Runnable可以通过线程池复用。
  • 扩展性好:java不支持多继承,所以继承Thread也就不可以继承其他类。

1.2.4 两种方法的本质

方法一:最终调用了target.run()方法

public class RunnableStyle implements Runnable{
    public static void main(String[] args) {
        new Thread(new RunnableStyle()).start();
    }

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }
}

我们来看一下Thread中的start方法:

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

    /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

start方法首先进行一个异常判断,接着将当前线程加入线程组,然后调用native方法start0。start0由于是C++代码,我们暂时不看,只要知道start0中如果获取到时间片后会调用了run()方法。

   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

其中target就是我们传入的Runnable

    /* What will be run. */
    private Runnable target;

所以target.run(),就是我们重写Runnable接口中的方法。

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }

方法二:run()整个方法被重写
因为类继承了Thread类,将整个类的run()进行了重写。所以启动线程仅仅是使用了我们自定义的run()。

public class ThreadStyle extends Thread{
    @Override
    public void run() {
        System.out.println("用Thread类创建线程");
    }

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

此时,如果我们同时使用两种方法会如何呢?
我们来自定义如下代码:

/**
 * 同时使用Runnable和Thread两种实现线程的方式
 */
public class BothRunnableThread {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("我来自Runnable");
        }){
            @Override
            public void run(){
                System.out.println("我来自Thread");
            }
        }.start();
    }
}

发现只执行继承Thread的方法,而忽略了实现Runnable的方法。

我来自Thread

Process finished with exit code 0

原因:

  • 前面说继承Thread类重写了run(),所以通过实现Runnable接口的方法中的target.run()被覆盖,所以失效了。

1.2.5 总结

  • 通常我们可以将创建线程的方式分为两类,Oracle官方也是这么说的
  • 准确讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式
    – 方式1:实现Runnable接口的run()方法,并把Runnable实例作为target传给Thread类。
    – 方式2:重写Thread的run()方法

1.2.6 常见错误观点

1.2.6.1 线程池也是创建线程的一种方式?

仔细分析源码就可以看到线程池的创建依赖于实现Runnable方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
	public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
     static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

DefaultThreadFactory 类中的产生新线程方法newThread中就用到了Runnable接口。

结论:线程池并不算是创建线程的一种新的方式。

1.2.6.2通过Callable和FutureTask创建线程,也算是创建新线程的一种方式?
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {

通过上面源码可以看到FutureTask实现了Runnable接口。

1.2.6.3、匿名内部类
public class AnonymousInnerClassDemo {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }.start();
    }
}

这种方式就是通过新建一个Thread对象,然后重写run方法,显然不是新方式

1.2.6.4 lamdba表达式
public class Lambda {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        }).start();
    }
}

这个本质就是将一个新的Runnable到Thread对象中而已。

等同:

public class Lambda {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
    }
}

所以并不是创建线程的一种新的方式。

1.2.6.5 总结

实现多线程-常见面试问题

1、有多少种实现线程的方式?思路有5点

1、从不同的角度看,会有不同的答案

  1. 本质上
  2. 外在表现

2、典型答案是两种:继承Thread类和实现Runnable接口

3、我们看原理,本质上是一样的,因为Thread类也是实现Runnable接口。使用第一种方式的优势,结合源码:

if(target!=null){
  target.run();
}

4、具体展开说其他方式:外在表现形式:线程池、定时器、匿名内部类、lambda表达式等等。。。

5、结论

2、实现Runnable接口和继承Thread类哪种方式更好?

1、从代码架构的角度:耦合性:Thread和run()分开

2、新建线程的损耗:Thread需要新建销毁。Runnable可复用

3、Java不支持双继承:扩展性不够好。

第二章 启动线程

2.1 启动线程的正确和错误方式

一种是通过start方式启动,另一种是通过run方法启动

public class StartAndRunMethod {
    public static void main(String[] args) {
        Runnable runnable=()->{
            System.out.println(Thread.currentThread().getName());
        };
        runnable.run(); //main

        //Thread-0
        new Thread(runnable).start();
    }
}
main
Thread-0

Process finished with exit code 0

可以看到直接调用run(),不会开启新线程,而是main调用,而通过start(),则是新线程。

2.2 start方法原理解读

2.2.1 启动新线程

  • 调用start方法只是通知JVM在空闲的状态下启动此线程。
  • 调用start方法的顺序不代表线程开始时间的顺序。

2.2.2 准备工作

让自己处于就绪状态(除CPU之外的其它资源准备就绪)

2.2.3 不能重复start()

public class CantStartTwice {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
        thread.start();
    }
}
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:708)
	at com.threadcoreknowledge.startthread.CantStartTwice.main(CantStartTwice.java:16)

Process finished with exit code 1

调用第一次start()时,线程已经处于Runnable状态,不能继续调用start()了。

2.2.4 start()源码解析

1、启动新线程检查线程状态

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

连续调用两次start(),抛出的一场就是这里定义的。

2、加入线程组

   /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

3、调用start0()

boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }

start0是C++实现的一个native方法。

2.3 run()方法原理解读

源码解析

   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

如果直接调用run,那么调用者为主线程。
正确的方式是调用start方法,由start方法间接调用run方法。只有调用start(),线程才会经历完整的生命周期。run()方法只是start()方法在拿到cpu资源后调度的一种方法。如果直接调用run()方法,我们得到的是主线程,而不是子线程。

第三章 停止线程

3.1 讲解原理

原理介绍:使用interrupt来通知,而不是强制停止

如果我们想中断一个线程,而线程本身不想停止,我们也无能为力。

发出停止通知的线程对需要停止线程的业务不太了解,所以将线程停止的权利交给需要停止的线程本身,这也是为了线程的安全考虑。

3.2 最佳实践 如何正确停止线程

通常线程会在什么情况下停止?
1、线程中的方法执行完毕
2、线程中出现异常并且方法中没有捕获异常。

正确的停止方式:interrupt

  • 普通情况
/**
 * 描述: run方法类没有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }

    @Override
    public void run() {
        int num=0;
        while (num<=Integer.MAX_VALUE/2){
            if(num%10000==0){
                System.out.println(num+"是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }
}
107363000010000的倍数
107364000010000的倍数
107365000010000的倍数
107366000010000的倍数
107367000010000的倍数
107368000010000的倍数
107369000010000的倍数
107370000010000的倍数
107371000010000的倍数
107372000010000的倍数
107373000010000的倍数
107374000010000的倍数
任务运行结束了

这种情况下不会停止,因为停止的权利在thread自己手中,而不在main线程中。要停止线程,就需要Thread线程在run方法中配合调用停止。

/**
 * 描述: run方法类没有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();
    }

    @Override
    public void run() {
        int num=0;
        while (!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){
            if(num%10000==0){
                System.out.println(num+"是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }
}

这样线程就会配合进行停止。

27681000010000的倍数
27682000010000的倍数
27683000010000的倍数
27684000010000的倍数
27685000010000的倍数
27686000010000的倍数
27687000010000的倍数
27688000010000的倍数
27689000010000的倍数
27690000010000的倍数
27691000010000的倍数
27692000010000的倍数
27693000010000的倍数
27694000010000的倍数
27695000010000的倍数
27696000010000的倍数
27697000010000的倍数
27698000010000的倍数
27699000010000的倍数
27700000010000的倍数
27701000010000的倍数
任务运行结束了
  • 线程可能会被阻塞的情况(sleep())
    当被允许使用sleep()时,我们模拟一下场景。因为可以使用sleep(),所以无需整那么多的数来构建处理时间,而是使用sleep()来延时,我们利用lambda表达式来创建一个线程,此线程的run()方法中输出300以内100的倍数,然后sleep()一秒,并且捕获异常。在调用此线程时,我们让线程sleep() 500ms,然后调用interrupt方法,此时运行出现:java.lang.InterruptedException:sleep interrrupted
public class RightWayStopThreadWithSleep {
    public static void main(String[] args) {
        Runnable runnable=()->{
            int num=0;
            while (num<=300&&!Thread.currentThread().isInterrupted()){
                if(num%100==0){
                    System.out.println(num+"是100的倍数");
                }
                num++;
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
0100的倍数
100100的倍数
200100的倍数
300100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:25)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

原因:在线程执行sleep()期间,不能调用interrupt方法使其中断,否则会出现异常。

  • 如果线程在每次迭代(while,for)后都被阻塞
    如下代码所示:每次循环都会休眠10ms,打印从0~10000中100的倍数,然后主线程休眠5S后发送中断。结果如下所示,前5S正常打印,5S后出现睡眠中断异常。
/**
 * 如果在执行过程中,每次循环都会调用sleep或wait等方法,那么
 */
public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) {
        Runnable runnable=()->{
            int num=0;
            try {
            while (num<=10000){ //这里不需要加上中断检测,因为睡眠会占用绝大多数时间,有睡眠会响应异常
                if(num%100==0){
                    System.out.println(num+"是100的倍数");
                }
                num++;
                TimeUnit.MILLISECONDS.sleep(10);//5s后线程仍然会继续处理,对于CPU来说计算花费的时间特别短,大多数时间都在睡眠中的10ms中,所以当主线程发送interrupt到thread中的时候会出现睡眠中断异常。
                }
            }catch (InterruptedException e) {
                e.printStackTrace();
            }

        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
100100的倍数
200100的倍数
300100的倍数
400100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadWithSleepEveryLoop.lambda$main$0(RightWayStopThreadWithSleepEveryLoop.java:24)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

  • while内try/catch的问题
    与上面场景类似,只是将try/catch放置在while循环内,我们观察发生的情况。
public class CantInterrupt {
    public static void main(String[] args) {
        Runnable runnable=()->{
            int num=0;
            while (num<=10000){
                if(num%100==0){
                    System.out.println(num+"是100的倍数");
                }
                num++;
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        Thread thread=new Thread(runnable);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
0100的倍数
100100的倍数
200100的倍数
300100的倍数
400100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:24)
	at java.lang.Thread.run(Thread.java:748)
500100的倍数
600100的倍数
700100的倍数
800100的倍数
900100的倍数
....

原因:5s后,发送中断方法调用Thread线程,此时线程仍然在进行睡眠,抛出睡眠中断异常,但由于已经进行了捕获,并且try catch处于循环内,循环并没有被打破,继续执行。

那我们思考一个问题:如果加上原来的中断判断处理是否就能达到我们想要的效果呢?

public class CantInterrupt {
    public static void main(String[] args) {
        Runnable runnable=()->{
            int num=0;
            while (num<=10000&&!Thread.currentThread().isInterrupted()){
                if(num%100==0){
                    System.out.println(num+"是100的倍数");
                }
                num++;
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        Thread thread=new Thread(runnable);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
0100的倍数
100100的倍数
200100的倍数
300100的倍数
400100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:24)
	at java.lang.Thread.run(Thread.java:748)
500100的倍数
600100的倍数
700100的倍数
800100的倍数
900100的倍数
...

发现还是原来的效果!!!
原因是:== JVM在抛出异常后,自动将interrupt丢弃了,所以判断失效==

3.3 实际开发中的两种最佳实践

3.3.1 传递中断(优先选择)

模拟一个复杂业务的场景:

public class RightWayStopThreadInProd implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new RightWayStopThreadInProd());
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }

    /**
     * run方法中模拟一个复杂业务逻辑
     */
    @Override
    public void run() {
        while (true&&!Thread.currentThread().isInterrupted()){
            System.out.println("Info.........");
            throwInMethod();
        }
    }

	/**
	*执行这个复杂业务需要1s
	*/
 	private void throwInMethod() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
Info.........
Info.........
Info.........
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.throwInMethod(RightWayStopThreadInProd.java:69)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.run(RightWayStopThreadInProd.java:60)
	at java.lang.Thread.run(Thread.java:748)
Info.........
Info.........
Info.........

发现这种情况就如我们上面描述的那样,JVM处理中断异常后,interrupt信息被自动清除,所以循环还会继续。

while中有别的工作组人员编写传递过来的方法throwInMethod(),此方法我们作为run()的编写者具体的实现并不知晓,所以在进行处理时我们当作普通方法进行处理,并且打印日志。在实际生产环境中,run方法的业务逻辑很复杂,会有大量的信息和日志输出。所以我们这里模拟输出Info…。由于在while()中又出现了上文发生的try/catch事件,在抛出异常后,interrupt信息被JVM自动清除。所以循环还会继续。

在茫茫日志中寻找此错误太难了!!!

并且即便我们加上中断判断也是无效的。

  • 作为throwInMethod()方法的编写者
    如果我们时throwInMethod()方法的编写者,那么我们一定不要使用try/catch方法来捕获异常,而使用向上抛出异常。
    private void throwInMethod() throws InterruptedException {
//        try {
//            TimeUnit.SECONDS.sleep(1);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        TimeUnit.SECONDS.sleep(1);
    }

这样到了run()方法中,由于run()属于高层方法,它无法再向上抛出异常,只能自己通过try/catch来处理异常,那么对run()方法的编写者来说,处理和打印日志的工作就轻松很多。

Runnable中的run()方法没有定义异常处理,所以重写run()无法抛出异常

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
/**
 * 描述:   run无法抛出checked Exception,只能用try/catch
 */
public class RunThrowException implements Runnable {
    public static void main(String[] args) {

    }

    public void exception() throws Exception {
        throw new Exception();
    }


    @Override
    public void run() {
       try {
         exception();
      } catch (Exception e) {
           e.printStackTrace();
      }
//        exception(); //exception无法抛出异常,只能自己通过try/catch处理
    }
}

  • 作为run方法的编写者
    我们尽量使用try/catch的方式来包含调用的throwInMethod,因为你没法保证同事不坑你。
 /**
     * run方法中模拟一个复杂业务逻辑
     */
    @Override
    public void run() {
        while (true&&!Thread.currentThread().isInterrupted()){
            System.out.println("Info.........");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                System.out.println("打印错误日志");
                System.out.println("发送短信给管理员");
                e.printStackTrace();
            }
        }
    }
Info.........
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.throwInMethod(RightWayStopThreadInProd.java:50)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.run(RightWayStopThreadInProd.java:35)
	at java.lang.Thread.run(Thread.java:748)
Info.........
打印错误日志
发送短信给管理员
Info.........
Info.........

这样问题就能得到很好的解决!!!

3.3.2 恢复中断

如果因为某些原因不能或不想直接抛出异常,那么就在catch语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中依然能够检查刚才发生了中断回到刚才RightWayStopThreadInProd补上中断,让他跳出。

/**
 * 描述: 最佳实践2:如果有某些原因不能或不想直接抛出异常,那么就在catch语句中调用Thread.currentThread().interrupt()来恢复设置中断状态,以便于在后续的执行中依然能够检查刚才发生了中断
 *                 回到刚才RightWayStopThreadInProd补上中断,让它跳出
 * 那么在run()就会强制try/catch
 */
public class RightWayStopThreadInProd2 implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }

    /**
     * run方法中模拟一个复杂业务逻辑
     */
    @Override
    public void run() {
        while (true){
            if(Thread.currentThread().isInterrupted()){
                System.out.println("发起中断,退出循环...");
                System.out.println("发送短信给管理员");
                System.out.println("保存日志");
                break;
            }
            System.out.println("Info.........");
            throwInMethod();
        }
    }

    private void throwInMethod() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();//恢复中断
            e.printStackTrace();
        }
    }
}
Info.........
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd2.throwInMethod(RightWayStopThreadInProd2.java:54)
	at com.threadcoreknowledge.stopthreads.RightWayStopThreadInProd2.run(RightWayStopThreadInProd2.java:48)
Info.........
	at java.lang.Thread.run(Thread.java:748)
发起中断,退出循环...
发送短信给管理员
保存日志

Process finished with exit code 0

3.4 停止线程的错误方法

3.4.1 被弃用过的stop、suspend、resume方法

场景:模拟士兵领取装备

/**
 * 描述: 错误的停止方法:用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本党委的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)
 */
public class StopThread implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //戛然而止
        thread.stop();
    }

    @Override
    public void run() {
        //模拟指挥军队:一共5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
        for (int i = 1; i <= 5; i++) {
            System.out.println("连队"+i+"开始领取武器");
            for (int j = 1; j <=10; j++) {
                System.out.println(j);
                try {
                    TimeUnit.MILLISECONDS.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队"+i+"已经领取完毕");
        }
    }
}
连队1开始领取武器
1
2
3
4
5
6
7
8
9
10
连队1已经领取完毕
连队2开始领取武器
1
2
3
4
5
6
7
8
9

Process finished with exit code 0

这就造成了一个很麻烦的结果,连队1都已经领取完毕。领队3,4,5都没有领取装备,到前线统一分配。可是联队2中的10号士兵没有领取到装备,并且联队2的其它成员都领取到了装备,这就是使用stop()方法造成的后果。

Suspend()方法:suspend()方法是带锁进行消息,当需要此锁资源进行唤醒时,容易产生死锁,弃用。
Resume()方法类似,不再解释。

3.4.2 用volatile设置boolean标记位(看似很高级,实则错误)

看似可行:如下场景

package com.threadcoreknowledge.stopthreads.volatiledemo;

/**
 * @author xyf
 * @version 1.0
 * @date 2020/11/30 1:42 AM
 */

import java.sql.Time;
import java.util.concurrent.TimeUnit;

/**
 * 描述:    演示用volatile的局限:part1 看似可行
 */
public class WrongWayVolatile implements Runnable{

    private volatile boolean canceled=false;

    @Override
    public void run() {
        int num=0;
        try {
        while (num<=10000&&!canceled){
            if(num%100==0){
                System.out.println(num+"是100的倍数");
            }
            num++;
                TimeUnit.MILLISECONDS.sleep(1);
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        WrongWayVolatile wrongWayVolatile = new WrongWayVolatile();
        Thread thread=new Thread(wrongWayVolatile);
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //取消
        wrongWayVolatile.canceled=true;
    }
}
0100的倍数
100100的倍数
200100的倍数
300100的倍数
400100的倍数
500100的倍数
600100的倍数
700100的倍数
800100的倍数
900100的倍数
1000100的倍数
1100100的倍数
1200100的倍数
1300100的倍数
1400100的倍数
1500100的倍数
1600100的倍数
1700100的倍数
1800100的倍数
1900100的倍数
2000100的倍数
2100100的倍数
2200100的倍数
2300100的倍数
2400100的倍数
2500100的倍数
2600100的倍数
2700100的倍数
2800100的倍数
2900100的倍数
3000100的倍数
3100100的倍数
3200100的倍数
3300100的倍数
3400100的倍数
3500100的倍数
3600100的倍数
3700100的倍数
3800100的倍数
3900100的倍数
4000100的倍数

Process finished with exit code 0

局限性:
场景:生产者消费者,使用阻塞队列

/**
 * 描述:  演示用volatile的局限part2
 * 陷入阻塞时,volatile是无法生效的
 * 此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
 */
public class WrongWayVolatileCantStop {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 阻塞队列,满的时候放不进去会阻塞。空的时候取不出来也会阻塞。
         */
        ArrayBlockingQueue storage=new ArrayBlockingQueue(10);
        //生产者
        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        //给生产者1S钟时间生产
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //消费者
        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()){
            System.out.println(consumer.storage.take()+"被消费了");
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者不需要更多数据了");
        //一旦消费者不需要更多数据了,我们应该让生产者也停下来,但是实际情况
        producer.canceled=true;
        System.out.println(producer.canceled);

    }
}

class Producer implements Runnable{
    public volatile boolean canceled=false;

    /**
     *  阻塞队列
     */
    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }


    @Override
    public void run() {
        int num=0;
        try {
            while (num<=10000&&!canceled){
                if(num%100==0){
                    //存进阻塞队列
                    storage.put(num);
                    System.out.println(num+"是100的倍数,被放进仓库中了");
                }
                num++;
//                TimeUnit.MILLISECONDS.sleep(1);  去掉这行代码是为了让阻塞队列阻塞。生产者快速生产。
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("生产者停止运行");
        }
    }
}

class Consumer{
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    /**
     * 判断是否需要继续消费,我们这里使用随机数来模拟
     * @return 是/否
     */
    public boolean needMoreNums(){
        if(Math.random()>0.95){
            return false;
        }
        return true;
    }
}
0100的倍数,被放进仓库中了
100100的倍数,被放进仓库中了
200100的倍数,被放进仓库中了
300100的倍数,被放进仓库中了
400100的倍数,被放进仓库中了
500100的倍数,被放进仓库中了
600100的倍数,被放进仓库中了
700100的倍数,被放进仓库中了
800100的倍数,被放进仓库中了
900100的倍数,被放进仓库中了
消费者不需要更多数据了
true

错误原因:

@Override
    public void run() {
        int num=0;
        try {
            while (num<=10000&&!canceled){//程序无法到达哦canceled
                if(num%100==0){
                    storage.put(num);//线程阻塞在这里
                    System.out.println(num+"是100的倍数,被放进仓库中了");
                }
                num++;
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("生产者停止运行");
        }
    }
}

解决方案:

使用interrupt:

System.out.println("消费者不需要更多数据了");
//一旦消费者不需要更多数据了,我们应该让生产者也停下来,但是实际情况
producerThread.interrupt(); //使用interrupt来停止
System.out.println(producer.canceled);

并且在run方法中添加响应中断

@Override
    public void run() {
        int num=0;
        try {
            while (num<=10000&&!Thread.currentThread().isInterrupted()){
                if(num%100==0){
                    //存进阻塞队列
                    storage.put(num);
                    System.out.println(num+"是100的倍数,被放进仓库中了");
                }
                num++;
//                TimeUnit.MILLISECONDS.sleep(1);  去掉这行代码是为了让阻塞队列阻塞。生产者快速生产。
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("生产者停止运行");
        }
    }
}

3.4.3 重要函数的源码解析

3.4.3.1、interrupt方法
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}
private native void interrupt0();

如何分析native方法

1、进gitHub(也可以进openJDK网站)

2、点“搜索文件”,搜索对应的c代码

3.4.3.2、停止线程相关重要函数解析

​ 判断是否已经被中断的相关方法

​ static boolean interrupted()

//静态方法的interrupted()返回的是当先线程是否被中断。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

​ boolean isinterrupted()

public boolean isInterrupted() {
    return isInterrupted(false);
}

​ Thread.interrupted()的目的对象

package com.threadcoreknowledge.stopthreads.volatiledemo;

/**
 * @author xyf
 * @version 1.0
 * @date 2020/12/1 4:18 PM
 */

/**
 * 描述: 注意Thread.interrupted()方法的目标对象是"当前线程",而不管本方法来自于哪个对象
 */
public class RightWayInterrupted {
    public static void main(String[] args) {
        Thread threadOne=new Thread(()->{
            for (;;){

            }
        });
        //启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupt: "+threadOne.isInterrupted());
        //获取中断标志并重置(静态方法关心的不是哪个实例调用的它,而是哪个线程调用的它,这里是main线程的调用)
        System.out.println("isInterrupt: "+threadOne.interrupted());
        //获取中断标志并重置(main线程的调用)
        System.out.println("isInterrupted: "+Thread.interrupted());
        //获取中断标志(threadOne已经执行了interrupt())
        System.out.println("isInterrupted: "+threadOne.isInterrupted());
    }
}

3.5 常见面试问题

3.5.1、如何停止线程:

​ 1、原理:用interrupt来请求、好处

​ 2、想停止线程,要请求方(发出请求信号)、被停止方(增加响应中断的代码、处理异常等等)、子方法(优先抛出、再次设置中断状态)被调用方相互配合

​ 3、最后再说出错误的方法:stop、suspend已废弃,volatile的boolean无法处理长时间阻塞的情况。

3.5.2、如何处理不可中断的阻塞

​ SocketIO和ReentrantLock不可中断的阻塞。没有通用的解决方法。针对具体的使用具体的解决方案

​ 针对IO的方法是使用一些可以响应中断的IO。就用这些特定的IO。

​ 针对ReentrantLock,提供了一个:lock.lockInterruptibly();方法,此方法可以响应中断。

第四章 线程的状态

线程的六种状态

  • New:创建还未运行的线程状态

  • Runnable:一旦调用start(),就会达到Runnable状态。既可以是操作系统中的Ready(未拿到CPU),也可以是running(拿到CPU)

  • Blocked:进入synchronized代码中,拿不到monitor的话就会进入Blocked状态(仅仅针对synchronized关键 字,ReentrantLock等等不会进入这个状态。)

  • Waiting:没有设置超时时间的Object.wait()、Thread.join()、LockSupport.park()方法后的状态

  • Timed Waiting:设置超时时间的Thread.sleep(time)、Object.wait(time)、Thread.join(time)、 LockSupport.parkNanos(time)、LockSupport.parkUntil(time)方法后的状态

  • Terminated:线程执行完毕或者抛出异常就会进入Terminated

可以通过getState()方法获取线程的状态

public class NewRunnableTerminated implements Runnable {
    public static void main(String[] args) {
        Thread thread = new Thread(new NewRunnableTerminated());
        //打印出NEW的状态
        System.out.println(thread.getState());//NEW
        thread.start();
        System.out.println(thread.getState());//RUNNABLE
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出RUNNABLE状态,即使是正在运行,也是RUNNABLE,而不是RUNNING
        System.out.println(thread.getState());//RUNNABLE
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.getState()); //TERMINATED
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
    }
}

public class BlockedWaitingTImedWaiting implements Runnable{
    public static void main(String[] args) {
        BlockedWaitingTImedWaiting thread = new BlockedWaitingTImedWaiting();
        Thread thread1 = new Thread(thread);
        thread1.start();
        Thread thread2 = new Thread(thread);
        thread2.start();
        //打印出Timed_Waiting状态,因为正在执行Thread.sleep(1000);
        System.out.println(thread1.getState());
        //打印出BLOCK状态,因为thread2想拿到syn的锁确拿不到
        System.out.println(thread2.getState());//Blocked
        try {
            Thread.sleep(1300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出waitting状态,因为现在已经过了Thread.sleep(1000);并且进入到wait()代码中。
        System.out.println(thread1.getState());//waiting
    }

    @Override
    public void run() {
        syn();
    }

    private synchronized void syn() {
        try {
            Thread.sleep(1000);
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
TIMED_WAITING
BLOCKED
WAITING

状态间的转化图示

状态转化的特殊情况:
1、从Object.wait()状态刚被唤醒时,通常不能立刻抢到monitor锁,那就从waiting状态到blocked状态,抢到锁之后再进入Runnable状态。
2、如果发生异常,可以直接跳到terminated状态,不再遵循此路径,比如可以直接从waiting到terminated。

第五章 Thread和Object类中的重要方法详解

常见面试问题
1、为什么线程通信方法wait()、notify()和notifyAll()被定义在Object类中?而sleep()定义在Thread类中?
2、用3种方式实现生产者模式
3、join、sleep、wait期间线程的状态分别是什么?为什么?

5.1 方法概览

5.1.1Thread中的方法

方法名简介
Threadsleep带锁休眠
join等待其他线程执行完毕
yield放弃已经获取到的CPU资源
currentThread获取当前执行线程的引用
start、run相关启动线程开关
interrupt相关中断线程
stop(),suspend(),resume()相关已废弃
Objectwait、notify()、notifyAll()让线程暂时休息或唤醒

5.2 wait、notify、notifyAll方法详解

5.2.1 wait 作用、用法:

wait必须在synchronized代码块中,当执行wait时,会释放锁。
直到以下4种情况之一发生时,才会被唤醒。
1、另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程;
2、另一个线程调用这个对象的nofifyAll()方法
3、过了wait(long timeout)规定的超时时间,如果传入0就是永久等待。
4、线程自身调用了interrupt()

5.2.2 notify、notifyAll

notify和notifyAll都只有在synchronized中才能使用,否则会抛出异常

  • wait、notify、notifyAll的特点、性质
    1、用之前必须拥有monitor,所以必须在同步代码块才能使用。
    2、notify只能唤醒其中一个,notifyAll唤醒所有线程。
    3、wait、notify、notifyAll属于Object类
    4、类似功能的condition
    5、同时持有多个锁的情况

5.2.3 为什么wait、notify、notifyAll属于Object类?

因为都是在同步代码块中调用,是对一个锁对象的占有和释放,属于Object对象,而不属于某一个线程。

5.3 手写生产者消费者设计模式

/**
 * 描述:用wait/notify来实现
 */
public class ProducerConsumerModel {
    public static void main(String[] args) {
        EventStorage storage=new EventStorage();
        Producer producer=new Producer(storage);
        Consumer consumer=new Consumer(storage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
class Producer implements Runnable{
    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.produce();
        }
    }
}

class Consumer implements Runnable{

    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.consume();
        }
    }
}

//仓库类
class EventStorage{
    private List<Date> storage=new ArrayList<>();

    public synchronized void produce() {
        while (storage.size()==10){
            try {
                System.out.println("生产者开始等待...");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("生产者开始生产第"+storage.size()+"个商品");
        notify();
    }

    public synchronized void consume() {
        while (storage.size() ==0) {
            try {
                System.out.println("消费者开始等待");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.remove(storage.get(0));
        System.out.println("消费者开始消费第" + (storage.size()+1) + "个商品,还剩"+storage.size()+"个");
        notify();
    }
}
生产者开始生产第1个商品
生产者开始生产第2个商品
生产者开始生产第3个商品
生产者开始生产第4个商品
生产者开始生产第5个商品
生产者开始生产第6个商品
生产者开始生产第7个商品
生产者开始生产第8个商品
生产者开始生产第9个商品
生产者开始生产第10个商品
生产者开始等待...
消费者开始消费第10个商品,还剩9个
消费者开始消费第9个商品,还剩8个
消费者开始消费第8个商品,还剩7个
消费者开始消费第7个商品,还剩6个
消费者开始消费第6个商品,还剩5个
消费者开始消费第5个商品,还剩4个
消费者开始消费第4个商品,还剩3个
消费者开始消费第3个商品,还剩2个
消费者开始消费第2个商品,还剩1个
消费者开始消费第1个商品,还剩0个
消费者开始等待

5.4 常见面试题

5.4.1 用程序实现两个线程交替打印0~100的奇偶数

思路1:synchronized代码块。两个线程分别处理奇数和偶数

public class WaitNotifyPrintOddEvenSyn {
    private static int count;
    private static final Object lock=new Object();

    //新建两个线程
    //第一个只处理偶数,第二个只处理基数(用位运算)
    //用synchronized作为通信的机制
    public static void main(String[] args) {
        new Thread(()->{
            while (count<100){
                synchronized (lock){
                    if((count & 1)==0){
                        System.out.println(Thread.currentThread().getName()+":"+count++);
                    }
                }
            }
        },"Odd").start();
        new Thread(()->{
            while (count<100){
                synchronized (lock){
                    if((count & 1)==1){
                        System.out.println(Thread.currentThread().getName()+":"+count++);
                    }
                }
            }
        },"Even").start();
    }


}
Odd:0
Even:1
Odd:2
Even:3
Odd:4
Even:5
Odd:6
Even:7
Odd:8
Even:9
Odd:10
Even:11
Odd:12
Even:13
Odd:14
Even:15
Odd:16
Even:17
Odd:18
Even:19
Odd:20
Even:21
Odd:22
Even:23
Odd:24
Even:25
Odd:26
Even:27
Odd:28
....

此方法会存在浪费计算资源的问题,因为存在很多无用判断。

思路2: wait()和notify()方法

import java.util.concurrent.TimeUnit;

/**
 * 描述:两个线程交替打印0~100的奇偶数,用wait()和notify()实现
 */
public class WaitNotifyPrintOddEvenSyn2 {
    private static int count;
    private final static Object lock=new Object();

    //1、拿到锁,就打印
    //2、一旦打印完,唤醒其他线程,就进行休眠
    public static void main(String[] args) throws InterruptedException {
        new Thread(new OddAndEven(),"Odd").start();
        new Thread(new OddAndEven(),"Even").start();
        TimeUnit.MILLISECONDS.sleep(10);


    }

    static class OddAndEven implements Runnable{

        @Override
        public void run() {
            while (count<=100){
                synchronized (lock){
                    System.out.println(Thread.currentThread().getName()+":"+count++);
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
Odd:0
Even:1
Odd:2
Even:3
Odd:4
Even:5
Odd:6
Even:7
Odd:8
Even:9
Odd:10
Even:11
Odd:12
Even:13
Odd:14
Even:15
Odd:16
Even:17
Odd:18
Even:19
Odd:20
Even:21
Odd:22
Even:23
Odd:24
Even:25
Odd:26
Even:27

5.4.2 为什么wait()需要在同步代码块内使用,而sleep()不需要

​ 为了通信可靠:如果没有synchronized保护。在执行wait()之前,可能会切换到别的线程执行先执行notify()方法,再切换回此线程执行wait()方法,那么这样就有可能会有死锁的产生。而sleep本身就是针对自己,而不需要进行线程通信,所以无需进行同步保护。

5.4.3 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?

​ 因为这三个方法是锁级别的操作方法。而锁是绑定某一个对象中,而不是线程中。而通常情况下,一个线程会具有多把锁,如果将这三个锁级别的方法定义在线程里,就会导致一个线程只能拥有一把锁,也无法实现notify的精准通知了。

5.4.4wait方法是属于Object对象的,那调用Thread.wait会怎么样?

Thread类本身不适合作为锁对象,因为在线程退出之后会自动执行notify()方法。

5.4.5 如何选择用notify还是notifyAll?

notify是通知一个线程,notifyAll是通知所有线程。

5.4.6 notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

​ 只不过就是问题的重现,在一开始也会出现此情况,抢夺失败的线程继续就行等待。

5.4.7 用suspend()和resume()来阻塞线程可以吗?为什么?

​ 不推荐,推荐使用wait()和notify()来实现。

5.5 Sleep方法详解

作用:只想让线程在预期时间执行,其他时间不占用CPU资源。
特点:

  • 不释放锁(包括synchronized和lock)
  • 和wait不同
Sleep方法响应中断

1、抛出InterruptedException
2、清除中断状态

package com.threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * @author xyf
 * @version 1.0
 * @date 2020/12/1 11:16 PM
 */

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 描述:每一秒钟输出当前时间,被中断,观察
 * Thread.sleep()
 * TimeUnit.SECONDS.sleep()
 */
public class SleepInterrupted implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
              //优雅的写法
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("我被中断了!");
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        SleepInterrupted sleepInterrupted = new SleepInterrupted();
        Thread thread=new Thread(sleepInterrupted);
        thread.start();
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}
Mon May 03 09:12:57 CST 2021
我被中断了!
Mon May 03 09:12:57 CST 2021
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:24)
	at java.lang.Thread.run(Thread.java:748)
Mon May 03 09:12:58 CST 2021
Mon May 03 09:12:59 CST 2021
Mon May 03 09:13:00 CST 2021
Mon May 03 09:13:01 CST 2021
Mon May 03 09:13:02 CST 2021
Mon May 03 09:13:03 CST 2021
Mon May 03 09:13:04 CST 2021

响应异常后清除了中断状态,继续执行。

Sleep的两种写法

Thread.sleep(1000);
TimeUnit.SECONDS.sleep(1);

一句话总结Sleep

sleep方法可以让线程进入waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态

常见面试问题

wait、notify、sleep异同(方法属于哪个对象?线程状态怎么切换)
相同点:
都可以阻塞
响应中断
不同点:
同步方法中(wait、notify需要,sleep不需要)
释放锁(wait会释放,sleep不会释放)
指定时间(wait可以不传,sleep必须传)
所属类不同(wait、notify属于Object,sleep属于Thread)

5.6 join方法详解

作用:因为新的线程加入了我们,所以我们要等他执行完成之后再出发。

用法:
main等待thread1执行完毕,注意谁等谁
场景:一个主线程中有5个子线程需要执行,把5个线程加入主线程中。

public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行完毕");
        });

        thread1.start();
        thread2.start();
        System.out.println("开始等待子线程运行完毕");
        thread1.join();
        thread2.join();
        System.out.println("所有子线程执行完毕");
    }
}
开始等待子线程运行完毕
Thread-1执行完毕
Thread-0执行完毕
所有子线程执行完毕

Process finished with exit code 0

主线程会等待两个子线程执行完毕再继续执行!

如果去掉join():

开始等待子线程运行完毕
所有子线程执行完毕
Thread-1执行完毕
Thread-0执行完毕

Process finished with exit code 0

主线程在加入子线程之后,直接打印语句,不会等待子线程的执行。

join期间发生中断

/**
 * 描述: 演示join期间被中断的效果
 */
public class JoinInterrupt {
    public static void main(String[] args) {
        //拿到主线程
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(() -> {
            try {
                mainThread.interrupt();
                TimeUnit.SECONDS.sleep(5);
                System.out.println("Thread1 finished");
            } catch (InterruptedException e) {
                System.out.println("子线程被中断");
                e.printStackTrace();
            }
        });
        thread1.start();
        System.out.println("等待子线程运行完毕");
        try {
            thread1.join();//这里是主线程在等子线程,所以必须模拟主线程中断
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中断了");
            e.printStackTrace();
        }
        //join抛出异常,并且清除异常
        System.out.println(Thread.currentThread().isInterrupted());
        System.out.println("子线程已经运行完毕");
    }
}
等待子线程运行完毕
java.lang.InterruptedException
main被中断了
false //异常已经被join清除,所以打印false
子线程已经运行完毕
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at com.threadcoreknowledge.threadobjectclasscommonmethods.JoinInterrupt.main(JoinInterrupt.java:31)
Thread1 finished.  //5秒后出现此语句

Process finished with exit code 0

如何处理:主线程中断时也将子线程中断

  try {
          thread1.join();//这里是主线程在等子线程,所以必须模拟主线程中断
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中断了");
            thread1.interrupt();
            e.printStackTrace();
        }
等待子线程运行完毕
main被中断了
子线程被中断
false
子线程已经运行完毕
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.threadcoreknowledge.threadobjectclasscommonmethods.JoinInterrupt.lambda$main$0(JoinInterrupt.java:21)
	at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1252)
	at java.lang.Thread.join(Thread.java:1326)
	at com.threadcoreknowledge.threadobjectclasscommonmethods.JoinInterrupt.main(JoinInterrupt.java:31)

Process finished with exit code 0

在join期间,线程到底是什么状态?waiting

/**
 * 描述:先join再mainThread.getState()
 * 通过debugger看线程join前后状态的对比
 */
public class JoinThreadState {
    public static void main(String[] args) throws InterruptedException {
        //获取主线程
        Thread mainThread = Thread.currentThread();
        Thread thread = new Thread(() -> {
            try {
                System.out.println("子线程开始运行");
                TimeUnit.SECONDS.sleep(3);
                //打印子线程运行时的主线程状态
                System.out.println("主线程在子线程运行期间的状态是:"+mainThread.getState());
                System.out.println("子线程运行结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread.start();
        thread.join();
        System.out.println("主线程在子线程运行后的状态是:"+Thread.currentThread().getState());
    }
}

子线程开始运行
主线程在子线程运行期间的状态是:WAITING
子线程运行结束
主线程在子线程运行后的状态是:RUNNABLE

Process finished with exit code 0

join源码分析

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

join等价于

synchronized(thread){
			thread.wait();  //当thread中的run方法执行完毕后,会自动执行notifyAll();
			}

因为thread.wait(),当thread执行完毕后,自动notifyAll。

第六章 线程的各个属性

属性名称用途
编号(ID)每个线程有自己的ID,用于标识不同的线程
名称(Name)作用让用户或程序员在开发、调试、运行过程中,更容易区分每个不同的线程、定位问题
是否是守护线程(isDaemon)true代表该线程是【守护线程】,false代表线程是非守护线程。也就是【用户线程】。
优先级(priority)优先级这个属性的目的是告诉线程调度器,用户希望哪些线程相对多运行、哪些少运行

6.1 线程ID

不可修改、自增。
在我们自己创建线程之前,JVM已经帮我们创建了很多守护线程,所以我们自己创建的线程不是从2开始。

6.2 线程名字

​ 默认线程名字源码分析:Thread-1…

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
	//保证不会出现线程重名
  private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

​ 修改线程的名字(代码演示、源码分析)

public final synchronized void setName(String name) {
    checkAccess();
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

    this.name = name;
    if (threadStatus != 0) {
        setNativeName(name);
    }
}

6.3 守护线程

作用:给用户线程提供服务
3个特性:
- 线程类型默认继承父线程
- JVM自动启动
- 不会影响JVM退出,JVM退出只看有没有用户线程,不看有没有守护线程。

守护线程和普通线程的区别:
整体上没有太大区别。都是线程,都去执行代码。唯一的区别就是是否能够影响JVM的离开。

我们是否需要给线程设置为守护线程?
不需要。如果我们自己将线程设置为守护线程,那么就会有JVM提前退出的危险,造成数据不一致。JVM自带的守护线程足够我们使用。

6.4 线程优先级

10个级别,默认为5

 /**
  * The minimum priority that a thread can have.
  */
 public final static int MIN_PRIORITY = 1;

/**
  * The default priority that is assigned to a thread.
  */
 public final static int NORM_PRIORITY = 5;

 /**
  * The maximum priority that a thread can have.
  */
 public final static int MAX_PRIORITY = 10;

程序设计不应该依赖优先级
不同操作系统的优先级不同,优先级会被操作系统改变。

第七章 未捕获的异常如何处理?

  • 实际工作中,如何全局处理异常?为什么要全局处理?不处理行不行?
    利用自定义的UncaughtExceptionHandler。不处理不行。

  • run方法是否可以抛出异常?如果抛出异常,线程的状态会怎么样?
    不可以,可以try/catch处理。如果是runtimeException,就会抛出异常,并且程序终止运行。

  • 线程中如何处理某个未处理异常?
    自定义一个异常处理类,然后使用自定义的异常处理。

7.1 为什么需要UncaughtExceptionHandler?

1、主线程可以轻松发现异常,子线程却不行

主线程发生异常时,程序直接中断,但子线程发生异常时,程序还会继续执行,虽然会打印异常信息,但如果没有监控很容易被忽略。

2、子线程异常无法用传统方法进行捕获

public class CantCatchDirectly implements Runnable{
    public static void main(String[] args) throws InterruptedException {
            new Thread(new CantCatchDirectly(),"Thread-1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-4").start();
            Thread.sleep(300);
    }
    @Override
    public void run() {
            throw new RuntimeException();
        }
    }
}
Exception in thread "Thread-1" java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:35)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-2" java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:35)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-3" java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:35)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-4" java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.CantCatchDirectly.run(CantCatchDirectly.java:35)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

就算加了try/catch后,还是会抛出异常

public class CantCatchDirectly implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        try {
            new Thread(new CantCatchDirectly(),"Thread-1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-4").start();
            Thread.sleep(300);
        }catch (RuntimeException e){
            System.out.println("Caught Exception");
        }
    }
    @Override
    public void run() {
            throw new RuntimeException();
    }
  }

原因:因为子线程发生异常的点是在执行子线程时发生的,对主线程捕捉异常无效。

7.2两种解决方案

方案1:手动在每一个run方法中进行try/catch

public class CantCatchDirectly implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        try {
            new Thread(new CantCatchDirectly(),"Thread-1").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-2").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-3").start();
            Thread.sleep(300);
            new Thread(new CantCatchDirectly(),"Thread-4").start();
            Thread.sleep(300);
        }catch (RuntimeException e){
            System.out.println("Caught Exception");
        }
    }
    @Override
    public void run() {
        try {
            throw new RuntimeException();
        }catch (RuntimeException e){
            System.out.println("Caught Exception");
        }
    }
}
Caught Exception
Caught Exception
Caught Exception
Caught Exception

Process finished with exit code 0

方案2:利用UncaughtExceptionHandler
void uncaughtExceptionHandler接口
void UncaughtException(Thread t,Throwable e)

异常处理器的调用策略

 public void uncaughtException(Thread t, Throwable e) {
 //默认情况下parent是null
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
        //调用Thread.setDefaultUncaughtExceptionHandler(...)
        //方法设置的全局handler进行处理
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
           //全局handler也不存在就输出异常栈 
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

给程序统一设置

/**
 * 描述:自定义的UncaughtExceptionHandler
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
    private String name;

    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING,
        "线程异常,终止了"+t.getName(),e);
        System.out.println(name+"捕获了异常"+t.getName()+"异常"+e);
    }
}
/**
 * 描述:使用刚才自己定义的UncaughtExceptionHandler
 */
public class UserOwnUncaughtExceptionHandler implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));
        new Thread(new UserOwnUncaughtExceptionHandler(),"MyThread-1").start();
        Thread.sleep(300);
        new Thread(new UserOwnUncaughtExceptionHandler(),"MyThread-2").start();
        Thread.sleep(300);
        new Thread(new UserOwnUncaughtExceptionHandler(),"MyThread-3").start();
        Thread.sleep(300);
        new Thread(new UserOwnUncaughtExceptionHandler(),"MyThread-4").start();
    }

    @Override
    public void run() {
        throw new RuntimeException();
    }
}
五月 03, 2021 10:37:25 上午 com.threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
捕获器1捕获了异常MyThread-1异常java.lang.RuntimeException
警告: 线程异常,终止了MyThread-1
java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.UserOwnUncaughtExceptionHandler.run(UserOwnUncaughtExceptionHandler.java:26)
	at java.lang.Thread.run(Thread.java:748)

五月 03, 2021 10:37:25 上午 com.threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止了MyThread-2
java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.UserOwnUncaughtExceptionHandler.run(UserOwnUncaughtExceptionHandler.java:26)
	at java.lang.Thread.run(Thread.java:748)

捕获器1捕获了异常MyThread-2异常java.lang.RuntimeException
五月 03, 2021 10:37:25 上午 com.threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止了MyThread-3
java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.UserOwnUncaughtExceptionHandler.run(UserOwnUncaughtExceptionHandler.java:26)
	at java.lang.Thread.run(Thread.java:748)

捕获器1捕获了异常MyThread-3异常java.lang.RuntimeException
捕获器1捕获了异常MyThread-4异常java.lang.RuntimeException
五月 03, 2021 10:37:25 上午 com.threadcoreknowledge.uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常,终止了MyThread-4
java.lang.RuntimeException
	at com.threadcoreknowledge.uncaughtexception.UserOwnUncaughtExceptionHandler.run(UserOwnUncaughtExceptionHandler.java:26)
	at java.lang.Thread.run(Thread.java:748)


Process finished with exit code 0

这样就换上了自己的捕获器
可以给每个线程单独设置也可以给线程池设置。

第八章 多线程会导致的问题

  • 一共有哪几类线程安全问题?

​ 两种:一种是返回private对象,第二种是发布时逸出

  • 哪些场景需要额外注意线程安全问题?

1、访问共享的变量或资源,会有并发风险,比如对象的属性,静态变量,共享缓存,数据库等(使用Synchronized或者使用原子类保证操作的原子性)

2、所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题:read-modify-write、check-then-act(下一步的操作需要根据上一步的结果)

3、不同的数据之间存在捆绑关系的时候(比如IP和端口)

4、我们使用其他类的时候,如果对方没有声明自己是线程安全的。(比如HashMap不是线程安全的,使用ConcurrentHashMap)

  • 什么是多线程上下文切换?
    上下文切换可以认为是内核在CPU上对于进程(包括线程)进行以下的活动:(1)挂起一个进程,将这个进程在CPU中的状态(上下文)存储于内存中的某处。(2)在内存中检索下一个进程的上下文并将其在CPU的寄存器中恢复。(3)跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程。

8.1什么是线程安全

不管业务中遇到怎样的多个线程访问某对象或某方法的情况。而在编程这个业务逻辑的时候,都不需要额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。

主要是两个问题:

​ 1、数据争用:数据读写由于同时写,会造成错误数据

​ 2、竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如在写入前就读取了。

线程不安全的例子:get同时set、额外同步

全都线程安全?:运行速度、设计成本、trade off

完全不用于多线程:不过度设计

8.2 什么情况下出现线程安全问题,怎么避免?

​ 运行结果错误:a++多线程下出现消失的请求现象

package com.background;

/**
 * 描述: 第一种:运行结果出错
 * 演示计数不准确(减少),找到具体出错的位置
 */
public class MultiThreadsError implements Runnable{
    static MultiThreadsError instance=new MultiThreadsError();
    int index=0;
    @Override
    public void run() {
//        while (index<10000){
//            index++;
//        }
        for (int i = 0; i < 10000; i++) {
            index++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(instance);
        Thread thread2=new Thread(instance);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(instance.index);
    }
}
15659

Process finished with exit code 0

正确结果应该是20000.出现错误的原因:
线程之间通信出现问题:线程1在进行+1操作的时候对于线程2来说可能是不知晓的,所以线程2在拿到i的时候还把它当作+1操作之前的值。

优化程序:比较难

**
 * 描述: 第一种:运行结果出错
 * 演示计数不准确(减少),找到具体出错的位置
 */
public class MultiThreadsError implements Runnable{
    static MultiThreadsError instance=new MultiThreadsError();
    int index=0;
    //将三个步骤(存入,+1,写入)合为一个步骤,保证原子性
    static AtomicInteger realIndex=new AtomicInteger();
    static AtomicInteger wrongCount=new AtomicInteger();
    //一共需要等待两个线程
    static volatile CyclicBarrier cyclicBarrier1=new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2=new CyclicBarrier(2);

    final boolean[] marked=new boolean[100000];
    @Override
    public void run() {
        marked[0]=true;
        for (int i = 0; i < 10000; i++) {
            try {
                cyclicBarrier2.reset();
                //两个线程都执行了这个,才一起继续下行
                cyclicBarrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            index++;
            try {
                cyclicBarrier1.reset();
                //两个线程都执行完到此操作时再继续下行
                cyclicBarrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            //检查是否发生问题
            realIndex.incrementAndGet(); //原子类的加是不会出现线程问题的
            synchronized (instance){
                if(marked[index]&&marked[index-1]){ //可能会出现误报。因为如果前一个是false,后一个是true,那么也是正确的。因为synchronized的可见性。
                    System.out.println("发生了错误"+index);
                    wrongCount.incrementAndGet();
                }
                marked[index]=true;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(instance);
        Thread thread2=new Thread(instance);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("表面上的结果是"+instance.index);
        System.out.println("真正运行的次数是"+realIndex.get());
        System.out.println("错误次数"+wrongCount.get());
    }
}
发生了错误7037
表面上的结果是19999
真正运行的次数是20000
错误次数1

Process finished with exit code 0

8.3 活跃性问题:死锁、活锁、饥饿

/**
 * 描述:  第二章线程安全问题,演示死锁
 */
public class MultiThreadError implements Runnable{
    int flag=1;
    static Object o1=new Object();
    static Object o2=new Object();

    public static void main(String[] args) {
        MultiThreadError r1 = new MultiThreadError();
        MultiThreadError r2 = new MultiThreadError();
        r1.flag=1;
        r2.flag=0;
        new Thread(r1).start();
        new Thread(r2).start();
    }
    @Override
    public void run() {
        System.out.println("flag="+flag);
        if(flag==1){
            synchronized (o1){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){
                    System.out.println("1");

                }
            }
        }

        if(flag==0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (o1){
                    System.out.println("0");
                }
            }
        }
    }
}

8.4 对象发布和初始化的安全问题

  • 什么是发布?
    把对象或者类提供给别人来用的事件就是发布。
  • 什么是逸出?
    1、方法返回一个private对象(private的本意是不让外部访问)
    2、还未完成初始化(构造函数没完全执行完毕)就把对象提供给外界
    3、在构造函数中未初始化完毕就this赋值
    4、隐式逸出–注册监听事件
    5、构造函数中运行线程
//发布逸出:返回一个private对象
public class MultiThreadsError3 {
    private Map<String,String> states;

    public MultiThreadsError3() {
       states=new HashMap<>();
       states.put("1","周一");
       states.put("2","周二");
       states.put("3","周三");
       states.put("4","周四");
    }

    public Map<String, String> getStates() {
        return states;
    }

    public static void main(String[] args) {
        MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
        Map<String, String> states = multiThreadsError3.getStates();
        System.out.println(states.get("1")); //周一
        states.remove("1");
        System.out.println(states.get("1"));//null

    }
}
/**
 * 描述: 初始化未完毕,就this赋值
 */
public class MultiThreadsError4 {
    static Point point;

    public static void main(String[] args) throws InterruptedException {
        new PointMaker().start();
        //如果这里时间小于200ms,那么就会输出Point{x=1, y=0},如果大于200ms,那么就会输出Point{x=1, y=1}
        Thread.sleep(300);
        if(point!=null){
            System.out.println(point);
        }
    }
}

class Point{
    private final int x,y;

    public Point(int x,int y) throws InterruptedException {
        this.x=x;
        //初始化未完毕,就this赋值
        MultiThreadsError4.point=this;
        Thread.sleep(200);
        this.y=y;
    }

    @Override
    public String toString() {
        return "Point{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

class PointMaker extends Thread{
    @Override
    public void run() {
        try {
            new Point(1,1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

构造函数中新开线程的安全隐患:

package com.background;

/**
 * @author xyf
 * @version 1.0
 * @date 2020/12/2 4:24 PM
 */

import java.util.HashMap;
import java.util.Map;

/**
 * 描述: 构造函数中新开线程的安全隐患
 * 主线程速度过快,访问不到。
 */
public class MultiThreadsError6 {
    private Map<String,String> states;

    public MultiThreadsError6() {
        new Thread(()->{
            states=new HashMap<>();
            states.put("1","周一");
            states.put("2","周二");
            states.put("3","周三");
            states.put("4","周四");
        }).start();
    }

    public Map<String, String> getStates() {
        return states;
    }

    public static void main(String[] args) {
        MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();
        System.out.println(multiThreadsError6.states);//null

    }
}

如何解决逸出

1、对应第一种情况:返回一个private对象

狸猫换太子

package com.background;

/**
 * @author xyf
 * @version 1.0
 * @date 2020/12/2 4:24 PM
 */

import java.util.HashMap;
import java.util.Map;

/**
 * 描述: 发布逸出:返回了一个private对象
 * 星期提供服务
 */
public class MultiThreadsError3 {
    private Map<String,String> states;

    public MultiThreadsError3() {
       states=new HashMap<>();
       states.put("1","周一");
       states.put("2","周二");
       states.put("3","周三");
       states.put("4","周四");
    }

    public Map<String, String> getStates() {
        return states;
    }

    public Map<String,String> getStatesImproved(){
        return new HashMap<>(states);
    }

    public static void main(String[] args) {
        MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
//        Map<String, String> states = multiThreadsError3.getStates();
//        System.out.println(states.get("1")); //周一
//        states.remove("1");
//        System.out.println(states.get("1"));//null
        Map<String, String> statesImproved = multiThreadsError3.getStatesImproved();
        System.out.println(statesImproved.get("1"));
        statesImproved.remove("1");
        System.out.println(multiThreadsError3.getStatesImproved().get("1"));
    }
}

性能问题

​ 性能问题有哪些体现、什么是性能问题

​ 服务响应慢,吞吐量低、资源消耗过高。

​ 虽然不是结果错误,但依然危害巨大

​ 引入多线程不能本末倒置

​ 影响性能问题的原因:

​ 1、调度:上下文切换(线程数超过CPU核数)

​ 什么是上下文?保存现场。主要和一些寄存器相关。比如说保存的执行到的指令位置。之后切换回来再次执行所要保存的内容。

​ 缓存开销:缓存失效

​ 何时会导致密集的上下文切换:抢锁、IO

​ 2、协作:内存同步

​ 为什么多线程会带来性能问题?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值