线程:
我们使⽤ 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内存模型
对于每⼀个线程来说,栈都是私有的,⽽堆是共有的。
既然堆是共享的,为什么在堆中会有内存不可⻅问题?
这是因为现代计算机为了⾼效,往往会在⾼速缓存区中缓存共享变量,因为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反复从阻塞队列中拿任务去执⾏。
四种常见的线程池,这是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接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
另外两个直接使用默认,一个是名字,一个是异常处理