一、基本概念
1.1、过程
过程是零碎资源分配的最小单位。由 文本区域
, 数据区域
和堆栈
组成。
- 文本区域存储处理器执行的代码
- 数据区域存储变量和过程执行期间应用的动态分配的内存;
- 堆栈区域存储着流动过程调用的指令和本地变量。
波及问题:cpu 抢占
, 内存调配 (虚拟内存 / 物理内存)
,以及 过程间通信
。
1.2、线程
线程是操作系统可能进行运算调度的最小单位。
一个过程能够包含多个线程,线程 共用
过程所调配到的 资源空间
波及问题:线程状态
, 并发问题
, 锁
1.3、协程
子例程
: 某个主程序的一部分代码, 也就是指某个办法,函数。
维基百科:执行过程相似于 子例程
,有本人的上下文,然而其切换由本人管制。
1.4、常见问题
-
1、过程和线程的区别
过程领有本人的资源空间,而线程须要依赖于过程进行资源的调配,能力执行相应的工作。过程间通信须要依赖于 管道,共享内存,信号(量)和音讯队列等形式。线程不平安,容易导致过程解体等
-
2、什么是多线程
线程是运算调度的最小单位,即每个处理器在某个工夫点上只能解决一个线程任务调度。在多核 cpu 上,为了进步咱们 cpu 的使用率,从而引出了多线程的实现。通过多个线程工作并发调度,实现工作的并发执行。也就是咱们所说的多线程工作执行。
二、Thread
2.1、应用多线程
2.1.1、继承 Thread 类
class JayThread extends Thread{
@Override
public void run(){System.out.println("hello world in JayThread!");
}
}
class Main{public static void main(String[] args){JayThread t1 = new JayThread();
t1.start();}
}
2.1.2、实现 Runnable 接口
class JayRunnable implements Runnable{
@Override
public void run(){System.out.println("hello world in JayRunnable!")
}
}
class Main{public static void main(String[] args){JayRunnable runnable = new JayRunnable();
Thread t1 = new Thread(runnable);
t1.start();}
}
2.1.3、实现 Callable 接口
class JayCallable implements Callable<String> {
@Override
public String call() throws Exception {System.out.println("run in JayCallable" + Thread.currentThread().getName());
return "Jayce";
}
}
class Main{public static void main(String[] args) {Thread.currentThread().setName("main thread");
ThreadPoolExecutor executor =new ThreadPoolExecutor(10,20,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
Future<String> future = executor.submit(new JayCallable());
try {future.get(10, TimeUnit.SECONDS);
}catch (Exception e){System.out.println("工作执行超时");
}
}
}
2.1.4、常见问题
- 1、应用多线程有哪些形式
罕用的形式次要由上述 3 种,须要留神的是 应用
, 而不是创立线程,从实现的代码咱们能够看到,Java 创立线程只有一种形式,就是通过 new Thread()
的形式进行创立线程。
- 2、
Thread()
,Runnable()
与Callable()
之间的区别
Thread
须要继承,重写 run()
办法, 对拓展不敌对,一个类即一个线程工作。
Runnbale
通过接口的形式,能够实现多个接口,继承父类。须要创立一个线程进行装载工作执行。
Callable
JDK1.5 后引入,解决 Runnable 不能返回后果或抛出异样的问题。须要联合 ThreadPoolExecutor
应用。
- 3、
Thread.run()
和Thread.start()
的区别
Thread.run()
public static void main(String[] args){Thread.currentThread().setName("main thread");
Thread t1 = new Thread(()->{System.out.println("run in"+Thread.currentThread().getName());
});
t1.setName("Jayce Thread");
t1.run();}
输入后果:
Thread.start()
public static void main(String[] args){Thread.currentThread().setName("main thread");
Thread t1 = new Thread(()->{System.out.println("run in"+Thread.currentThread().getName());
});
t1.setName("Jayce Thread");
t1.start();}
输入后果:
start() 办法来启动线程, 使当前任务进入 cpu 期待队列(进入就绪状态,期待 cpu 分片),获取分片后执行 run 办法。run() 办法执行,会被解析成一个一般办法的调用,间接在以后线程执行。
2.2、线程状态
线程状态,也称为线程的生命周期,次要能够分为:新建
, 就绪
, 运行
, 死亡
, 梗塞
等五个阶段。
图片援用 芋道源码
2.2.1 新建
新建状态比拟好了解,就是咱们调用 new Thread()
的时候所创立的线程类。
2.2.2 就绪
就绪状态指得是:
1、当调用 Thread.start
时,线程能够开始执行,然而须要期待获取 cpu 资源。区别于 Thread.run
办法,run
办法是间接在以后线程进行执行,沿用其 cpu 资源。
2、运行状态下,cpu 资源
应用完后,从新进入就绪状态,从新期待获取 cpu 资源
. 从图中能够看到,能够间接调用 Thread.yield
放弃以后的 cpu 资源,进入就绪状态。让其余优先级更高的工作优先执行。
2.2.3 运行
在 步骤 2
就绪状态中,获取到 cpu 资源
后,进入到运行状态,执行对应的工作,也就是咱们实现的 run()
办法。
2.2.4 完结
1、失常工作执行实现,run() 办法执行结束
2、异样退出,程序抛出异样,没有捕捉
2.2.5 阻塞
阻塞次要分为:io 期待,锁期待,线程期待 这几种形式。通过上述图片能够直观的看到。
io 期待:期待用户输出,让出 cpu 资源,等用户操作实现后(io 就绪),从新进入就绪状态。
锁期待:同步代码块须要期待获取锁,能力进入就绪状态
线程期待:sleep()
,join()
和 wait()/notify()
办法都是期待线程状态的阻塞(能够了解成以后线程的状态受别的线程影响)
二、线程池
2.1 池化技术
池化技术,次要是为了缩小每次资源的创立,销毁所带来的损耗,通过资源的反复利用进步资源利用率而实现的一种技术计划。常见的例如:数据库连接池,http 连接池以及线程池等。都是通过池同一治理,反复利用,从而进步资源的利用率。
应用线程池的益处:
- 升高资源耗费:通过反复利用已创立的线程升高线程创立和销毁造成的耗费。
- 进步响应速度:当工作达到时,工作能够不须要的等到线程创立就能立刻执行。
- 进步线程的可管理性:线程是稀缺资源,如果无限度的创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够进行对立的调配,调优和监控。
2.2 线程池创立
2.2.1 Executors (不倡议)
Executors 能够比拟快捷的帮咱们创立相似 FixedThreadPool,CachedThreadPool 等类型的线程池。
// 创立繁多线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创立固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创立带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创立定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创立流式(fork-join)线程池
public static ExecutorService newWorkStealingPool();
存在的弊病:
FixedThreadPool 和 SingleThreadExecutor:容许申请的队列长度为 Integer.MAX_VALUE,可能沉积大量的申请,从而导致 OOM。CachedThreadPool 和 ScheduledThreadPool:容许创立的线程数量为 Integer.MAX_VALUE,可能会创立大量线程,从而导致 OOM。
2.2.2 ThreadPoolExecuotr
构造函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
几个外围的参数:
- 1、
corePoolSize
: 外围线程数 - 2、
maximumPoolSize
: 最大线程数 - 3、
keepAliveTime
:线程闲暇存活工夫 - 4、
unit
:工夫单位 - 5、
workQueue
:期待队列 - 6、
threadFactory
:线程工厂 - 7、
handler
: 回绝策略
与上述的 ExecutorService.newSingleThreadExecutor
等多个 api
进行比照,能够比拟容易的辨别出底层的实现是依赖于 BlockingQueue
的不同而定义的线程池。
次要由以下几种的阻塞队列:
- 1、
ArrayBlockingQueue
,队列是有界的,基于数组实现的阻塞队列 - 2、
LinkedBlockingQueue
,队列能够有界,也能够无界。基于链表实现的阻塞队列 对应了:Executors.newFixedThreadPool()
的实现。 - 3、
SynchronousQueue
,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将始终处于阻塞状态。对应了:Executors.newCachedThreadPool()
的实现。 - 4、
PriorityBlockingQueue
,带优先级的无界阻塞队列
回绝策略次要有以下 4 种:
- 1、
CallerRunsPolicy
:在调用者线程执行 - 2、
AbortPolicy
:间接抛出 RejectedExecutionException 异样 - 3、
DiscardPolicy
:工作间接抛弃,不做任何解决 - 4、
DiscardOldestPolicy
: 抛弃队列里最旧的那个工作,再尝试执行当前任务
2.3 线程池提交工作
往线程池中提交工作,次要有两种办法,execute()
和submit()
1、execute()
无返回后果,间接执行工作
public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("hello"));
}
2、submit()
submit()
会返回一个 Future
对象,用于获取返回后果,罕用的 api 有 get()
和 get(timeout,unit)
两种形式,罕用于做限时解决
public static void main(String[] args) throws Exception {ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> future = executor.submit(() -> {System.out.println("hello world!");
return "hello world!";
});
System.out.println("get result:" + future.get());
}
三、线程工具类
3.1 ThreadlLocal
ThreadLocal,很多中央叫做线程本地变量,也有些中央叫做线程本地存储,其实意思差不多。可能很多敌人都晓得 ThreadLocal 为变量在每个线程中都创立了一个正本,那么每个线程能够拜访本人外部的正本变量。
3.2 Semaphore
Semaphore,是一种新的同步类,它是一个计数信号. 应用示例代码:
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能 5 个线程同时拜访
final Semaphore semp = new Semaphore(5);
// 模仿 20 个客户端拜访
for (int index = 0; index < 50; index++) {
final int NO = index;
Runnable run = new Runnable() {public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing:" + NO);
Thread.sleep((long) (Math.random() * 6000));
// 拜访完后,开释
semp.release();
//availablePermits()指的是当前信号灯库中有多少个能够被应用
System.out.println("-----------------" + semp.availablePermits());
} catch (InterruptedException e) {e.printStackTrace();
}
}
};
exec.execute(run);
}
// 退出线程池
exec.shutdown();
3.3 CountDownLatch
能够了解成是一个栅栏,须要等所有的线程都执行实现后,能力持续往下走。
CountDownLatch
默认的构造方法是 CountDownLatch(int count)
,其参数示意须要缩小的计数,主线程调用 #await()
办法通知 CountDownLatch
阻塞期待指定数量的计数被缩小,而后其它线程调用 CountDownLatch
的 #countDown()
办法,减小计数(不会阻塞)。期待计数被缩小到零,主线程完结阻塞期待,持续往下执行。
3.4 CyclicBarrier
CyclicBarrier
与 CountDownLatch
有点类似,都是让线程都达到某个点,能力持续往下走,有所不同的是 CyclicBarrier
是能够屡次应用的。示例代码:
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {this.barrier = barrier;}
@Override
public void run() {
try {Thread.sleep(1000);
System.out.println(getName() + "达到栅栏 A");
barrier.await();
System.out.println(getName() + "冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + "达到栅栏 B");
barrier.await();
System.out.println(getName() + "冲破栅栏 B");
} catch (Exception e) {e.printStackTrace();
}
}
四、总结
最初贴一个新生的公众号(Java 补习课
),欢送各位关注,次要会分享一下面试的内容(参考之前博主的文章),阿里的开源技术之类和阿里生存相干。想要交换面试教训的,能够增加我的集体微信(Jayce-K
)进群学习~