多线程|深刻浅出线程核心知识

1.多线程简介

1.1 什么是线程

线程是操做系统可以进行运算调度的最小单位,它被包含在进程中,是进程中的实际运做单位。程序员能够经过它进行多处理器编程。你能够经过使用多线程对运算密集的任务提速。好比,若是一个线程完成一个任务须要100毫秒,那么用十个线程完成此任务只需10毫秒。Java在语言层面对多线程提供了卓越的支持,他是一个很好的卖点。java

1.2 进程与线程的区别

  • 进程是资源分配的基本单位,线程是调度和执行的基本单位
  • 它们的关系是包含的,一个进程中包含多个线程
  • 当操做系统分配资源时会为进程分配资源不会为线程分配资源,线程使用的资源是所在进程的资源
  • 线程间切换的资源开销比较小,而进程之间切换资源开销比较大。

1.3 为何要使用多线程

当一个网站遇到并发量很大的问题时,普通的系统很快就会达到性能瓶颈,而使用多线程能够轻松的解决性能问题。程序员

2.线程的实现方式有几种?

There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows数据库

The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following编程

上面的两段引用出自Oracle官方文档,将这两段英文翻译过来意思就是说实现线程的方式有两个种,第一种是继承Thread类的方式,另外一种就是实现Runnable接口。缓存

2.1 实现多线程

继承Thread类实现多线程

/**
 * 用Thread方式实现线程
 */
public class ThreadStyle extends Thread {
    public static void main(String[] args) {
        ThreadStyle thread = new ThreadStyle();
        thread.start();
    }

    @Override
    public void run() {
        System.out.println("用Thread类实现线程");
    }
}
复制代码

实现Runnable接口实现多线程

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

    @Override
    public void run() {
        System.out.println("用Runnable方式实现线程");
    }
}
复制代码

2.2 多线程实现方式对比

  • 实现Runnable接口方式能够下降代码耦合度
  • 继承Thread类后就没法再继承别的类,下降了类的扩展性
  • 若是使用Thread类须要每实现一个线程类就进行一次建立,形成了较大的资源开销。

总结:综上所述,实现线程采用Runnable接口的方式比较好。安全

2.3 同时用两种方式会是怎样

/**
 * 同时使用Runnable和Thread两种方式实现多线程
 */
public class BothRunnableThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread");
            }
        }.start();
    }
}
复制代码

运行结果bash

分析:

实现Runnable接口实现多线程的方式

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

实现Runnable接口方式会要求重写run()方法,因此会执行其中的三行代码,其中target是一个private Runnable target;,由于后面又覆盖了一次Thread类的run方法,因此if判断也就消失了就会直接执行本身在run方法中的打印语句。多线程

总结:建立线程的方式只有构造Thread类一种方法,可是实现Thread类中的run方法有两种方式。并发

3.线程的启动

3.1 启动线程的正确方式

/**
 * 对比start和run这两种启动线程的方式
 */
public class StartAndRunMethod {

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

        new Thread(runnable).start();
    }
}
复制代码

运行结果dom

总结:启动线程须要使用start()方法

3.2 start()方法原理解读

  • start()方法含义

调用start()方法意味着向JVM发起通知,若是有空能够来我这里执行一下么,本质也就是经过调用start()方法请求JVM运行此线程,可是调用该方法以后不必定就会当即运行,而是须要等到JVM有空执行时才会执行。

  • start()方法源码解析
public synchronized void start() {
    //进行线程状态的检查,默认值是0
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
        
    //加入线程组
    group.add(this);

    boolean started = false;
    try {
        //执行线程的方法,是一个native方法
        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 */
        }
    }
}
复制代码

总结:执行start()要经历的步骤

  • 检查线程状态
  • 加入线程组
  • 执行start0()方法

3.3 run()方法原理解读

@Override
public void run() {
    //判断传入的Runnable是否为空
    if (target != null) {
        //不为空则启动
        target.run();
    }
}
复制代码

从这里能够看出上面直接调用run()方法为何会在主线程中执行,这是由于target是空的因此不会启动,这样就和调用一个普通的方法没有区别了。

4.线程的中止

4.1 线程中止的原则

在Java中中止线程的最好方式是使用interrupt,可是这样仅仅会对须要中止的线程进行通知而不是直接停掉,线程是否的中止的权利属于须要被中止的线程(什么时候中止以及是否中止),这就须要请求中止方和被中止方都遵循一种编码规范。

4.2 线程一般会在什么状况下中止

  • run()方法中的代码运行完毕(最多见)
  • 有异常出现可是没有进行捕获

4.3 正确的中止方法——使用interrupt

  • 在普通状况下中止
/**
 * run方法内没有sleep或wait方法时,中止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable {

    @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("任务运行结束了");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();

        Thread.sleep(2000);
        //发起通知
        thread.interrupt();
    }
}
复制代码
  • 若是线程阻塞如何中止
/**
 * 带有sleep的中断线程的中止方法
 */
public class RightWayStopThreadWhthSleep {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () ->{
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()){
                    if (num % 100 == 0){
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}
复制代码
  • 线程每次迭代后都阻塞如何中止
/**
 * 若是在每次循环中都会sleep或wait,须要如何中止线程
 */
public class RightWayStopThreadWithSleepEveryLoop implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithSleepEveryLoop());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

    @Override
    public void run() {
        try {
            int num = 0;
            while (num <= 30){
                if (num % 10 == 0){
                    System.out.println(num + "是10的倍数");
                }
                num++;
                Thread.sleep(50);
            }
            System.out.println("任务完成了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
复制代码

注意:若是每次迭代都有阻塞状态,这样就不须要判断是否收到中断请求,由于在sleep过程当中会对中断进行响应

  • whiletry/catch的问题
/**
 * 若是while里面放try/catch,会致使中断失效
 */
public class CantInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () ->{
            int num = 0;
            while (num <= 10000){
                if (num % 100 == 0 && !Thread.currentThread().isInterrupted()){
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}
复制代码

注意:在while循环中加入try/catch没法中止线程,由于在try/catch中异常被捕获后仍是不知足跳出循环的条件,interrupt标记位被清除也就没法检查到被中断的迹象,因此会继续执行线程

4.4 实际开发中中止的两种最佳实践

原则:

  • 优先选择:传递中断
  • 不想或没法传递:恢复中断
  • 不该屏蔽中断

抛出式

/**
 * 最佳实践:catch住InterruptedException后优先选择在方法签名中抛出异常,
 * 那么在run()方法中就会强制try/catch
 */
public class RightWayStopThreadInProd implements Runnable {
    @Override
    public void run() {
        try {
            while (true){
                System.out.println("go");
                throwInMethod();
            }
        } catch (InterruptedException e) {
            //保存日志
            //中止程序
            System.out.println("保存日志、中止程序");
            e.printStackTrace();
        }
    }

    private void throwInMethod() throws InterruptedException {
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
复制代码

注意:在方法中遇到异常应该首先选择抛出,异常由run方法进行处理,这样能够增长代码的健壮性

恢复中断式

/**
 * 最佳实践2:在catch语句中调用Thread.currentThread.interrupt()
 * 来恢复中断状态,以便于在后续的执行中依然可以检查到刚才发生了中断
 */
public class RightWayStopThreadInProd2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadInProd2());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}
复制代码

注意:若是在调用的方法中不抛出异常的话也能够在catch块再次调用Thread.currentThread().interrupt();,这样能够从新设置中断表示已经有中断发生,从而让run方法感知

4.5 响应中断的方法列表总结

  • Object.wait()/wait(long)/wait(long,int)
  • Thread.sleep(long)/sleep(long,int)
  • Thread.join()/join(long)/join(long,int)
  • java.util.concurrent.BlockingQueue.take()/put(E)
  • java.util.concurrent.locks.Lock.lockInterruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.CyclicBarrier.await()
  • java.util.concurrent.Exchanger.exchange(V)
  • java.nio.channels.InterruptibleChannel相关方法
  • java.nio.channels.Selector的相关方法

4.6 错误的中止线程方法

  • 使用stop方法中止
/**
 * 错误的中止方法:用stop中止线程,会致使线程运行一半忽然中止,这样没有办法完成一个基本单位(一个连队)的操做,
 * 会形成脏数据(有的连队多领取或少领取装备)
 */
public class StopThread implements Runnable {
    @Override
    public void run() {
        /**
         * 模拟指挥军队:一个5个连队,每一个连队10人,以连队为单位发放弹药,叫到号
         * 的士兵去领取
         */
        for (int i = 0; i < 5; i++) {
            System.out.println("连队" + i + "开始领取装备");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队" + i + "领取完毕");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        //1s以后战争爆发须要奔赴战场
        Thread.sleep(1000);
        //中止领取
        thread.stop();
    }
}
复制代码
  • volatile设置boolean标记位
/**
 * 演示用volatile的局限part2  陷入阻塞时volatile没法中止
 * 此例中生产者的生产速度很快,可是消费者的消费速度很慢,因此阻塞队列满了之后,
 * 生产者会阻塞,生产者会等待消费者进一步消费
 */
public class WrongWayVolatileCantStop {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()){
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不须要更多数据了");

        //一旦消费者不须要更多数据了,咱们应当让消费者也停下来,可是实际状况。。。
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

class Producer implements Runnable {

    public volatile boolean canceled = false;
    BlockingQueue<Integer> storage;

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

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    //是100的倍数时,将num放入阻塞队列
                    storage.put(num);
                    System.out.println(num + "是100的倍数,被放入阻塞队列");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者中止运行");
        }
    }
}

class Consumer {

    BlockingQueue<Integer> storage;

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

    public boolean needMoreNums() {
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }

}
复制代码

注意:若是线程长时间阻塞,这种方法就会失效

对上面方式的修复

/**
 * 用中断修复刚才一直等待的问题
 */
public class WrongWayVolatileFixed {

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatileFixed body = new WrongWayVolatileFixed();

        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }

        System.out.println("消费者不须要更多数据了");

        producerThread.interrupt();

    }
    class Producer implements Runnable {

        BlockingQueue storage;

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

        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                    //若是num是100的倍数,就将他添加到阻塞队列
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放入阻塞队列");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生产者线程阻塞");
            }
        }
    }

    class Consumer {

        BlockingQueue storage;

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

        public boolean needMoreNums() {

            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}
复制代码

4.7 没法响应中断时如何中止线程

答:须要根据状况的不一样而采起不一样的方法

若是线程阻塞是由于调用了sleep()、wait()或join()致使的,能够经过抛出InterruptException异常来唤醒,可是不能响应InterruptException异常则没法经过这种方法进行唤醒。 可是咱们能够利用其余能够响应中断的方法,好比: ReentrantLock.lockInterruptibly() ,关闭套接字使线程当即返回等方法来达到目的。 因此如何处理不可中断的阻塞要视状况而定

5.线程的生命周期

5.1 线程的6种状态及转化路径

  • New(新生):已建立可是尚未启动的新线程,例如:线程建立出来尚未执行start()方法
  • Runnable(可运行):一旦从New调用了start()方法就会进入Runnable状态。
  • Blocked(阻塞):当线程进入到被synchronized修饰的代码后,而且该锁已经被其它线程拿走了就会进入阻塞状态。
  • Waiting(等待):没有设置timeout参数的wait()方法,须要等待唤醒不然不会醒来
  • Timed Waiting(计时等待):为等待设置了时间,若是在时间结束前进行了唤醒,线程能够苏醒,若是不进行唤醒,等到时间结束也会自动苏醒。
  • Terminated(死亡):线程执行完成或者run()方法被意外终止。

注意:通常而言把Blocked(被阻塞)、Waiting(等待)、Timed_Waiting(计时等待)都称为阻塞,而不只仅是Blocked

6.Thread类与Object类线程相关的方法

6.1 wait()、notify()、notifyAll()方法的使用

做用:wait()方法会让线程进入等待状态,若是想让线程继续执行必须知足一下四种方式中的一种

  • 调用notify()方法,本线程正好被唤醒
  • 调用notifyAll()方法,全部线程都会被唤醒
  • 设置的wait(long timout)达到了参数的时间,若是传入0会进入永久等待
  • 调用interrupt()方法进行唤醒
/**
 * 展现wait和notify的基本用法
 * 1.研究代码执行顺序
 * 2.证实wait是释放锁的
 */
public class Wait {

    public static Object object = new Object();

    static class Thread1 extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+"开始执行");

                try {
                    object.wait();  //等待期间若是遇到中断会抛出InterruptedException
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");
            }
        }
    }

    static class Thread2 extends Thread{
        @Override
        public void run() {
            synchronized (object){
                object.notify();
                System.out.println("线程"+Thread.currentThread().getName()+"调用了notify()");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread1().start();
        Thread.sleep(200);
        new Thread2().start();
    }
}
复制代码

总结:wait()方法会释放对象锁,只有释放了对象锁其余线程才能够进入synchronized代码块

/**
 * 3个线程,线程1和线程2首先被阻塞,线程3去唤醒线程1和线程2
 * start先执行不表明线程先启动
 */
public class WaitNotifyAll implements Runnable {

    private static final Object resourceA = new Object();

    @Override
    public void run() {
        synchronized (resourceA){
            System.out.println(Thread.currentThread().getName()+"获得对象锁");
            try {
                System.out.println(Thread.currentThread().getName()+"等待下一次开始");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+"立刻运行结束了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
        Thread threadA = new Thread(waitNotifyAll);
        Thread threadB = new Thread(waitNotifyAll);

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    resourceA.notifyAll();
                    System.out.println("线程C已经成功notify了");
                }
            }
        });
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        threadC.start();
    }
}
复制代码

总结:notify只会唤醒等待线程中的一个而notifyAll则会唤醒全部等待线程,在线程启动时必定要等到线程进入等待状态以后再进行唤醒

/**
 * 证实wait只释放当前那把锁
 */
public class WaitNotifyReleaseOwnMonitor {

    private static Object resourceA = new Object();
    private static Object resourceB = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println("ThreadA got resourceA lock.");
                    synchronized (resourceB) {
                        System.out.println("ThreadA got resourceB lock.");
                        try {
                            System.out.println("ThreadA releases resourceA lock.");
                            resourceA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resourceA){
                    System.out.println("ThreadB got resourceA lock.");
                    System.out.println("ThreadB tries to ResourceB lock.");
                    synchronized (resourceB){
                        System.out.println("ThreadB got resourceB lock.");
                    }
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}
复制代码

总结:wait()只释放当前monitor

6.2 wait方法的原理

最初线程都会在入口集中,当线程进入到 synchronized时就会获取到对象锁,若是执行了 wait方法后就会进入到等待集,在等待集中若是对某个线程执行了 notify操做它就会再次回到入口集,若是使用的是 notifyAll那么等待集中的所有线程都会进入到入口集。

6.3 使用wait方法实现生产者消费者模式

/**
 * 用wait和notify来实现
 */
public class ProducerConsumerModel {

    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();
        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);

        Thread thread1 = new Thread(producer);
        Thread thread2 = new Thread(consumer);
        thread1.start();
        thread2.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.put();
        }
    }
}

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.take();
        }
    }
}

class EventStorage {
    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }

    public synchronized void put() {
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("生产出了一个商品,仓库中有:" + storage.size() + "个商品");
        notify();
    }

    public synchronized void take() {
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("取走了:" + storage.poll() + ",还剩下" + storage.size() + "个商品");
        notify();
    }
}
复制代码

总结:生产者生产到10的时候就会进入wait状态,不然就会进行生产,消费者将队列中的商品消费到0时就会进入wait状态,就这就会消费,当生产者生产出了商品就会notify消费者,将其唤醒。反之消费者就会notify唤醒生产者

6.4 交替打印100之内的奇偶数

  • synchronized方式
/**
 * 两个线程交替打印0到100奇偶数
 */
public class WaitNotifyPrintOddEvenSyn {

    private static int count;
    private static final Object lock = new Object();

    //新建2个线程
    //1个只处理偶数,第2个处理奇数(用位运算)
    //用synchronized进行通讯
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100){
                    synchronized (lock){
                        if ((count & 1) == 0){
                            System.out.println(Thread.currentThread().getName() + ":" + count);
                            count++;
                        }
                    }
                }
            }
        },"偶数").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count < 100){
                    synchronized (lock){
                        if ((count & 1) != 0){
                            System.out.println(Thread.currentThread().getName() + ":" + count);
                            count++;
                        }
                    }
                }
            }
        },"奇数").start();
    }
}
复制代码

总结:这样实现有可能形成同一个线程老是拿到对象锁,可是if判断只会进一次,这样就影响了效率,可使用wait/notify方式解决

  • wait/notify方法
/**
 * 两个线程交替打印0到100的奇偶数,使用wait/notify
 */
public class WaitNotifyPrintOddEvenWait {

    public static void main(String[] args) throws InterruptedException {
        new Thread(new TurningRunner(), "偶数").start();
        Thread.sleep(100);
        new Thread(new TurningRunner(), "奇数").start();
    }

    //1.一旦拿到锁就打印
    //2.打印完,唤醒其余线程,而后再休眠
    static class TurningRunner implements Runnable{

        private static int count;
        private static Object lock = new Object();

        @Override
        public void run() {
            while (count < 100){
                synchronized (lock){
                    //拿到锁就打印
                    System.out.println(Thread.currentThread().getName() + ":" + count++);
                    lock.notify();
                    if (count < 100){
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}
复制代码

总结:拿到锁就打印,打印完就唤醒若是知足条件就等待,这样能够提升程序的效率

6.5 sleep方法详解

做用:让线程进入阻塞状态,睡眠时不会占用cpu资源。

注意:sleep方法不会释放synchronizedlock

  • 使用synchronized演示
/**
 * 展现线程sleep的时候不释放synchronized的monitor,
 * 等sleep的时间到了之后,正常结束后才会释放锁
 */
public class SleepDontReleaseMonitor implements Runnable {

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

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

    private synchronized void syn() {
        System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor.");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块.");
    }
}
复制代码
  • 使用ReentrantLock
/**
 * 演示sleep不释放lock(lock自己也须要手动释放)
 */
public class SleepDontReleaseLock implements Runnable {

    private static final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        System.out.println("线程" +Thread.currentThread().getName()+ "获取到了锁");
        try {
            Thread.sleep(5000);
            System.out.println("线程" +Thread.currentThread().getName()+ "睡眠结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
        new Thread(sleepDontReleaseLock).start();
        new Thread(sleepDontReleaseLock).start();
    }
}
复制代码

6.6 对比wait和sleep方法的异同

相同:

  • wait和sleep都会让线程进入阻塞状态
  • wait和sleep均可以响应中断

不一样:

  • 使用位置不一样,wait只能在synchronized代码块中使用,sleep不须要
  • 所属的类不一样,wait属于Object类,sleep属于Thread类
  • wait能够释放对象锁,sleep不会释放对象锁
  • 阻塞时间长短有差别,wait若是不传入参数就会永久等待直到对其进行唤醒,sleep只须要等到参数时间到了就会苏醒

6.7 join方法学习

做用:由于新的线程加入了咱们,因此咱们要等他执行完再出发 用法:main等待thread1执行完毕,主线程等待子线程

  • 普通用法
/**
 * 演示join用法,注意语句输出顺序是否会变化
 */
public class Join {

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "已经执行完毕");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                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("全部子线程执行完毕");
    }
}
复制代码
  • 在join期间遇到中断
/**
 * 演示join期间被中断的效果
 */
public class JoinInterrupt {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mainThread.interrupt();
                    Thread.sleep(5000);
                    System.out.println("Thread1 finished.");
                } catch (InterruptedException e) {
                    System.out.println("子线程中断");
                }
            }
        });

        thread1.start();
        System.out.println("等待子线程运行完毕");
        try {
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "主线程被中断了");
            thread1.interrupt();
        }
        System.out.println("子线程已经运行完毕");
    }
}
复制代码
  • join原理分析

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方法默认传入的millis是0,此时会进入永久等待,可是因为JVM的缘由在Thread类中每一个方法结束都会有一个notify操做,因此join才不须要手动进行唤醒。

由于join方法的底层是wait因此使用以下代码能够与join等价

synchronized (thread1){
    thread1.wait();
}
复制代码

Thread类的对象加锁,在结束时会自动进行释放,因此能够达到join的效果。

6.8 yield方法详解

做用:释放个人CPU时间片,但不会释放锁也不会进入阻塞。

定位:JVM不保证遵循yield

  • yield和sleep的区别

sleep期间线程进入阻塞状态因此不会再被调度。而yield只是暂时做出让步但还能够处于竞争状态。

7.线程各类属性

7.1 线程id

线程id是不可修改的,主线程的id从1开始

private static synchronized long nextThreadID() {
    return ++threadSeqNumber;
}
复制代码

线程的id是先++后返回,因此主线程的id为1

查看子线程的id

/**
 * Id从1开始,JVM运行起来以后,咱们本身建立的线程Id早已不是0
 */
public class Id {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println("主线程的ID:" + Thread.currentThread().getId());
        System.out.println("子线程的ID:" + thread.getId());
    }
}
复制代码

会发现子线程的id为11,这是为何呢? 经过debug的方式能够发现,main线程启动后会建立不少线程,因此执行到子线程时id已经不会是2了

7.2 线程名字

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}
复制代码

经过Thread类的构造方法能够发现,若是不传入线程的名字就会默认在Thread-后面添加一个从0开始的数字,由于有synchronized因此不会出现线程重名的状况。

7.3 守护线程

做用:给用户线程提供服务(一共有用户线程和守护线程两大类)

若是程序中有用户线程,JVM不会中止工做,可是若是程序中只有守护线程,那么守护线程也就没有了守护的对象,因此只有守护线程的状况下JVM会中止工做。例如:垃圾处理器就是守护线程。

守护线程的3个特性

  • 线程类型默认继承自父线程
  • 被谁启动,当程序启动时main线程是用户线程,其余的都是守护线程
  • 不影响JVM退出,若是只有守护线程JVM会结束

守护线程和用户线程的区别

由于都是线程因此总体没什么区别

  • 主要就是对JVM的影响,在有用户线程的状况下JVM不会退出,若是只有守护线程存在JVM会退出
  • 用户线程用来执行任务,守护线程服务于用户线程

应该把用户线程设置为守护线程吗?

不该该。若是把用户线程设置为守护线程了,那么在执行任务时JVM发现此时没有用户线程,这样就会中止虚拟机,从而致使数据不一致的状况。

7.4 线程优先级

能够经过设置优先级来增长某个线程的运行次数,优先级最高能够设置为10,默认是5,最低是1。

注意:程序设计不该该依赖于优先级

  • 由于不一样操做系统不同
  • 优先级会被操做系统改变,例如:Windows中有一个优先级推动器,当它发现某个线程执行很积极,那么他就会越过优先级多为这个线程分配执行时间。另外一种状况就是若是将线程的优先级设置的太低,那么操做系统可能就不为这个线程分配优先级,这样线程就有可能被“饿死”。

8.线程异常处理

8.1 为何要处理线程异常

在程序的运行中有不少异常是不可预料的,若是在返回以前不被拦截而是直接返回给用户的话这样可能会引起安全性的问题。

8.2 怎么处理线程异常

使用UncaughtExceptionHandler

8.3 使用 UncaughtExceptionHandler的3个理由

  • 主线程能够轻松发现异常,子线程却不行
/**
 * 单线程,抛出,处理,有异常堆栈
 * 多线程状况下子线程发生异常,会有什么不一样?
 */
public class ExceptionInChildThread implements Runnable {

    public static void main(String[] args) {
        new Thread(new ExceptionInChildThread()).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
    }

    @Override
    public void run() {
        throw new RuntimeException();
    }
}
复制代码

当子线程抛出异常时,不会对主线程的运行产生影响。在真实的生产环境中由于有大量日志的产生,可能会忽略子线程中出现的问题。

  • 子线程中的异常没法用传统的方法捕获
    • 不加try-catch时抛出4个异常
/**
 * 1.不加try catch时抛出4个异常,都带线程名
 * 2.若是加了try catch,但愿能够捕获到第一个线程的异常并处理,线程234不该该再运行,
 *      但愿看到打印出的Caught Exception
 * 3.执行时发现,根本没有Caught Exception,线程234依然运行,而且还抛出异常
 *
 * 说明线程的异常不能用传统方法捕获
 */
public class CantCatchDirectly implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        new Thread(new CantCatchDirectly(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new CantCatchDirectly(), "MyThread-4").start();
    }
    @Override
    public void run() {
        throw new RuntimeException();
    }
}
复制代码

  • 添加try-catch依然抛出异常
/**
 * 1.不加try catch时抛出4个异常,都带线程名
 * 2.若是加了try catch,但愿能够捕获到第一个线程的异常并处理,线程234不该该再运行,
 *      但愿看到打印出的Caught Exception
 * 3.执行时发现,根本没有Caught Exception,线程234依然运行,而且还抛出异常
 *
 * 说明线程的异常不能用传统方法捕获
 */
public class CantCatchDirectly implements Runnable {

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

注意:由于try-catch语句块添加在了主线程中,可是抛出异常的位置位于子线程,因此没法捕获

  • 若是不能捕获异常子线程会中止

8.4 解决子线程抛出异常问题

  • 方案1(不推荐):手动在每一个run()方法中进行try-catch
@Override
public void run() {
    try {
        throw new RuntimeException();
    } catch (RuntimeException e) {
        System.out.println("Caught Exception");
    }
}
复制代码

  • 方案2(推荐):利用UncaughtExceptionHandler

8.5 本身实现并处理异常

实现方案

  • 给程序统一设置
  • 给每一个线程单独设置
  • 给线程池设置

建立MyUncaughtExceptionHandler

/**
 * 实现本身的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());
        System.out.println(name + " 捕获了" + t.getName() + "的"+ e +"异常");
    }
}
复制代码

使用MyUncaughtExceptionHandler

/**
 * 使用本身建立的UncaughtExceptionHandler
 */
public class UseOwnUncaughtExceptionHandler implements Runnable {

    public static void main(String[] args) throws InterruptedException {

        Thread.setDefaultUncaughtExceptionHandler(new
                MyUncaughtExceptionHandler("捕获器1"));
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-1").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-2").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-3").start();
        Thread.sleep(300);
        new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-4").start();

    }

    @Override
    public void run() {
        throw new RuntimeException();
    }
}
复制代码

9.多线程的缺点

线程是一把双刃剑,他在提升程序执行效率的同时也会存在一些弊端,好比线程安全问题,这会致使数据发生错乱,还有就是性能问题,好比服务响应慢、吞吐率低、资源开销大。使用线程的目的就是为了让程序更好的运行,若是这些问题不解决就本末倒置了,在这里学习一下如何解决吧!

9.1 什么是线程安全

当多个线程访问一个对象时,若是不用考虑这些线程在运行时环境下的调度和交替执行,也不须要进行额外的同步,或者在调用方在进行任何其余的协调操做,调用这个对象的行为均可以得到正确的结果,那么这个对象就是线程安全的。

9.2 线程安全问题如何避免

线程安全问题主要分为以下两种状况

  • 数据征用:两个数据同时去写,这就有可能致使有一方的数据要么被丢弃要么写入错误
  • 竞争条件:竞争条件主要体如今顺序上,好比一个线程对文件进行读取,读取发生在写入以前,这就会引发顺序上的错误。

9.3 线程安全——运行结果问题(i++加的次数会莫名消失)

/**
 * 第一种状况:运行结果出错
 * 演示计数不许确(减小),找出具体出错的位置
 */
public class MultiThreadError implements Runnable {

    private int index = 0;
    static MultiThreadError instance = new MultiThreadError();

    @Override
    public void run() {

        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);
    }
}
复制代码

其实每次执行i++操做须要三步,若是线程1执行了 i+1的操做后线程进行了切换,线程2不知道线程1已经执行了 +1操做,依然执行+1,加完以后又切换到线程1,此时线程1的加操做已经完成了,i变成了2,线程2再次执行后,i的值也变成了2,这就是致使i出现少加的状况的缘由。

9.4 i++错误的解决

/**
 * 第一种状况:运行结果出错
 * 演示计数不许确(减小),找出具体出错的位置
 */
public class MultiThreadError implements Runnable {

    private int index = 0;
    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongCount = new AtomicInteger();
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    static MultiThreadError instance = new MultiThreadError();
    final boolean[] marked = new boolean[10000000];

    @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]){
                    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());
    }
}
复制代码

9.5 线程安全——活跃性问题(死锁、活锁、饥饿)

死锁的实现

/**
 * 第二种线程安全问题,演示死锁
 */
public class MultiThreadError2 implements Runnable {

    int flag;
    static Object o1 = new Object();
    static Object o2 = new Object();

    public static void main(String[] args) {
        MultiThreadError2 r1 = new MultiThreadError2();
        MultiThreadError2 r2 = new MultiThreadError2();
        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){  //想拿到o2却始终拿不到
                    System.out.println("1");
                }
            }
        }
        if (flag == 0){
            synchronized (o2){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){  //想拿到o1却始终拿不到
                    System.out.println("0");
                }
            }
        }
    }
}
复制代码

9.6 线程安全——对象发布和初始化时的安全问题

什么是发布

对象可让超出范围以内的类进行使用,好比在方法结束后return 一个对象,从而让外部可使用这个对象,这就是对象的发布。

什么是逸出

逸出是指将对象发布到了不应发布的地方,好比:

  • 方法返回一个private的对象(private正常只能在本类中使用)
  • 还未完成初始化(构造函数没彻底执行完毕)就把对象提供给外界,好比: 构造函数中未初始化完毕就this赋值,隐式逸出——注册监听事件,构造函数中运行线程

return私有对象致使的逸出

/**
 * 发布逸出
 */
public class MultiThreadError3 {
    private Map<String, String> states;
    public MultiThreadError3(){
        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) {
        MultiThreadError3 multiThreadError3 = new MultiThreadError3();
        Map<String, String> states = multiThreadError3.getStates();
        System.out.println(states.get("1"));
        states.remove("1");
        System.out.println(states.get("1"));
    }
}
复制代码

private的本意就是不但愿外部访问到,能够经过 return以后能够从外部对数据进行修改,这就很危险了!

构造方法未初始化完成就赋值致使逸出

/**
 * 初始化未完毕就this赋值
 */
public class MultiThreadError4 {

    static Point point;

    public static void main(String[] args) throws InterruptedException {
        new PointMaker().start();
        Thread.sleep(10);
        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;
        MultiThreadError4.point = this;
        Thread.sleep(100);
        this.y = y;
    }

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

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

若是将主线程中 sleep的时间增长到100ms以上便不会发生逸出

注册监听器致使逸出

/**
 * 观察者模式
 */
public class MultiThreadError5 {

    int count;
    public MultiThreadError5(MySource source){
        source.registerListener(new EventListener() {
            @Override
            public void onEvent(Event e) {
                System.out.println("\n我获得的数字是:" + count);
            }
        });
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        count = 100;
    }

    static class MySource {
        private EventListener listener;

        void registerListener(EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(Event e){
            if (listener != null){
                listener.onEvent(e);
            }else{
                System.out.println("还未初始化完毕");
            }
        }
    }


    interface EventListener {
        void onEvent(Event e);
    }

    interface Event {

    }

    public static void main(String[] args) {
        MySource mySource = new MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mySource.eventCome(new Event() {
                });
            }
        }).start();

        MultiThreadError5 multiThreadError5 = new MultiThreadError5(mySource);
    }
}
复制代码

在构造方法中使用线程致使逸出

/**
 * 构造函数中新建线程
 */
public class MultiThreadError6 {

    private Map<String, String> states;

    public MultiThreadError6() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                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) {
        MultiThreadError6 multiThreadError6 = new MultiThreadError6();
        Map<String, String> states = multiThreadError6.getStates();
        System.out.println(states.get("1"));
    }
}
复制代码

9.7 逸出的解决——用“副本”替代“真身”(针对返回私有对象)

/**
 * 发布逸出
 */
public class MultiThreadError3 {
    private Map<String, String> states;
    public MultiThreadError3(){
        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);   //建立一个states副本
    }

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

        System.out.println(multiThreadError3.getStatesImproved().get("1"));
        multiThreadError3.getStatesImproved().remove("1");
        System.out.println(multiThreadError3.getStatesImproved().get("1"));
    }
}
复制代码

9.8 逸出的解决——巧用工厂模式(能够修复监听器注册的问题)

/**
 * 用工厂模式解决监听器注册问题
 */
public class MultiThreadError7 {

    int count;
    private EventListener listener;

    private MultiThreadError7(MySource source){
        listener = new EventListener() {
            @Override
            public void onEvent(MultiThreadError7.Event e) {
                System.out.println("\n我获得的数字是:" + count);
            }
        };
        for (int i = 0; i < 10000; i++) {
            System.out.print(i);
        }
        count = 100;
    }

    public static MultiThreadError7 getInstance(MySource source){
        MultiThreadError7 safeListener = new MultiThreadError7(source);
        source.registerListener(safeListener.listener);
        return safeListener;
    }

    static class MySource {
        private MultiThreadError7.EventListener listener;

        void registerListener(MultiThreadError7.EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(MultiThreadError7.Event e){
            if (listener != null){
                listener.onEvent(e);
            }else{
                System.out.println("还未初始化完毕");
            }
        }
    }


    interface EventListener {
        void onEvent(MultiThreadError7.Event e);
    }

    interface Event {

    }

    public static void main(String[] args) {
        MultiThreadError7.MySource mySource = new MultiThreadError7.MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mySource.eventCome(new MultiThreadError7.Event() {
                });
            }
        }).start();

        MultiThreadError7 multiThreadError7 = new MultiThreadError7(mySource);
    }
}
复制代码

10.何时会有线程安全的问题

  • 并发访问共享的资源时会产生线程安全问题,好比对象属性、静态变量、数据库、共享缓存等
  • 全部依赖时序的操做,即便每步操做都是线程安全的,也会存在并发问题,其中包括:先读取后修改(这样读取到的内容有可能会失效)、先检查再执行
  • 不一样的数据之间存在绑定关系的时候,例如对外发布服务端口号和ip地址必须对应,即保证原子性。
  • 在使用其余类时,不是线程安全的(比例HashMap在并发中可能会出现问题)

11.为何多线程会有性能问题

  • 调度:上下文切换

    • 上下文切换能够认为是内核在CPU上进行的如下活动:(1)挂起一个进程将进程中的状态(上下文)存储在内存中的某处。(2)在内存中检索下一个进程的状态将它在CPU寄存器中恢复。(3)跳转到程序计数器所指向的位置以恢复该线程

    • 缓存开销:CPU从新缓存

    • 什么时候会致使密集的上下文切换:频繁竞争锁或者由于IO频繁致使阻塞

  • 协做:内存同步

    • 为了数据的正确性,同步手段每每会使用禁止编译器优化、使CPU的缓存失效
相关文章
相关标签/搜索