java 线程

进程和线程的区别

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。

多线程的存在其实就是“最大限度的利用cpu资源”,当某一个线程的处理不需要占用cpu而之和I/O打交道的时候,让需要占用cpu资源的其他线程有机会获得cpu资源。从根本上说,这就是说多线程编程的目的。
多线程目的: 使用多线程,可以帮助我们编写出cpu最大利用率的高效程序,使得空闲时间降到最低

在java中要想实现多线程:
1
2
3
4
继续Thread类
实现Runable接口
通过Callable创建线程
线程池来实现

ExecutorService 创建线程池

// 创建一个可缓存的线程池,调用execute将重用以前构成的线程(如果线程可用)。
// 如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移出那些已有60秒钟未被使用的线程。
Executors.newCachedThreadPool();
// 创建固定数目的线程池
Executors.newFixedThreadPool(1);
// 创建一个单线程化的Executor
Executors.newSingleThreadExecutor();
// 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Time类。
Executors.newScheduledThreadPool(1);

为什么不推荐通过Executors直接创建线程池

原因:
java中BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue。ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
而LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE.

查看new SingleExecutor时的源码可以发现,在创建LinkedBlockingQueue时,并未指定容量。
此时,LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出的问题。

创建线程池的正确方法:

避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数自己创建线程池。在创建的同时,给BlockQueue指定容量就可以了。

ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(10));

这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,
这是因为当前线程池使用的队列是有界边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。

ExecutorService shutdown()、ShutdownNow()、awaitTermination()

shutdown() 停止接收新任务,原来的任务继续执行
    1、停止接收新的submit的任务;
    2、已经提交的任务(包括正在跑的和队列中等待的),会继续执行完成;
    3、等到第2步完成后,才真正停止;

ShutdownNow() 停止接收新任务,原来的任务停止执行
    1、跟 shutdown() 一样,先停止接收新submit的任务;
    2、忽略队列里等待的任务;
    3、尝试将正在执行的任务interrupt中断;
    4、返回未执行的任务列表;

awaitTermination(long timeOut, TimeUnit unit):当前线程阻塞
    当前线程阻塞,直到:
    等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
    或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
    或者 线程被中断,抛出InterruptedException

区别

    1、shutdown() 和 shutdownNow() 的区别
        shutdown() 只是关闭了提交通道,用submit()是无效的;而内部该怎么跑还是怎么跑,跑完再停。
        shutdownNow() 能立即停止线程池,正在跑的和正在等待的任务都停下了。
    2、shutdown() 和 awaitTermination() 的区别
        shutdown() 后,不能再提交新的任务进去;但是 awaitTermination() 后,可以继续提交。
        awaitTermination()是阻塞的,返回结果是线程池是否已停止(true/false);shutdown() 不阻塞。

总结
    1、优雅的关闭,用 shutdown()
    2、想立马关闭,并得到未执行任务列表,用shutdownNow()
    3、优雅的关闭,并允许关闭声明后新任务能提交,用 awaitTermination()
    4、关闭功能 【从强到弱】 依次是:shuntdownNow() > shutdown() > awaitTermination()

例:

1、扩展Thread类实现的多线程例子

public static void main(String[] args) {
    MyThread T1 = new MyThread("A");
    MyThread T2 = new MyThread("B");
    T1.start();
    T2.start();
}
class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name+":"+i);
            try {
                sleep(1000); //休眠1秒,避免太快导致看不到同时执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

结果:
   A:0
   B:0
   A:1
   B:1
   A:2
   B:2
   A:3
   B:3
   A:4
   B:4

2、实现Runnable 、 ExecutorService 接口的多线程例子

    ExecutorService executor = new ThreadPoolExecutor(10, 10,
                     60L, TimeUnit.SECONDS,
                     new ArrayBlockingQueue<Runnable>(10));
    public static void main(String[] args) {
        // executor
        /*for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executor.execute(new SubThread());
        }*/

        // Runnable
        executor.execute(new MyRunnable());
    }

 // 1. Runnable
class SubThread implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) { //do nothing
        }
    }
}

 // 2. Runnable
class MyRunnable implements Runnable{

    public void run(){
        System.out.println("Runnable:run()....");
        int i=0;
        while(i<20){
            i++;
            for(int j=0;j<1000000;j++);
            System.out.println("i="+i);
        }
    }
}
结果:
    Runnable:run()....
    i=1
    i=2
    i=3
    i=4
    i=5
    i=6
    i=7
    i=8
    i=9
    i=10
    i=11
    i=12
    i=13
    i=14
    i=15
    i=16
    i=17
    i=18
    i=19
    i=20

3、实现Callable接口的多线程例子

    ExecutorService executor = new ThreadPoolExecutor(10, 10,
                             60L, TimeUnit.SECONDS,
                             new ArrayBlockingQueue<Runnable>(10));
    public static void main(String[] args) {
        // Callable
        Future future1 = executor.submit(new MyCallable());
        Future future2 = executor.submit(new MyCallable());
        try {
            System.out.println(future1.get());
            System.out.println(future2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 停止接收新任务,原来的任务继续执行
        executor.shutdown();
    }
class MyCallable implements Callable<String> {
    public String call() throws Exception {
        System.out.println("开始执行Callable");
        String[] ss={"zhangsan","lisi"};
        long[] num=new long[2];
        for(int i=0;i<1000000;i++){
            num[(int)(Math.random()*2)]++;
        }

        if(num[0]>num[1]){
            return ss[0];
        }else if(num[0]<num[1]){
            throw new Exception("弃权!");
        }else{
            return ss[1];
        }
    }
}

结果;

开始执行Callable
开始执行Callable
zhangsan
zhangsan

wait和sleep的区别

1.wait和notify方法定义在Object类中,因此会被所有的类所继承。 这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。 而sleep方法是在Thread类中是由native修饰的,本地方法。
2.当线程调用了wait()方法时,它会释放掉对象的锁。 
    Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
3.正因为wait方法会释放锁,所以调用该方法时,当前的线程必须拥有当前对象的monitor,也即lock,就是锁。
要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
4.sleep方法时间到,线程处于临时阻塞状态或者运行状态。 wait方法如果没有被设置时间,就必须要通过notify或者notifyAll来唤醒。

notify()

notify()方法会唤醒一个等待当前对象的锁的线程。 如果多个线程在等待,它们中的一个将会选择被唤醒。
这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait方法中的一个)。

被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁。
被唤醒的线程将和其他线程以通常的方式进行竞争,来获得对象的锁。也就是说,被唤醒的线程并没有什么优先权,也没有什么劣势,对象的下一个线程还是需要通过一般性的竞争。
且notify方法和wait一样,是需要放在synchronized方法或synchronized块中。

notifyAll()方法会唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;

尽量使用notifyAll(),notify()非常容易导致死锁。

ThreadLocal 的简单使用及实现的原理

ThreadLocal简介

ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。

它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。

ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

ThreadLocal的核心机制

1
2
3
每个Thread线程内部都有一个Map。
Map里面存储线程本地对象(key)和线程的变量副本(value)
但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰

ThreadLocal的简单使用

下面的例子中,创建了两个线程,然后线程对各自的局部变量进行递增的操作。每个线程中的局部变量的初始值都是100。

public class ThreadLocalTest {
    // ThreadLocal中的值
    static class Bank {
        ThreadLocal<Integer> t = new ThreadLocal<Integer>() {
            // 重写里面的方法就可以修改初始值了
            @Override
            protected Integer initialValue(){
                return 100;
            }
        };
        public int get() {
            return t.get();
        }
        public void set() {
            // 获取值
            t.set(t.get() + 10);
        }
        public void set(Integer value) {
            // 获取值
            t.set(value);
        }
    }
    /** 定义对ThreadLocal的操作,也就是在原来的基础上进行加10的操作,然后打印出结果。 */
    // 对ThreadLocal的操作
    static class Transfer implements Runnable {
        Bank bank;
        public Transfer(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                //
                bank.set();
                // 输出不同线程的ThreadLocal的值
                System.out.println(Thread.currentThread() + ":::" + bank.get());
            }
        }
    }
    /** 创建两个线程并启动,主线程等待这两个线程执行完成。最值得注意的就是主线程中输出的bank.get(),输出的初始值100。 */
    // 创建线程并等待线程执行完成
    public static void main(String[] args) throws InterruptedException {
        //
        Bank bank = new Bank();
        // bank.set(50);
        // 多个线程都是同时操作一个变量,但是不同线程的结果是互不影响的
        Transfer t = new Transfer(bank);
        Thread t1 = new Thread(t);
        t1.start();
        Thread t2 = new Thread(t);
        t2.start();
        t1.join();
        t2.join();
        // 需要注意的是,这个是main() 线程中的变量,输出的是 ThreadLocal<Integer> t 的初始值, 也就是100
        System.out.println(bank.get());
    }
}

ThreadLocal的实现原理

ThreadLocal类提供如下几个核心方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
1
2
3
4
get()方法用于获取当前线程的副本变量值。
set()方法用于保存当前线程的副本变量值。
initialValue()为当前线程初始副本变量值。
remove()方法移除当前前程的副本变量值

每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。

在该类中,重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

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