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

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

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

线程同步为了能够解决刚才出现的问题,我们可以考虑使用线程同步,让多个线程实现先后依次访问共享资源。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。

创作不易,给个三连

    推荐阅读
  • 恋活sunshine人物卡潘希洛奈分享 恋活sunshine人物性格

    恋活2潘希洛奈怎么捏?潘希洛奈是王之袭击中的人物之一,今天小编给大家带来恋活sunshine人物卡潘希洛奈分享,感兴趣的小伙伴快来看一下吧。

  • 张星特广州巡演地点(还记得创4的张星特吗)

    而在各路神仙当中,赢得最大的就是张星特和米卡了。(今天主要说张星特!)张星特一开口,橘真的就是汗毛竖立的程度,什么叫开口跪!)总之,就仅仅是这一首歌,就让橘彻底折服在了张星特的vocal实力中。唱的真好橘已经说累了,永远可以相信张星特的唱功。但是由于疫情原因,目前只定下了6月19号的首站杭州,可就连这也不能百分百确定能够如期举办……就是说疫情真的影响了太多,希望病毒赶紧退!弟弟的巡演橘就大大期待一个吧!

  • 足球术语大全和解释英语(足球英语球场位置中英文对照及基础知识)

    守门员Goalkeeper,GK在球场上,最后的一道防线自然是各自球队的守门员,英文为Goalkeeper。守门员的职责是使用身体去阻挡射门,阻止对方的进球尝试。规则允许守门员出现在球场的任何位置,但是在本方禁区外的位置,守门员不得以手持球。大部分守门员会佩带手套以加强对球的控制并防止手指,手掌或手腕关节在扑救时受伤。守门员在点球大战中扑救点球数量最多的纪录由罗马尼亚人赫尔穆特.杜卡达姆保持。

  • 钢材的理论重量计算(各类钢材理论重量计算公式)

    今天带来的是“各类钢材的理论重量计算公式”,内容方面还算是比较全面的了,不过知识点还是比较密集,单靠个人记忆应该是比较困难的,建议大家收藏起来,当做工具手册来使用。

  • 怎样做排骨好吃又简单(排骨的做法)

    怎样做排骨好吃又简单焖排骨:锅中倒入水、料酒和排骨,捞出血沫,捞出排骨备用。起锅烧油放入生姜和大蒜,倒入排骨翻炒,倒入酱油加水盖上锅盖焖5分钟。锅中烧水,水开后放入排骨,大火煮至出现浮沫,排骨变白后,用漏勺捞出。汤锅洗净后,重新加入排骨,倒入开水,水量以没过肉表面为准。汤汁沸腾后,转小火,煮40分钟后加入豆角、玉米、胡萝卜,继续煮10分钟,根据个人口味调入适量盐即可。

  • 高脂血症吃什么食物好 高脂血症吃什么食物好吃多少克

    若是胆固醇的含量增高,或是甘油三酯的含量增高,或两者都增高,则统称其为高脂血症。早饭前空腹食用。适用于视物模糊、冠心病、高血压病、高脂血症等病症。如何预防高脂血症1、控制饮食主要是减少动物脂肪的摄入量,多食用如香菇等菌类,或是水果等,这些食物都有助于降低血脂。因为气候的急剧变化,不仅易于使高脂血症加重并发高血压病,还可能诱发脑卒中。

  • 10招让你英文写作更棒(一键提升英语写作)

    诸多烦恼,一个杀手锏解决——秘塔写作猫功能!很多用户期待已久的功能已正式上线!除了修正基础语句错误,秘塔写作猫英文改写功能还可以对单一、不准确的用词自动进行同义替换、节省大量思考替换时间。总之,使用秘塔写作猫功能,可一键修正基础错误、调整替换同义高级词汇,让整体表达更准确、地道,给你的写作增加亮点。工作学习中的各类英语写作需求,秘塔写作猫都能助你完美应对。

  • 卤肉最主要的香料有哪些(用了30年卤肉老方子)

    卤水老师傅说,在做卤肉时香料是关键,合理的搭配起来,才能使卤出来的食材更香更入味。卤肉必用的这10种香料,就是:八角、桂皮、甘草,香叶、小茴香,白芷,豆蔻,甘草,草果,丁香。第十种:甘草甘草是一味中药材,味道甘甜香味清香,它放在卤肉里面的作用主要就是增加回甜味,让肉的口味更加的丰富多样,用量为每500克肉放1克。

  • 交易买卖是什么意思(交易买卖具体是什么意思)

    我们一起去了解并探讨一下这个问题吧!交易买卖是什么意思交易买卖是指当事人双方在一定期限内,卖方定期或者不定期地供给买方某种物品,买方按照一定标准支付价款的买卖,双方之间的每次交易都是有关联的。交易是指双方以货币及服务为媒介的价值的交换。交易,又称贸易、交换,是买卖双方对有价物品及服务进行互通有无的行为。它可以是以货币为交易媒介的一种过程,也可以是以物易物,例如一只黄牛交换三只猪。

  • 杨梅的功效和作用(杨梅的功效和作用是什么)

    杨梅的功效和作用生津止渴,对于暑热伤津所导致的口渴口干、声音嘶哑、咽喉肿痛的人,如果适当吃杨梅有很好的生发口中津液、缓解症状的作用。健脾开胃、增进食欲,因为杨梅当中含有丰富的有机酸,而有机酸能够很好的刺激消化道中消化液的分泌,有很好的健脾开胃、增进食欲的作用。为机体补充丰富的维生素C,而维生素C能够很好的刺激众多免疫物质的生成,适吃杨梅补充维生素C,在提高机体免疫力方面的作用也非常明显。