Java多线程基础

1、多线程引入

1.1 进程和线程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

线程(Thread)是操作系统能够进行运算调度的最小单位。

——百度百科

在操作系统中,每打开一个应用程序(如QQ、网易云音乐),就是启动了一个进程,操作系统会为它们分配内存空间,并按照时间片算法来调度它们,由于CPU计算非常快,视觉感官上会认为是多个程序在同时运行。

每个应用程序都可以同时执行不同的任务,如QQ的聊天、上传文件、下载文件等,每个任务都交给不同的线程处理。

1.2 多线程

首先,多个线程并发执行,在很多场景下都能大大提高CPU资源的利用率。

  • 对于IO密集型作业,一个任务很可能只执行1ms的CPU,然后花50ms的时间去请求外部资源了(如查数据库、调用RPC),如果没有多线程,程序只能大眼瞪小眼地干等待。而多线程的引入,在线程1请求外部资源时,可以让出CPU给其他线程,从而大大程序整体运行效率。

  • 对于CPU密集型作业,多线程的存在依然是十分重要的,若存在一个新来的小任务,只需要执行不到1ms,而当前CPU正在处理一个大任务,需要很长时间,这时候引入多线程,就能尽快把小任务处理完,避免小任务长时间等待。

其次,目前CPU大多数都支持多核技术,多线程的引入能充分利用多核优势,进一步提高整体性能。

2、线程基础

2.1 线程状态转移图

Java多线程基础_第1张图片

  1. NEW:线程创建状态

  2. RUNNABLE:可运行状态,调用start()方法后进入该状态,可分为:

    1. RUNNING状态,即线程获取到CPU调度资源时

    2. READY状态,即就绪状态,可以随时被CPU调度执行

    3. yeild()方法,主动释放CPU资源,线程状态由RUNNING转为READY,也可能刚释放又获得CPU调度,线程状态由REDAY转为RUNNING

  3. WAITING:等待状态。

    1. Object.wait() && Object.notify()、Object.notifyAll()

      1. 线程只有在获取当前对象的锁时,才能调用该方法,否则抛出IllegalMonitorStateException异常

      2. 该方法调用后会释放当前的锁

      3. 只有在被其他线程notify()notifyAll()时唤醒

      4. 唤醒后进入BLOCKED状态,只有重新获得对象锁,才能继续往下执行

    2. thread.join()

      1. 调用该方法的线程(如main线程),会等待thread线程执行结束,thread线程执行结束后,会调用notifyAll()方法

      2. join()方法底层调用了wait()方法

      3. 注:上图的Object.join()是不对的,join()方法只属于Thread类

    3. LockSupport.park() && LockSupport.unpark(thread)

      1. park()方法阻塞当前线程,不释放锁

      2. unpark(thread)方法唤醒指定线程

  4. TIMED_WAITING:超时等待状态

    1. Thread.sleep(timeout)

      1. 不释放锁,睡一会,timeout后醒来

      2. 可被其他线程调用interrupt()中断

    2. Object.wait(timeout)

      1. 释放锁

      2. 等待timeout,若有notify()notifyAll(),马上醒来;否则timeout时间过后,自动醒来

    3. Thread.join(timeout)

      1. 会阻塞,释放锁

      2. timeout时间过后自动唤醒

      3. 当成功获取Thread对象的锁之后,才继续向下执行

    4. LockSupport.parkNanos(timeout) 、LockSupport.parkUntil(time)&&LockSupport.unpark(thread)

      1. parkNanos() :休眠timeout,除非被unpark(thread)唤醒

      2. parkUntil(time):休眠到time时间点

  5. BLOCKED:阻塞状态

    1. 当线程获取锁失败时,如synchronized方法、synchronized代码块

    2. 执行wait()/wait(timeout)方法的线程被唤醒后

    3. 执行join(timeout)方法的线程被唤醒后

  6. TERMINATED:终止状态

    1. 线程正常执行完

2.2 Thread常用的方法

  • Thread.currentThread():获取当前执行的线程

  • isAlive():判断线程是否存活

  • StackTraceElement[] getStackTrace():返回线程的堆栈跟踪元素数组。如多层调用了方法,则将方法信息层层压栈

  • dumpStack():将当前线程的堆栈信息输出至标准错误流

  • getId():获取线程唯一数字标识。main线程的id是1,新建一个线程id从11开始递增,说明2~9由隐藏线程占有

  • getName():获取线程名称。常用于分析线程执行情况

  • holdsLock(Obj):若当前线程成功获取obj的锁时,返回true,否则返回false

  • 废弃的方法:

    • stop():停止线程。暴力停止,有可能造成资源未关闭

    • suspend()和resume():挂起和恢复。可能造成无法预料到结果。可使用LockSupport.park()LockSupport.unpark(thread)来实现线程的暂停与继续

2.3 热点问题分析

2.3.1 中断interrupt()对wait、sleep、join、LockSupport.park的影响

wait、sleep、join都会抛出InterruptedException异常,而LockSupport.park会吞噬异常。

2.3.2 volatile关键字

可见性:一个线程对共享变量的修改,马上对其他使用该共享变量的线程可见

禁止指令重排序:volatile变量的读写,会加上内存屏障,阻止编译器对volatile变量前后的指令进行重排序:

代码块

a = 12;
b = 1;
volatile c = 0; // 1~2行 与 4~5行不能重排序,但1~2内部、4~5内部可以重排序
d = a;
e = b;

2.3.3 synchronized关键字

synchronized是一个悲观锁、独占锁,一次只能由一个线程执行synchronized所在的代码块。

    1. 对象的实例方法

public class Obj {
	public synchronized void test() {
  	// 锁住的是当前Obj对象
  }
  
  public void test2() {
  	synchronized(this) {
    	// 锁住的是当前Obj对象
    }
  }
}

2. 对象的静态方法

public class Obj {
	public static synchronized void test() {
  	// 锁住的是当前Obj.class对象
  }
  
  public void test2() {
  	synchronized(Obj.class) {
    	// 锁住的是当前Obj.class对象
    }
  }
}

2.3.4 线程的优先级

线程优先级分为1~10级,默认是5,当线程A创建另一个线程B时,也即A是B的父线程,B线程继承A线程的优先级。

线程的优先级越高,被JVM调用的几率也会越大。

2.3.5 守护线程和非守护线程

守护线程:为非守护线程提供服务,如GC保洁线程

非守护线程:实际工作处理任务的线程

当JVM中只剩守护线程时,系统会停止,因为没有非守护线程可以服务了。

3. 线程的通信与隔离

3.1 wait/notify机制——经典的生产者/消费者模式

当需要某种条件不满足时,执行wait方法等待;当条件满足时,由其它线程唤醒等待的线程。wait/notify机制就是为了控制线程“条件不满足则等待,条件满足则唤醒、重新获取锁、继续执行”。

(1)一个生产者&一个消费者

public class OneProducerOneConsumer {
    Object obj = new Object();
    Object resource = null;
    public void consume() {
        synchronized(obj) {
            if (resource == null) {
                try {
                    obj.wait(); // 没有产品,则等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"消费了:"+resource);//消费一个产品
            resource = null;
            obj.notify();//通知生产
        }
    }

    public void produce() {
        synchronized(obj) {
            if (resource != null) {
                try {
                    obj.wait();// 产品未消费完,则等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            resource = "demo_" + new Random().nextInt(); //生产一个产品
            System.out.println(Thread.currentThread().getName()+"生产了:"+resource);
            obj.notify();//通知消费
        }
    }

    static class Consumer extends Thread {
        private OneProducerOneConsumer demo;
        public Consumer(OneProducerOneConsumer demo) {
            this.demo = demo;
        }

        @Override
        public void run() {
            while(true) {
                demo.consume();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Producer extends Thread {
        private OneProducerOneConsumer demo;
        public Producer(OneProducerOneConsumer demo) {
            this.demo = demo;
        }

        @Override
        public void run() {
            while(true) {
                demo.produce();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        OneProducerOneConsumer demo = new OneProducerOneConsumer();
        new Consumer(demo).start();
        new Producer(demo).start();
    }
}

结果如下,每生产一个产品,就消费一个产品,生产与消费总是成对出现。

Thread-1生产了:demo_1806630852
Thread-0消费了:demo_1806630852
Thread-1生产了:demo_240542868
Thread-0消费了:demo_240542868
Thread-1生产了:demo_-1755077240
Thread-0消费了:demo_-1755077240
Thread-1生产了:demo_-1253326727
Thread-0消费了:demo_-1253326727

(2)多个生产者&多个消费者

public class ManyProducerManyConsumers {
    Object consumerLock = new Object(); // 一次一个消费者
    Object producerLock = new Object(); // 一次一个生产者
    Object notEmpty = new Object(); 
    Object notFull = new Object();
    Object lock = new Object();// 资源互斥访问container资源
    List container = new ArrayList<>(FULL_SIZE);
    final static int FULL_SIZE = 10;

    public void consume() {
        // 一次只有一个消费者消费
        synchronized (consumerLock) {
            try {
                // 判断容器是否空
                synchronized (notEmpty) {
                    while (container.size() == 0) {
                        notEmpty.wait();
                    }
                }
                // 互斥访问资源
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "消费了:" + container.remove(container.size() - 1));
                }
                // 唤醒生产者
                synchronized (notFull) {
                    notFull.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public void produce() {
        // 一次只有一个生产者生产
        synchronized (producerLock) {
            try {
                // 若为满,则阻塞
                synchronized (notFull) {
                    while (container.size() == FULL_SIZE) {
                        notFull.wait();
                    }
                }
                // 互斥访问
                synchronized (lock) {
                    String str = "proId" + new Random().nextInt();
                    container.add(str);
                    System.out.println(Thread.currentThread().getName() + "生产了:" + str);
                }
                // 唤醒消费者
                synchronized (notEmpty) {
                    notEmpty.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Consumer extends Thread {
        private ManyProducerManyConsumers demo;

        public Consumer(ManyProducerManyConsumers demo) {
            this.demo = demo;
        }

        @Override
        public void run() {
            while (true) {
                demo.consume();
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Producer extends Thread {
        private ManyProducerManyConsumers demo;

        public Producer(ManyProducerManyConsumers demo) {
            this.demo = demo;
        }

        @Override
        public void run() {
            while (true) {
                demo.produce();
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ManyProducerManyConsumers demo = new ManyProducerManyConsumers();
        for(int i=0;i<5;i++) {
            new Producer(demo).start();
        }
        for (int i = 0; i < 5; i++) {
            new Consumer(demo).start();
        }
    }
}

3.2 ThreadLocal实现每个线程的数据隔离

每个线程都有自己的ThreadLocalMap变量,因此数据是隔离的。

需要注意的是,使用ThreadLocal后,应当手动调用remove方法清楚entry对象,这是因为Entry类将key(ThreadLocal)设置为弱引用,当GC发现它时,就会回收ThreadLocal对象,而value并没有回收,这就导致ThreadLocalMap中存在很多key为null,value不为null的Entry对象。

应用场景:登陆的用户数据,数据库连接,传参数。

Java多线程基础_第2张图片

public class ThreadLocalDemo {
    ThreadLocal threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        System.out.println(Thread.currentThread().getName()+" put value: main");
        threadLocalDemo.threadLocal.set("main");
        Thread.sleep(1000);
        Thread thread1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " put value: AAAA");
            threadLocalDemo.threadLocal.set("AAAA");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
        });
        Thread thread2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " put value: ZZZZZ");
            threadLocalDemo.threadLocal.set("ZZZZZ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
        });
        thread1.start();
        thread2.start();
        System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
    }
}

3.2 InheritableThreadLocal实现子线程继承父线程的值

InheritableThreadLocal能实现子线程继承父线程的值,也即做了一份浅拷贝。

应用场景:子线程需要获取父线程的数据信息。

public class InheritableThreadLocalDemo {
    static InheritableThreadLocal local = new InheritableThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+" set Value: hello");
        local.set("hello");
        Thread.sleep(100);
        new Thread(() -> System.out.println(Thread.currentThread().getName()+" getValue:"+local.get())).start();
    }
}

执行结果:

main set Value: hello
Thread-0 getValue:hello

你可能感兴趣的:(Java学习,java,开发语言)