Java线程,线程池API

线程:

我们使⽤  Runnable  和  Thread  来创建⼀个新的线程.

package com.tian;

/**
 * @Author Administrator
 * @Date 2020/4/13 0013 16:08
 * @Version 1.0
 */
public class Test {
    public static class MyThread extends Thread{
        @Override
        public void run(){
            System.out.println("启动一个线程");
        }
    }

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

        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        },"one").start();
    }
}

开启⼀个线程去执⾏⼀个任务,并且这个任务执⾏完成后有⼀个返回值.

Callable接⼝

@?unctionalInterface
public interface Callable {
V call() throws Exception;
}

ExecutorService  可以使⽤  submit  ⽅法来让⼀个  Callable  接⼝执⾏。它会返回⼀个  Future  ,我们后续的程序可以通过这个  Future  的  get  ⽅法得到结果。

// ⾃定义Callable
class Task implements Callable{
@Override
public Integer call() throws Exception {
// 模拟计算需要⼀秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
// 使⽤
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future result = executor.submit(task);
// 注意调⽤get⽅法会阻塞当前线程,直到得到结果。
// 所以实际编码中建议使⽤可以设置超时时间的重载get⽅法。
System.out.println(result.get());
}
}

 Future  接⼝。这个接⼝有⼀个实现类叫  FutureTask  。  FutureTask  是实现的  RunnableFuture  接⼝的,⽽  RunnableFuture  接⼝同时继承了  Runnable  接⼝和Future 接⼝:这是JDK提供一个Future接口的实现类让我们使用

public interface RunnableFuture extends Runnable, Future {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}

使用示例:

// ⾃定义Callable,与上⾯⼀样
class Task implements Callable{
@Override
public Integer call() throws Exception {
// 模拟计算需要⼀秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
// 使⽤
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}

在很多⾼并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能
够在⾼并发环境下确保任务只执⾏⼀次。

线程使用锁:

 public class ObjectLock {
        private static Object lock = new Object();
        static class ThreadA implements Runnable {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("Thread A " + i);
                    }
                }
            }
        }
        static class ThreadB implements Runnable {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("Thread B " + i);
                    }
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            new Thread(new ThreadA()).start();
            Thread.sleep(10);
            new Thread(new ThreadB()).start();
        }
    }
这⾥声明了⼀个名字为  lock  的对象锁。我们在  ThreadA  和  ThreadB  内需要同步的
代码块⾥,都是⽤  synchronized  关键字加上了同⼀个对象锁  lock  。
上⽂我们说到了,根据线程和锁的关系,同⼀时间只有⼀个线程持有⼀个锁,那么
线程B就会等线程A执⾏完成后释放  lock  ,线程B才能获得锁  lock  。

基于“锁”的⽅式,线程需要不断地去尝试获得锁,如果失败了,再继续尝试。这可能会耗费服务器资源。

另一种方式:Java多线程的等待/通知机制是基于  Object  类的  wait()  ⽅法和  notify()  , notifyAll()  ⽅法来实现的。

notify()⽅法会随机叫醒⼀个正在等待的线程,⽽notifyAll()会叫醒所有正在等待的线程。

public class Test {

    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadA: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadB: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}
// 输出:
ThreadA: 0
        ThreadB: 0
        ThreadA: 1
        ThreadB: 1
        ThreadA: 2
        ThreadB: 2
        ThreadA: 3
        ThreadB: 3
        ThreadA: 4
        ThreadB: 4

线程A和线程B⾸先打印出⾃⼰需要的东⻄,然后使⽤  notify()  ⽅法叫醒另⼀个正在等待的线程,然后⾃⼰使⽤  wait()  ⽅法陷⼊等待并释放  lock  锁。(A在notify()的时候,B并没有获得锁,只有A wait()之后B才能获得锁.)

如果两个线程使⽤的是不同的对象锁,那它们之间是不能⽤等待/通知机制通信的。

join⽅法

有时候,主线程创建并启动了⼦线程,如果⼦线程中需要进⾏⼤量的耗时运算,主线程往往将早于⼦线程结束之前结束。如果主线程想等待⼦线程执⾏完毕后,获得⼦线程中的处理完的某个数据,就要⽤到join⽅法了。

public class Join {
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("我是⼦线程,我先睡⼀秒");
                Thread.sleep(1000);
                System.out.println("我是⼦线程,我睡完了⼀秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadA());
        thread.start();
        thread.join();
        System.out.println("如果不加join⽅法,我会先被打出来,加了就不⼀样了");
    }
}

sleep⽅法

sleep⽅法是Thread类的⼀个静态⽅法。它的作⽤是让当前线程睡眠⼀段时间。

Thread.sleep(long)
Thread.sleep(long, int)

sleep⽅法是不会释放当前的锁的,⽽wait⽅法会。

wait可以指定时间,也可以不指定;⽽sleep必须指定时间。
wait释放cpu资源,同时释放锁;sleep释放cpu资源,但是不释放锁,所以易死锁。
wait必须放在同步块或同步⽅法中,⽽sleep可以再任意位置

ThreadLocal类

严格来说,ThreadLocal类并不属于多线程间的通信,⽽是让每个线程有⾃⼰”独⽴“的变量,线程之间互不影响。它为每个线程都创建⼀个副本,每个线程可以访问⾃⼰内部的副本变量。

java内存模型

Java线程,线程池API_第1张图片

对于每⼀个线程来说,栈都是私有的,⽽堆是共有的。

既然堆是共享的,为什么在堆中会有内存不可⻅问题?

这是因为现代计算机为了⾼效,往往会在⾼速缓存区中缓存共享变量,因为cpu访问缓存区⽐访问内存要快得多。

volatile的内存语义

1.保证变量的内存可⻅性
2.禁⽌volatile变量与普通变量重排序(JSR133提出,Java 5 开始才有这个“增强的volatile内存语义”)

每个线程都有自己私有的工作内存,工作内存中保存了一些在主内存拷贝的变量.

public class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // step 1
flag = true; // step 2
}
public void reader() {
if (flag) { // step 3
System.out.println(a); // step 4
}
}
}

所谓内存可⻅性,指的是当⼀个线程对  volatile  修饰的变量进⾏写操作(⽐如step 2)时,JMM会⽴即把该线程对应的本地内存中的共享变量的值刷新到主内存;当⼀个线程对  volatile  修饰的变量进⾏读操作(⽐如step 3)时,JMM会把⽴即该线程对应的本地内存置为⽆效,从主内存中读取共享变量的值。

synchronized与锁

Java多线程的锁都是基于对象的,Java中的每⼀个对象都可以作为⼀个锁。

// 关键字在实例⽅法上,锁为当前实例
public synchronized void instanceLock() {
// code
}
// 关键字在静态⽅法上,锁为当前Class对象
public static synchronized void classLock() {
// code
}
// 关键字在代码块上,锁为括号⾥⾯的对象
public void blockLock() {
Object o = new Object();
synchronized (o) {
// code
}
}

线程池

1. 创建/销毁线程需要消耗系统资源,线程池可以复⽤已创建的线程。
2. 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从⽽造成服务器崩溃。(主要原因)
3. 可以对线程做统⼀管理。

Java中的线程池顶层接⼝是  Executor  接⼝,  ThreadPoolExecutor  是这个接⼝的实现类。

ThreadPoolExecutor如何做到线程复⽤的?

ThreadPoolExecutor在创建线程时,会将线程封装成⼯作线程worker,并放⼊⼯作线程组中,然后这个worker反复从阻塞队列中拿任务去执⾏。

Java线程,线程池API_第2张图片

 四种常见的线程池,这是JDK提供的.

一.newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
1. 提交任务进线程池。
2. 因为corePoolSize为0的关系,不创建核⼼线程,线程池最⼤为
Integer.MAX_VALUE。
3. 尝试将任务添加到SynchronousQueue队列。
4. 如果SynchronousQueue⼊列成功,等待被当前运⾏的线程空闲后拉取执⾏。
如果当前没有空闲线程,那么就创建⼀个⾮核⼼线程,然后从
SynchronousQueue拉取任务并在当前线程执⾏。
5. 如果SynchronousQueue已有任务在等待,⼊列操作将会阻塞。
当需要执⾏很多短时间的任务时,CacheThreadPool的线程复⽤率⽐较⾼, 会显
著的提⾼性能。⽽且线程60s后会回收,意味着即使没有任务进来,
CacheThreadPool并不会占⽤很多资源。

二.newFixedThreadPool

public static ExecutorService new?ixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
核⼼线程数量和总线程数量相等,都是传⼊的参数nThreads,所以只能创建核⼼线
程,不能创建⾮核⼼线程。因为LinkedBlockingQueue的默认⼤⼩是
Integer.MAX_VALUE,故如果核⼼线程空闲,则交给核⼼线程处理;如果核⼼线程
不空闲,则⼊列等待,直到核⼼线程空闲。
与CachedThreadPool的区别:
因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核
⼼线程。 ⽽CachedThreadPool因为corePoolSize=0,所以只会创建⾮核⼼线
程。
在 getTask() ⽅法,如果队列⾥没有任务可取,线程会⼀直阻塞在
LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在
60s后收回。由于线程不会被回收,会⼀直卡在阻塞,所以没有任务的情况下,
FixedThreadPool占⽤资源更多。
都⼏乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列
可以很⼤(最⼤为Integer最⼤值),故⼏乎不会触发拒绝策略;
CachedThreadPool是因为线程池很⼤(最⼤为Integer最⼤值),⼏乎不会导
致线程数量⼤于最⼤线程数,故⼏乎不会触发拒绝策略。

三.newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
return new ?inalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
有且仅有⼀个核⼼线程( corePoolSize == maximumPoolSize=1),使⽤了
LinkedBlockingQueue(容量很⼤),所以,不会创建⾮核⼼线程。所有任务按照
先来先执⾏的顺序执⾏。如果这个唯⼀的线程不空闲,那么新来的任务会存储在任
务队列⾥等待执⾏。

四.newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DE?AULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
创建⼀个定⻓线程池,⽀持定时及周期性任务执⾏。



 public static void main(String[] args) {
        //创建使用单个线程的线程池
        ExecutorService es1 = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            es1.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在执行任务");
                }
            });
        }
        //创建使用固定线程数的线程池
        ExecutorService es2 = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            es2.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在执行任务");
                }
            });
        }
        //创建一个会根据需要创建新线程的线程池
        ExecutorService es3 = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            es3.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "正在执行任务");
                }
            });
        }
        //创建拥有固定线程数量的定时线程任务的线程池
        ScheduledExecutorService es4 = Executors.newScheduledThreadPool(2);
        System.out.println("时间:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            es4.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("时间:"+System.currentTimeMillis()+"--"+Thread.currentThread().getName() + "正在执行任务");
                }
            },3, TimeUnit.SECONDS);
        }
        //创建只有一个线程的定时线程任务的线程池
        ScheduledExecutorService es5 = Executors.newSingleThreadScheduledExecutor();
        System.out.println("时间:" + System.currentTimeMillis());
        for (int i = 0; i < 5; i++) {
            es5.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println("时间:"+System.currentTimeMillis()+"--"+Thread.currentThread().getName() + "正在执行任务");
                }
            },3, TimeUnit.SECONDS);
        }
    }

线程池的构造函数:

//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue)

//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory)

//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          RejectedExecutionHandler handler)

//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

int corePoolSize  该线程池中核心线程数最大值

int maximumPoolSize 该线程池中线程总数最大值(核心线程+非核心线程)

long keepAliveTime 该线程池中非核心线程闲置超时时长

TimeUnit unit keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天

BlockingQueue workQueue  该线程池中的任务队列:维护着等待执行的Runnable对象

当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

常用的workQueue类型:
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大

LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误

DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

另外两个直接使用默认,一个是名字,一个是异常处理

 

你可能感兴趣的:(日常)