高考考试网
当前位置: 首页 高考资讯

java多线程编程的用处(Java中的多线程如何理解)

时间:2023-08-09 作者: 小编 阅读量: 2 栏目名: 高考资讯

线程同步为了能够解决刚才出现的问题,我们可以考虑使用线程同步,让多个线程实现先后依次访问共享资源。synchronized{//喝水是对于两个线程的不变对象if{System.out.println(thread.getName()"已经成功获得了这瓶水!

目录

线程池处理Runnable任务

线程池处理Callable任务

Executors的工具类构建线程池对象

引言

通过前面的学习,我们已经学会了线程是如何创建的以及线程的常用方法,接下来呢,我们将要深入性了解线程中的知识,主要是线程安全,线程同步,线程池三个知识点。我相信大家通过这节课的简单地学习,就可以大概地掌握了线程吧!好了,废话不多说,我们开始今天的学习吧!

线程安全

首先我们应该了解的是什么是“ 线程安全 ”问题呢?通俗易懂的讲的话,那就是“假设在某地有一瓶水,石原里美和工藤静香都很渴,想要去喝这瓶水,然而当这两个线程同时启动的时候,二人都会去拿这瓶水,并且同时判断这瓶水是否还在?在这瓶水未被取走之前,二人的判断都是true,因此二人都能够取到这瓶水,可是明明只有一瓶水,却可以让两个人都取到水,这很明显是与现实生活中的情况是不符合的。”这就是我们需要处理的“线程安全”问题。

线程安全问题出现的原因:

  • 存在多线程并发
  • 同时访问共享资源
  • 存在修改共享资源
实战模拟

问题描述:

仍然是前面的例子,现有两人需要喝水,分别是石原里美和工藤静香,二者共享同一瓶水,若对方喝掉这瓶水,则另一方则没有水可以喝。

具体操作:

1、提供一个Account类并创建它,作为二人的共享水资源账户;

public class Account {private int num;//代表水的数量public Account() {}public Account(int num) {this.num = num;}public int getNum() {return num;}public void setNum(int num) {this.num = num;}}

2、定义一个线程类,并且该线程可以处理Account对象;

public class ThreadAccount extends Thread{private Account account;public ThreadAccount() {}public ThreadAccount(Account account) {this.account = account;}public Account getAccount() {return account;}public void setAccount(Account account) {this.account = account;}@Overridepublic void run() {Thread thread = Thread.currentThread();if(account.getNum()>0){System.out.println(thread.getName() "已经成功获得了这瓶水!");account.setNum(account.getNum()-1);System.out.println("此时还剩下" account.getNum() "瓶水");}else {System.out.println("水资源不够,已经无法取出");}}}

3、创建两个线程,并传入同一个Account对象;

public static void main(String[] args) throws Exception {Account account = new Account(1);ThreadAccount threadAccount1 = new ThreadAccount(account);ThreadAccount threadAccount2 = new ThreadAccount(account);Thread thread1 = new Thread(threadAccount1,"石原里美");Thread thread2 = new Thread(threadAccount1,"工藤静香");thread1.start();thread2.start();}

4、启动两个线程,同时获取同一瓶水。

通过运行结果,我们可以清晰的看出来存在很大的问题,当第一个人石原里美获得这瓶水的时候,水资源账户中已经没有水了,所以工藤静香是不能够取到水资源的,然而工藤静香仍然获得了水,且剩下了-1瓶水,这很明显是不符合现实情况的,那么我们该如何解决呢?

线程同步

为了能够解决刚才出现的问题,我们可以考虑使用线程同步,让多个线程实现先后依次访问共享资源。其核心思想是: 加锁 ,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。通俗易懂的讲就是:“假如两个线程同时开始访问共享资源,在访问之前,谁先拿到钥匙打开这把锁,谁才能访问该共享资源。而另一个线程则只能在共享资源外等待钥匙空下来。”接下来介绍几种方式来解决该问题。

方式一:同步代码块

作用: 把出现线程安全问题的核心代码给上锁

原理:每次只能一个线程进入,执行完毕后自动解锁,其他进程才可以进来执行

synchronized (同步锁对象){操作共享资源的代码(核心代码)}

锁对象要求:理论上,锁对象只要对于当前同时执行的线程来说是 同一个对象 即可。

synchronized ("喝水") {//喝水是对于两个线程的不变对象if(account.getNum()>0){System.out.println(thread.getName() "已经成功获得了这瓶水!");account.setNum(account.getNum()-1);System.out.println("此时还剩下" account.getNum() "瓶水");}else {System.out.println("水资源不够,已经无法取出");}}

具体的操作就是将之前的run()方法中的核心代码加锁,即可实现多个线程依次访问共享资源

其中需要注意的一点是,同步锁对象等同于打开共享资源的一把钥匙,所以应该对多个线程是同一个对象,并且要规范命名,若同步锁对象命名相同,则会影响另一组线程对共享资源的访问。对于实例方法建议使用this作为锁对象;对于静态方法建议使用字节码(类名.class)对象作为锁对象。

方式二:同步方法

作用: 把出现线程问题的核心方法给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

修饰符 synchronized 返回值类型 方法名称(形参列表){操作共享资源的代码}

与方式一的不同之处在于,前者是封装共享资源的核心代码,后者则是封装整个方法

public synchronized void LockWater(){if(account.getNum()>0){System.out.println(thread.getName() "已经成功获得了这瓶水!");account.setNum(account.getNum()-1);System.out.println("此时还剩下" account.getNum() "瓶水");}else {System.out.println("水资源不够,已经无法取出");}}

如果方法是实例方法:同步方法默认用this作为锁对象,但是代码要高度面向对象;

如果方法是静态方法:同步方法默认用类名.class作为锁对象。

方式三:Lock锁
  • 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象lock,更加灵活、方便
  • Lock实现提供比使用 synchronized 方法和语句可以获得更广泛的锁操作
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建锁对象

方法名称

说明

public ReentrantLock( )

获得Lock锁的实现类对象

void lock( )

获得锁

void unlock( )

释放锁

//在使用之前先定义一个锁对象private Lock lock = new ReentrantLock();//定义过锁对象之后,即可以调用其APIlock.lock();//共享资源代码lock.unlock();

线程池

线程池是一个可以复用线程的技术;如果不使用线程池的话,用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销很大,这样会严重影响系统性能。

线程池的实现原理:

在创建线程池时,设定该线程池固定存在N个 核心线程 用于处理任务,另外会有一个 任务队列 提供给任务排队等待,在任务队列中的前N个任务则是交给核心线程去处理,在没有空余线程的时候,其余任务则在任务队列中等待。

如何得到线程池对象?

  • 方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象;
  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程对象。

ThreadPoolExecutor构造器的参数说明

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

参数一:指定线程池的线程数量(核心线程): corePoolSize (不能小于0)

参数二:指定线程池可支持的最大线程数: maximumPoolSize (最大数量>=核心线程)

参数三:指定临时线程的最大存活时间: keepAliveTime (不能小于0)

参数四:指定存活时间的单位(秒、分、时、天): unit (时间单位)

参数五:指定任务队列: workQueue (不能为null)

参数六:指定用哪个线程工厂创建线程: threadFactory (不能为null)

参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler (不能为null)

为了更好的去理解多线程:我们可以假设 核心线程 数量为3个, 最大线程 数为5个,那么该线程池可以创建的 临时线程 数为5-3=2个线程,临时线程的最大存活时间是指其被创建之后不处理任务之后的存活时间,时间单位结合实际即可, 任务队列 设置为5个,参数六字面意思理解即可,参数七则是规定线程池不能再接收任务的时候如何处理。接下来通过这两个问题加深理解。

临时线程什么时候创建?

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
  • (此时3个核心线程在处理任务,并且任务队列中已经有5个任务在等待了,然而仍然有任务过来,此时即可开始创建临时线程处理任务)

什么时候会开始拒绝任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
  • (当3个核心线程和2个临时线程都在处理任务,且任务队列满的情况下,将会开始拒绝接收任务,由参数七决定该如何处理,由此我们也可以得出该线程池任务处理的最大数量为:3 2 5=10,即核心线程 临时线程 任务队列)
线程池处理Runnable任务

ThreadPoolExecutor创建线程池对象:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

ExecutorService常用方法

常用方法

说明

void executor(Runnable command)

执行任务/命令,没有返回值。一般用来执行Runnable任务

Future<T>submit(Callable<T> task)

执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务

void shutdown( )

等任务执行完毕后关闭线程池

List<Runnable shutdownNow>

立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

参数七新任务拒绝策略

策略

说明

ThreadPoolExecutor.AbortPolicy

丢弃任务,并抛出RejectExecutionException异常,默认的策略

ThreadPoolExecutor.DiscardPolicy

丢弃任务,但是不抛出异常,不推荐

ThreadPoolExecutor.DiscardOldPolicy

抛弃队列中等待最久的任务,然后把当前任务加到队列中

ThreadPoolExecutor.CallerRunsPolicy

由主线程负责调用任务的run()方法从而绕过线程池直接执行

//main方法public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2,4,10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());Runnable target = new MyThreadRunnable();//核心线程 任务队列=5,所以5个以内不需要创建临时线程pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);//当任务数量达到5个的时候,接收任务的时候就需要创建临时线程pool.execute(target);pool.execute(target);//当超过7个线程时,再次接收任务时,则会拒绝pool.execute(target);}//实现Runnable接口public class MyThreadRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName() "正在处理任务");Thread.sleep(10000000);//避免线程快速处理任务,无法达到实验效果}}//输出结果://pool-1-thread-2正在处理任务//pool-1-thread-4正在处理任务//pool-1-thread-3正在处理任务//pool-1-thread-1正在处理任务

线程池处理Callable任务

Future<T>submit(Callable<T> task)

执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务

//main方法public class MoreThread {public static void main(String[] args) throws Exception{ThreadPoolExecutor pool = new ThreadPoolExecutor(2,4,10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());Future<String> f1 = pool.submit(new MyThreadCallable());Future<String> f2 = pool.submit(new MyThreadCallable());Future<String> f3 = pool.submit(new MyThreadCallable());Future<String> f4 = pool.submit(new MyThreadCallable());System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}}//线程实现Callable接口public class MyThreadCallable implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName() "正在工作中~";}}//输出结果:仅由两个核心线程完成即可//pool-1-thread-1正在工作中~//pool-1-thread-2正在工作中~//pool-1-thread-2正在工作中~//pool-1-thread-2正在工作中~

Executors的工具类构建线程池对象

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

方法名称

说明

public static ExecutorsService newCachedThreadPool( )

线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。

public static ExecutorsService newFixedThreadPool(int nThread)

创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。

public static ExecutorsService newSingleThreadExecutor( )

创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。

public static ScheduledExecutorsService newScheduledThreadPool(int corePoolSize)

创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象。

ExecutorService pool1 = Executors.newFixedThreadPool(5);pool.execute(new MyThreadRunnable());pool.execute(new MyThreadRunnable());pool.execute(new MyThreadRunnable());

虽然Executors使用起来会很方便,但是仍然是存在风险的,因此还是推荐前面使用的线程池创建方式。

(1)FixedThreadPool和SingleThreadExecutor:

允许的请求队列长度为Integer.Max_VALUE,可能会堆积大量的请求,从而导致oom。

(2)CachedThreadPool和ScheduledThreadPool:

允许的创建线程数量为Integer.Max.VALUE,可能会创建大量的线程,从而导致oom。

创作不易,给个三连

    推荐阅读
  • 樟茶鸭如何加热(樟茶鸭怎样加热)

    接下来我们就一起去了解一下吧!樟茶鸭如何加热樟茶鸭放在蒸锅里,加水之后盖上锅盖。中火加热,静静等待,看到锅中起烟后改为微火,熏制5分钟后关火。此时盖子先不要敞开,再焖3分钟,3分钟之后打开盖子放掉烟气,这样的樟茶鸭就加热好了。樟茶鸭属熏鸭的一种,制作考究,要求严格,成菜色泽金红,外酥里嫩,带有樟木和茶叶的特殊香味,是四川省经典的传统名肴之一,属于川菜。

  • 每周调研汇总表(蜻蜓点水式调研)

    蜻蜓点水式调研  “蜻蜓点水”  ——引自2021年9月1日在2021年秋季学期中央党校(国家行政学院)中青年干部培训班开班式上的讲话  ——典出唐•杜甫《曲江》  讲话原文:坚持从实际出发,前提是深入实际、了解实。

  • 2021珠海平沙镇第二中心幼儿园电脑派位结果查询

    2021珠海平沙镇第二中心幼儿园电脑派位结果(拟录取名单)序号年级报名号姓名随机号备注1小班37491035谭羽晴10687942拟录取2小班37491061黄子浩10724539拟录取3小班37491102曹惠10984275拟录取4小班37491249黄勋12368950拟录取5小班37491116杨惠心13096827拟录取6小班37491201周婉瑜13974862拟录取7小班374910

  • 海关isps代表什么意思(海关又熔断4家印度企业)

    继6月30日海关总署“熔断”8家来自印度的水产品生产企业,创单日熔断数量记录后,7月3日海关总署周末不“打烊”,再次“熔断”4家印度企业。这是书面商务英语中惯用的缩写,用于表示尊敬,是礼貌用语。舌鳎鱼因体侧扁,呈舌状而得名,一般体长25~40厘米、体重500~1500克。在我国各近海渔场均可捕获,海洋岛、石岛渔场为主要产地。宽体舌鳎为海洋名贵经济鱼类之一。红烧舌鳎鱼是有名的菜肴之一。

  • 火日立是什么字(字内容介绍)

    下面希望有你要的答案,我们一起来看看吧!火日立是什么字煜,基本释义:照耀,火焰。从火,昱(yù)声。日以煜乎昼,月以煜乎夜。——《太玄·元告》〈形〉光耀;明亮。

  • 煤渣弄成什么状态来养花(不起眼的蜂窝煤渣也能用来养花)

    小小的蜂窝煤也能用来种花,花花真是长见识了,但是现在蜂窝煤可是不好找了,要是找不到蜂窝煤的朋友们最好还是用别的办法来养花吧。

  • 以银离子为阳离子的白色配合物(让分子筛发光)

    基于上述原因,人们依然在尝试发展新的合成手段来对金属团簇,特别是低于10个原子的金属团簇,进行可控的合成以及相应的光学性质调控。分子筛是一类具有规则孔道结构的无机材料。通过调控Ag的交换量,可以控制分子筛中Ag物种的密度,从而得到不同尺寸的Ag团簇。通过EPR和EXAFS,作者发现在LTA分子筛中,Ag主要是Ag6团簇;而在FAU分子筛中主要是Ag4团簇。

  • 快速缓解腰痛的方法(快速缓解腰痛的方法视频)

    右侧腰痛按左手,左边腰痛按右手,若两侧腰痛双手均按。可用于缓解急性腰痛。敲打太溪穴太息穴位于足少阴原穴,位于足内侧,内踝后方与脚跟骨筋腱之间的凹陷处。经常敲打太溪穴再加上用艾灸的方法可以治疗肾虚所导致的慢性腰痛。适用于治疗慢性腰痛,常因阴雨天或是受寒后腰痛出现加剧等症状。注意本方法对医者操作要求严格,因此必须有专业医生方可进行。

  • 酸白菜怎么做(如何制作酸白菜)

    接下来我们就一起去研究一下吧!酸白菜怎么做白菜洗净切成块,白菜帮和菜叶分开装在两个碗中,干辣椒剪成小段,大葱切段,蒜切片。碗中放入白糖、鸡精、盐、醋、生抽,加少量水调和均匀备用。锅中加入适量油,烧热后放入干辣椒和葱段,炒出香味后放入白菜帮,翻炒至白菜帮变软后加入菜叶。继续炒至白菜八成熟时,加入调好的料汁,加入蒜片,翻炒入味,最后加入少许水淀粉收汁,美味酸白菜就做好了。

  • 眼尾上扬是什么眼(眼尾上扬的眼叫什么)

    接下来我们就一起去了解一下吧!眼尾上扬是什么眼丹凤眼每个人的眼型可能不完全一样,常见眼形可能有杏眼,荔枝眼,丹凤眼,瑞凤眼,眯缝眼,三角眼,桃花眼,柳叶眼等大体分类。眼尾上扬大体属于丹凤眼范畴,内眦狭长,眼尾高,这种眼睛神气逼人,极具美感。总结这类人性格可能刚直,也有的柔和,不能通过眼睛评判一个人的好坏,不应该歧视特殊对待,增进人与人之间的沟通交流,提高内在美。