笔记来源:小量传智播客+个人填充
//构造方法
Thread();
Thread(String threadname);//创建线程并指定线程实例名
Thread(Runnable r);//指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable r, String name);
Thread(FutureTask<> ft);//FutureTask<>(Callable<>);
常用方法:
void start(); 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield(): 线程让步,让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态。让cpu重新调度,礼让不一定成功,看CPU心情
//暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
//若队列中没有同优先级的线程,忽略此方法
join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
//低优先级的线程也可以获得执行
static void sleep(long millis)//令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
sleep存在异常InteruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网延时,倒计时等;
每一个对象都有一个锁,sleep不会释放锁;
stop(): 强制线程生命期结束,不推荐使用
destroy()方法(已废弃)
boolean isAlive(): 返回boolean,判断线程是否还活着
1.继承Thread
2.实现Runnable
3.实现Callable
4.使用线程池
注意点:
\1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
\2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
\3. 想要启动多线程,必须调用start方法。
\4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“IllegalThreadStateException”
区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
package com.atguigu.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//方式1:继承Thrad
class MyThread01 extends Thread {
@Override
public void run() {//无返回值
System.out.println("-----MyThread01");
}
}
//方式2:实现Runnable
class MyThread02 implements Runnable {//还可以实现、继承别的类
public void run() {//无返回值
System.out.println("-----MyThread02");
}
}
//方式3:实现Callable<>
class MyThread03 implements Callable<Integer> {
@Override
public Integer call() throws Exception {//有返回值//不是run,而是call
System.out.println("-----MyThread03");
return 200;
}
}
public class ThreadNew {
public static void main(String[] args) {
//方式1 实现
new MyThread01().start();
//方式2 继承Runnable
new Thread(new MyThread02()).start();//传入Runnable对象
//方式3 Callable
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread03());//传入Callable对象
new Thread(futureTask).start();
try {
Integer value = futureTask.get();//得到返回值
System.out.println(value);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程的优先级等级
MAX_PRIORITY: 10
MIN _PRIORITY: 1
NORM_PRIORITY: 5(默认)
getPriority() : 返回线程优先值
setPriority(int) : 改变线程的优先级
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
形象理解: 兔死狗烹,鸟尽弓藏
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不必等待守护线程执行完毕
如:后台记录操作日志,监控内存,垃圾回收等待
list不安全的原因是可能同一时间操作了同一个索引
for(){
new Thread(()->{list.add();}).start();
}
JDK中用Thread.State类(线程的状态)定义了线程的几种状态
要想实现多线程, 必须在主线程中创建新的线程对象。 java语言使用Thread类及其子类的对象来表示线程, 在它的一个完整的生命周期中通常要经历如下的五种状态:
NEW尚未启动
RUNNABLE执行中
BLOCKED
WAITING正在等待另一个线程执行动作
TIME_WAITING正在等待另一个线程执行动作达到指定等待时间
TERMINATED结束
//1. 同步代码块:
synchronized (同步监视器(锁)对象){//任何一个类的对象,都可以充当锁
...// 需要被同步的代码;
}
//2.同步方法: synchronized还可以放在方法声明中,表示整个方法为同步方法。
//继承的需要static,实现的不需要,因为传入的都是一个Runable对象,this指针指向同一个,此时同步监视器对象是当前类.class对象
//继承的方式不一定非得加static,比如多个用户共用一个银行账户。银行账户用共用一个就无需static,因为只有一个账户
public synchronized void show (String name){
...
}
同步机制中的锁
任意对象都可以作为同步锁。 所有对象都自动含有单一的锁(监视器) 。
同步方法的锁:静态方法(锁class对象)、非静态方法(锁this对象)
同步代码块:自己指定, 很多时候也是指定为this或类名.class
注意:
必须确保使用同一个资源的多个线程共用一把锁, 这个非常重要, 否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class) , 所有非静态方法共用同一把锁(this) , 同步代码块(指定需谨慎)
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、 return终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
//单例模式之懒汉式(线程安全)
class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){//把if写到外面效率高
synchronized(Singleton.class){
if(instance == null){
instance=new Singleton();
}
}
}
return instance;
}
}
public class SingletonTest{
public static void main(String[] args){
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
}
}
线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下面示例中,“爸爸线程”要抽烟,于是联合了“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。
public class TestThreadState {
public static void main(String[] args) {
System.out.println("爸爸和儿子买烟故事");
Thread father = new Thread(new FatherThread());
father.start();
}
}
class FatherThread implements Runnable {
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完了");
System.out.println("爸爸让儿子去买包红塔山");
Thread son = new Thread(new SonThread());//还没start
son.start();
System.out.println("爸爸等儿子买烟回来");
try {
son.join();//重点
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门去找儿子跑哪去了");
// 结束JVM。如果是0则表示正常结束;如果是非0则表示非正常结束
System.exit(1);
}
System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
}
}
class SonThread implements Runnable {
public void run() {
System.out.println("儿子出门去买烟");
System.out.println("儿子买烟需要10分钟");
try {
for (int i = 1; i <= 10; i++) {
System.out.println("第" + i + "分钟");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("儿子买烟回来了");
}
}
从JDK 5.0开始, java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义, 在实现线程安全的控制中,比较常用的是ReentrantLock, 可以显式加锁、释放锁
class A{
private final ReentrantLock lock = new ReenTrantLock();//可以传入参数fair:true
public void m(){
lock.lock();//加锁
// Condition condition2 = lock.newCondition()
try{
//保证线程安全的代码;
condition2.await();
}
finally{
// condition1.signal();
// conditin1.signalAll();
lock.unlock();//解锁
}
}
}
synchronized 与 Lock 的对比
\1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
\2. Lock只有代码块锁, synchronized有代码块锁和方法锁
\3. 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:Lock >>> 同步代码块(已经进入了方法体,分配了相应资源) >>> 同步方法(在方法体之外)
线程通信(等待唤醒机制):多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
多个线程共同执行意见任务,但是都需要一个相同的资源,但资源有限,如何协调使用。这种协调可以是竞争也可以是协作。
比如:线程A店铺用来生成包子的,线程B顾客用来吃包子的,但是只能同时存在一个包子,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
店铺生成完一个后,生产下一个时发现商品还在,所以就不能生产了,开始睡眠(wait)。顾客到了,①如果有包子,等顾客吃了后,通知(notify)店铺可以生产了。②如果没包子,就需要等待wait,等店铺生产完通知notify。
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
wait() 与 notify() 和 notifyAll()
代码演示:
包子资源类:
public class BaoZi {
String pier ;//皮
String xianer ;//馅
boolean flag = false ;//包子资源状态 是否存在
}
吃货线程类:
public class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
while(true){
synchronized (bz){
if(bz.flag == false){//没包子
try {
bz.wait();//释放bz
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");
bz.flag = false;
bz.notify();//唤醒其他拿包子的对象
}
}
}
}
包子铺线程类:
public class BaoZiPu extends Thread {
private BaoZi bz;
public BaoZiPu(String name,BaoZi bz){
super(name);
this.bz = bz;
}
@Override
public void run() {
int count = 0;
//造包子
while(true){
//同步
synchronized (bz){
if(bz.flag == true){//包子资源 存在
try {
bz.wait();//释放bz
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 没有包子 造包子
System.out.println("包子铺开始做包子");
if(count%2 == 0){
// 冰皮 五仁
bz.pier = "冰皮";
bz.xianer = "五仁";
}else{
// 薄皮 牛肉大葱
bz.pier = "薄皮";
bz.xianer = "牛肉大葱";
}
count++;
bz.flag=true;
System.out.println("包子造好了:"+bz.pier+bz.xianer);
System.out.println("吃货来吃吧");
//唤醒等待线程 (吃货)
bz.notify();
}
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
//等待唤醒案例
BaoZi bz = new BaoZi();
ChiHuo ch = new ChiHuo("吃货",bz);
BaoZiPu bzp = new BaoZiPu("包子铺",bz);
ch.start();
bzp.start();
}
}
/*
执行效果:
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
包子铺开始做包子
包子造好了:薄皮牛肉大葱
吃货来吃吧
吃货正在吃薄皮牛肉大葱包子
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
*/
可重入锁:
synchronized和Lock都是可重入锁。什么是可重入锁?
//synchronized的可重入锁
synchronized (this) {
System.out.println("第1次获取锁,这个锁是:" + this);
int index = 1;
while (true) {
synchronized (this) {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
}
if (index == 10) {
break;
}
//无需手动释放
}
}
//lock的可重入锁
try {
lock.lock();
System.out.println("第1次获取锁,这个锁是:" + lock);
int index = 1;
while (true) {
try {
lock.lock();
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
try {
Thread.sleep(new Random().nextInt(200));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (index == 10) {
break;
}
} finally {
lock.unlock();//需要手动释放
}
}
} finally {
lock.unlock();//最后一个释放
//只要退出函数的时候把全部锁都unlock就行,无所谓作用域。
}
ReentrantLock 和 synchronized 不一样,需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样。加锁和释放次数不一样导致死锁
在一个函数里随意重入,只要退出函数的时候把全部锁都unlock就行,无所谓作用域。
(1) synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看lock它的源代码,来看它是如何实现的。
(2) ReentrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)④ 性能已不是选择标准
ReentrantLock(boolean fair)
构造方法来制定是否是公平的。如果你想使用上述功能,那么选择ReentrantLock是一个不错的选择。
与使用Runnable相比, Callable功能更强大些
Future接口
我们刚刚在学习java内置线程池使用时,没有考虑线程计算的结果,但开发中,我们有时需要利用线程进行一些计算,然后获取这些计算的结果,而java中的Future接口就是专门用于描述异步计算结果的,我们可以通过Future 对象获取线程计算的结果;
Future 的常用方法如下:
boolean cancel(boolean mayInterruptIfRunning) // 试图取消对此任务的执行。返回取消成功还是取消失败的bool
V get() // 如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit) //如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled() //如果在任务正常完成前将其取消,则返回 true。
boolean isDone() //如果任务已完成,则返回 true。
package com.itheima.demo04;
import java.util.concurrent.*;
/*练习异步计算结果*/
public class FutureDemo {
public static void main(String[] args) throws Exception {
//1:获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//2:创建Callable类型的任务对象
Future<Integer> f = es.submit(new MyCall(1, 1));
//3:判断任务是否已经完成
//test1(f);
boolean b = f.cancel(true);
//System.out.println("取消任务执行的结果:"+b);
//Integer v = f.get(1, TimeUnit.SECONDS);//由于等待时间过短,任务来不及执行完成,会报异常
//System.out.println("任务执行的结果是:"+v);
}
//正常测试流程
private static void test1(Future<Integer> f) throws InterruptedException, ExecutionException {
boolean done = f.isDone();
System.out.println("第一次判断任务是否完成:"+done);
boolean cancelled = f.isCancelled();
System.out.println("第一次判断任务是否取消:"+cancelled);
Integer v = f.get();//一直等待任务的执行,直到完成为止
System.out.println("任务执行的结果是:"+v);
boolean done2 = f.isDone();
System.out.println("第二次判断任务是否完成:"+done2);
boolean cancelled2 = f.isCancelled();
System.out.println("第二次判断任务是否取消:"+cancelled2);
}
}
class MyCall implements Callable<Integer>{
private int a;
private int b;
//通过构造方法传递两个参数
public MyCall(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public Integer call() throws Exception {
String name = Thread.currentThread().getName();
System.out.println(name+"准备开始计算...");
Thread.sleep(2000);
System.out.println(name+"计算完成...");
return a+b;
}
}
背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。频繁创建线程和销毁线程需要时间。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
ThreadPoolExecutor
类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么),这里就不贴代码讲了,比较简单。
创建好的线程池用ExecutorService接口接收。
// 用给定的初始参数创建一个新的ThreadPoolExecutor。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor
构造函数重要参数分析
corePoolSize
: 常驻核心线程数。如果等于0,则任务执行完成后,没有任何请求进入时销毁线程池的线程;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。这个值的设置非常关键,设置过大会浪费资源,设置的过小会导致线程频繁地创建或销毁。maximumPoolSize
: 线程池能够容纳同时执行的最大线程数,但是他仅在队列满的时候才能用备选。当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。从上方的示例代码中第一处来看,必须大于或等于1。如果待执行的线程数大于此值,需要借助第5个参数的帮助。缓存在队列中。如果maximumPoolSize 与corePoolSize 相等,即是固定大小线程池。workQueue
: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。当请求的线程数大于maximumPoolSize时,线程进入BlockingQueue 阻塞队列。后续示例代码中使用的LinkedBlockingQueue 是单向链表,使用锁来控制入队和出对的原子性,两个锁分别控制元素的添加和获取,是一个生产消费模型队列。
keepAliveTime
:当线程池中的线程数量大于 corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime
才会被回收销毁;keepAliveTime 表示线程池中的线程空闲时间,当空闲时间达到KeepAliveTime 值时,线程被销毁,直到剩下corePoolSize 个线程为止,避免浪费内存和句柄资源。在默认情况下,当线程池的线程大于corePoolSize 时,keepAliveTime 才会起作用。但是ThreadPoolExecutor的allowCoreThreadTimeOut 变量设置为ture时,核心线程超时后也会被回收。unit
: keepAliveTime
:TimeUnit 表示时间单位。keepAliveTime 的时间单位通常是TimeUnit.SECONDS。threadFactory
:threadFactory 表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给这个factory增加组名前缀来实现的。在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的。handler
:饱和策略、执行拒绝策略的对象。当超过参数workQueue的任务缓存区上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护。友好的拒绝策略可以使如下三种:ThreadPoolExecutor
饱和策略定义:
如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任时,ThreadPoolTaskExecutor
定义一些策略:
ThreadPoolExecutor.AbortPolicy
(默认):抛出 RejectedExecutionException
异常来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy
:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。ThreadPoolExecutor.DiscardPolicy
: 不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy
: 此策略将丢弃最早未处理的任务请求。举个例子: Spring 通过 ThreadPoolTaskExecutor
或者我们直接通过 ThreadPoolExecutor
的构造函数创建线程池的时候,采用默认的拒绝策略。 对于可伸缩的应用程序,建议使用 ThreadPoolExecutor.CallerRunsPolicy
。当最大池被填满时,此策略为我们提供可伸缩队列。
1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)。
ExecutorService es = ThreadPoolExecutor(...);
//往线程池里提交任务
es.submit(Runnable);
Future<Integer> ftask = es.submit(Callable);//可以返回值
es.inVokeAny();
es.inVokeAll();
es.shutdown();//使当前未执行的线程继续执行,而不再添加新的任务Task,该方法不会阻塞。如果线程池内有任务,那么把这些任务执行完毕后,关闭线程池....
es.shutdownNow()//不再接受新的任务,并把任务队列中的任务直接移出掉,如果有正在执行的,尝试进行停止...
es.execute(Runnable);
《阿里巴巴java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险
Executors 返回线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
通过Executor 框架的工具类Executors来实现 我们可以创建三种类型的ThreadPoolExecutor:
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
//构造函数:
ExecutorService es = =Executors.newCachedThreadPool();//:创建一个可根据需要创建新线程的线程池
Executors.newCachedThreadPool(ThreadFactory);//:线程池中的所有线程都使用ThreadFactory来创建,这样线程无需手动启动,自动执行
Executors.newFixedThreadPool(n);// 创建一个可重用固定线程数的线程池
Executors.newFixedThreadPool(int,ThreadFactory);
Executors.newSingleThreadExecutor();// :创建一个只有一个线程的线程池
Executors.newSingleThreadExecutor(ThreadFactory);
Executors.newScheduledThreadPool(n);//:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class MyTest01{
main(){
test1();
test2();
}
private static void test1(){
//使用工程类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//ExecutorService es = Executors.newFixThreadPool(3);
//ExecutorService es = Executors.newSingleThreadExecutor();
//提交任务
for(int i=1;i<=10;i++){
es.submit(new MyRunnable1(i));
}
//3:关闭线程池,仅仅是不再接受新的任务,以前的任务还会继续执行
es.shutdown();//已经提交完了
//es.submit(new MyRunnable4(888));//不能再提交新的任务了
//3:立刻关闭线程池,如果线程池中还有缓存的任务,没有执行,则取消执行,并返回这些任务
//List<Runnable> list = es.shutdownNow();
//System.out.println(list);
}
private static void test2(){
//使用工程类获取线程池对象
ExecutorService es = Executors.newCachedThreadPool(new ThreadFacory(){
int n=1;
@Override
public Thread newThread(Runnable r ){return new Thread(r,"自定义线程名称"+n++);}//虽然叫新线程,但是用旧线程的任务也会通过这个函数,每个线程会重复利用
});/*
ExecutorService es = Executors.newFixThreadPool(3,new ThreadFacory(){
int n=1;
@Override
public Thread newThread(Runnable r ){return new Thread(r,"自定义线程名称"+n++);}
});
*//*
//ExecutorService es = Executors.newSingleThreadExecutor(new ThreadFacory(){
int n=1;
@Override
public Thread newThread(Runnable r ){return new Thread(r,"自定义线程名称"+n++);}
});
*/
//提交任务
for(int i=1;i<=10;i++){
es.submit(new MyRunnable1(i));
}
}
}
/*
任务类,包含一个任务编号,在任务中,打印出是哪一个线程正在执行任务
*/
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
//获取线程的名称,打印一句话
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务..."+id);
}
}
延迟执行与每时间段间隔执行
ScheduledExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力,
常用获取方式如下:
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务;
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建,且允许延迟运行或定期执行任务;
static ScheduledExecutorService newSingleThreadScheduledExecutor()
创建一个单线程执行程序,它允许在给定延迟后运行命令或者定期地执行。
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService常用方法如下:
// 提交任务
<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行callable。 //作用类似于submit
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
延迟时间单位是unit,数量是delay的时间后执行command。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
延迟时间单位是unit,数量是initialDelay的时间后,每间隔period时间重复执行一次command。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。
任务时间30s,间隔时间1min,scheduleAtFixedRate下次执行时间是1min后,scheduleWithFixedDelay是执行完再间隔1min,总共90s
package com.itheima.demo03;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
测试ScheduleExecutorService接口中延迟执行任务和重复执行任务的功能
*/
public class ScheduleExecutorServiceDemo01 {
public static void main(String[] args) {
//1:获取一个具备延迟执行任务的线程池对象
ScheduledExecutorService es = Executors.newScheduledThreadPool(3);
// ScheduledExecutorService es = Executors.newSingleThreadScheduledExecutor( new ThreadFactory() {
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
for (int i=1;i<=10;i++){
es.schedule(new MyRunnable(i),2, TimeUnit.SECONDS);
//原来是submit
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
// es.scheduleAtFixedRate(new MyRunnable2(1),1,2,TimeUnit.SECONDS);
//2:创建多个任务对象,提交任务,每个任务延迟2秒执行
//es.scheduleWithFixedDelay(new MyRunnable3(1),1,2,TimeUnit.SECONDS);
}
System.out.println("over");
}
}
class MyRunnable implements Runnable{
private int id;
public MyRunnable(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"执行了任务:"+id);
}
}
自定义线程池:
使用示例:
public class MyTask implements Runnable{
private int id;
public MyTask(int id) { this.id = id; }
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程:"+name+" 即将执行任务:"+id);
try {
Thread.sleep(200);//每个任务200ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+" 完成了任务:"+id);
}
@Override
public String toString() {
return "MyTask{" +"id=" + id + '}';
}
}
public class MyWorker extends Thread {//用一个工作台让全部任务开始执行
private String name;//保存线程的名字
private List<Runnable> tasks;
//利用构造方法,给成员变量赋值
public MyWorker(String name, List<Runnable> tasks) {//list
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有,就一直执行任务
while (tasks.size()>0){
Runnable r = tasks.remove(0);
r.run();//出队执行
}
}
}
public class MyThreadPool {
// 1:任务队列 集合 需要控制线程安全问题
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
//2:当前线程数量
private int num;
//3:核心线程数量
private int corePoolSize;
//4:最大线程数量
private int maxSize;
//5:任务队列的长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
//1:提交任务;
public void submit(Runnable r){
//判断当前集合中任务的数量,是否超出了最大任务数量
if(tasks.size()>=workSize){
System.out.println("任务:"+r+"被丢弃了...");
}else {
tasks.add(r);
//执行任务
execTask(r);
}
}
//2:执行任务;
private void execTask(Runnable r) {
//判断当前线程池中的线程总数量,是否超出了核心数,
if(num < corePoolSize){
new MyWorker("核心线程:"+num,tasks).start();
num++;
}else if(num < maxSize){
new MyWorker("非核心线程:"+num,tasks).start();
num++;
}else {
System.out.println("任务:"+r+" 被缓存了...");
}
}
}
public class MyTest {
public static void main(String[] args) {
//1:创建线程池类对象;
MyThreadPool pool = new MyThreadPool(4,8,40);
//2: 提交多个任务
for (int i = 0; i <100 ; i++) {
//3:创建任务对象,并提交给线程池
MyTask my = new MyTask(i);
pool.submit(my);
}
}
}
秒杀商品
案例介绍:
假如某网上商城推出活动,新上架10部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢,假如有20人同时参与了该活动,请使用线程池模拟这个场景,保证前10人秒杀成功,后10人秒杀失败;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:既然商品总数量是10个,那么我们可以在创建线程池的时候初始化线程数是10个及以下,设计线程池最大数量为10个;
2:当某个线程执行完任务之后,可以让其他秒杀的人继续使用该线程参与秒杀;
3:使用synchronized控制线程安全,防止出现错误数据;
代码步骤:
1:编写任务类,主要是送出手机给秒杀成功的客户;
2:编写主程序类,创建20个任务(模拟20个客户);
3:创建线程池对象并接收20个任务,开始执行任务;
(代码演示参考idea)
package com.itheima.demo05;
/*
任务类:
包含了商品数量,客户名称,送手机的行为;
*/
public class MyTask implements Runnable {
//设计一个变量,用于表示商品的数量
private static int id = 10;
//表示客户名称的变量
private String userName;
public MyTask(String userName) {
this.userName = userName;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在使用"+name+"参与秒杀任务...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(id>0){
System.out.println(userName+"使用"+name+"秒杀:"+id-- +"号商品成功啦!");
}else {
System.out.println(userName+"使用"+name+"秒杀失败啦!");
}
}
}
}
package com.itheima.demo05;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
主程序类,测试任务类
*/
public class MyTest {
public static void main(String[] args) {
//1:创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>(15));
//2:循环创建任务对象
for (int i = 1; i <=20 ; i++) {
MyTask myTask = new MyTask("客户"+i);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
取款业务
案例介绍:
设计一个程序,使用两个线程模拟在两个地点同时从一个账号中取钱,假如卡中一共有1000元,每个线程取800元,要求演示结果一个线程取款成功,剩余200元,另一个线程取款失败,余额不足;
要求:
1:使用线程池创建线程
2:解决线程安全问题
思路提示:
1:线程池可以利用Executors工厂类的静态方法,创建线程池对象;
2:解决线程安全问题可以使用synchronized方法控制取钱的操作
3:在取款前,先判断余额是否足够,且保证余额判断和取钱行为的原子性;
(代码演示参考idea)
package com.itheima.demo06;
public class MyTask implements Runnable {
//用户姓名
private String userName;
//取款金额
private double money;
//总金额
private static double total = 1000;
public MyTask(String userName, double money) {
this.userName = userName;
this.money = money;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(userName+"正在准备使用"+name+"取款:"+money+"元");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyTask.class){
if(total-money>0){
System.out.println(userName+"使用"+name+"取款:"+money+"元成功,余额:"+(total-money));
total-=money;
}else {
System.out.println(userName+"使用"+name+"取款:"+money+"元失败,余额:"+total);
}
}
}
}
package com.itheima.demo06;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class MyTest {
public static void main(String[] args) {
//1:创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
int id = 1;
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ATM" + id++);
}
});
//2:创建两个任务并提交
for (int i = 1; i <=2 ; i++) {
MyTask myTask = new MyTask("客户" + i, 800);
pool.submit(myTask);
}
//3:关闭线程池
pool.shutdown();
}
}
线程池的使用步骤可以归纳总结为五步 :
1:利用Executors工厂类的静态方法,创建线程池对象;
2:编写Runnable或Callable实现类的实例对象;
3:利用ExecutorService的submit方法或ScheduledExecutorService的schedule方 法提交并执行线程任务
4:如果有执行结果,则处理异步执行结果(Future)
5:调用shutdown()方法,关闭线程池
区别 | 进程 | 线程 |
---|---|---|
根本区别 | 作为资源分配的单位 | 调度和执行的单位 |
开销 | ||
所处环境 | ||
分配内存 | ||
包含关系 |
\1. 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
\2. 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
\3. 线程和进程最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
\4. 多进程: 在操作系统中能同时运行多个任务(程序)。
\5. 多线程: 在同一应用程序中有多个顺序流同时执行。
\6. 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
\7. 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
\8. 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
在 JDK1.2 之前,java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成==数据的不一致==。
要解决这个问题,就需要把变量声明为volatile,这就指示 JVM,这个变量是不稳定的,每次使用这个变量都到主存中进行读取。
说白了, volatile 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。
并发编程的三个重要特性
synchronized
可以保证代码片段的原子性。volatile
关键字可以保证共享变量的可见性。volatile
关键字可以禁止指令进行重排序优化。synchronized 关键字和 volatile 关键字的区别:
synchronized
关键字和 volatile
关键字是两个互补的存在,而不是对立的存在:
volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键字在javaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场景还是更多一些。
多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
什么是CAS:compare and swap
比如一个i=0(当前值E),我们想+1,那么结果是1(结果值V),当我们去写回的时候,检查当前新值N和印象中的值E是否一致。如果一致代表没有别的线程改,如果不一致代表被别的线程改过了。
如果被别的线程改过了,那么重新读心的当前值E,再继续进行修改操作。
如果一致的时候,也会存在ABA问题。
ABA:虽然一致,但是已经被其他若干个线程更改过又更改回了。(其他线程修改次数的值和原值相同)
如何感知ABA问题:把值加个版本号,读值的时候顺便把版本号也读走,比较的时候不仅比值,也比较版本值。
“死锁”指的是:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
因此, 某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。下面案例中,“化妆线程”需要同时拥有“镜子对象”、“口红对象”才能运行同步块。那么,实际运行时,“小丫的化妆线程”拥有了“镜子对象”,“大丫的化妆线程”拥有了“口红对象”,都在互相等待对方释放资源,才能化妆。这样,两个线程就形成了互相等待,无法继续运行的“死锁状态”。
class Lipstick {//口红类
}
class Mirror {//镜子类
}
class Makeup extends Thread {//化妆类继承了Thread类
int flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {//需要得到口红的“锁”;
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {//需要得到镜子的“锁”;
System.out.println(girl + "拿着镜子!");
}
}
} else {
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
}
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();//大丫的化妆线程;
m1.girl = "大丫";
m1.flag = 0;
Makeup m2 = new Makeup();//小丫的化妆线程;
m2.girl = "小丫";
m2.flag = 1;
m1.start();
m2.start();
}
}
死锁的解决方法
死锁是由于“同步块需要同时持有多个对象锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁。
class Lipstick {//口红类
}
class Mirror {//镜子类
}
class Makeup extends Thread {//化妆类继承了Thread类
int flag;
String girl;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup() {
if (flag == 0) {
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
}
} else {
synchronized (mirror) {
System.out.println(girl + "拿着镜子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick) {
System.out.println(girl + "拿着口红!");
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup();// 大丫的化妆线程;
m1.girl = "大丫";
m1.flag = 0;
Makeup m2 = new Makeup();// 小丫的化妆线程;
m2.girl = "小丫";
m2.flag = 1;
m1.start();
m2.start();
}
}
https:///hancoder/article/details/105740321