title: Java线程池实现音频播放
date: 2019-05-06 21:00:00

Java线程池实现音频播放

参考文章:

Java并发编程:线程池的使用

必须要理清的Java线程池

关于什么是多线程?什么是线程池可以看看参考文章,我觉得大佬们描述的,比较全面吧,反正我不太想码字......下面我写一下我为啥要用Java线程池实现音乐播放吧,先说一下背景:我需要在一个特定的条件,例如你触动鼠标或按键时,播放音频。这个就是我想实现的效果,下面我贴上我一开始用线程实现的代码

package com.gzcodestudio.soundeffects.event;
​
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
​
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
​
/*** @author hkq* @date 2019/05/01*/
public class FirstMusic extends Thread {private InputStream url = null;private AudioStream audioStream = null;
​private boolean[] on_off = null;
​public FirstMusic(boolean[] on_off) {this.on_off = on_off;}
​
​@Overridepublic void run() {AudioPlayer.player.stop ( audioStream );
​
​try {
​url = new FileInputStream ( FirstMusic.class.getClassLoader ( ).getResource ( "a1.wav" ).getPath ( ) );// 创建音频流对象audioStream = new AudioStream ( url );// 使用音频播放器播放声音AudioPlayer.player.start ( audioStream );
​} catch (IOException e) {e.printStackTrace ( );} finally {//如果为空,关闭资源try {if (url == null) {url.close ( );}
​if (audioStream == null) {audioStream.close ( );}
​
​} catch (IOException e) {e.printStackTrace ( );}}
​
​}
}
​

代码比较蹧啊,单纯的写了一个线程实现音频播放,这个AudioStream类就是实现音频播放的音频流,调用的时候需要创建一个新的音频流对象接收它......这个线程的调用方式就是在其他监听条件里创建一个新的线程对象接收它,并运行它的strat()方法。代码如下:

 //开启音频播放线程boolean[] on_off = {true};FirstMusic music = new FirstMusic ( on_off );music.start ( );

效果是达到我所想的效果了,但是我总觉得它是在每一次监听条件里创建一个新的线程对象运行这个线程...这样子如果只是运行一次,问题就不大,但如果我是做了鼠标监听,玩个枪战游戏,不停的点击鼠标,那它会创建多少线程对象啊......资源就浪费掉了,严重会造成电脑死机。当我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在Java中可以通过线程池来达到这样的效果。下面我写一下我的实现过程吧。首先你得认真看看文章推荐的文章参考,理解一下java中的ThreadPoolExecutor类,java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。看完这个类诸多的方法了吗?晕吗?还是想试试码码代码了?对不起,本次需要用到的是ThreadFactory,ScheduledThreadPoolExecutor...

ThreadFactory是一个线程工厂。用来创建线程。这里为什么要使用线程工厂呢?其实就是为了统一在创建线程时设置一些参数,如:是否守护线程。线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。它首先是一个接口类,而且方法只有一个。就是创建一个线程。

ScheduledThreadPoolExecutor继承ThreadPoolExecutor来重用线程池的功能,它的实现方式如下:

  • 将任务封装成ScheduledFutureTask对象,ScheduledFutureTask基于相对时间,不受系统时间的改变所影响;

  • ScheduledFutureTask实现了java.lang.Comparable接口和java.util.concurrent.Delayed接口,所以有两个重要的方法:compareTo和getDelay。compareTo方法用于比较任务之间的优先级关系,如果距离下次执行的时间间隔较短,则优先级高;getDelay方法用于返回距离下次任务执行时间的时间间隔;

  • ScheduledThreadPoolExecutor定义了一个DelayedWorkQueue,它是一个有序队列,会通过每个任务按照距离下次执行时间间隔的大小来排序;

  • ScheduledFutureTask继承自FutureTask,可以通过返回Future对象来获取执行的结果。

ScheduledThreadPoolExecutor可以用来在给定延时后执行异步任务或者周期性执行任务,相对于任务调度的Timer来说,其功能更加强大,Timer只能使用一个后台线程执行任务,而ScheduledThreadPoolExecutor则可以通过构造函数来指定后台线程的个数。

懂了吗?你百度吧,我也说不清楚!可以参考一下以下文章

线程池创建

线程池之ScheduledThreadPoolExecutor

ThreadFactory工厂模式

我看完上述文章,还是一知半解的情况下啊!贴上以下代码:

创建线程池:

package com.gzcodestudio.soundeffects.util;
​
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
​
import java.util.concurrent.ScheduledThreadPoolExecutor;
​
/*** @author DHB*/
public abstract class ThreadUtil {
​
​/*** 创建线程池** @param corePoolSize 线程池大小* @return 线程池*/public static ScheduledThreadPoolExecutor newExecutorService(int corePoolSize, String name) {return new ScheduledThreadPoolExecutor(corePoolSize,new BasicThreadFactory.Builder().namingPattern(name + "-%d").daemon(true).build());}
​
}
​

只解析一下new BasicThreadFactory.Builder().namingPattern(name + "-%d").daemon(true).build());这行代码,Factory结尾的是用了工厂模式,工厂模式就是用来生成对象的,相当于一个构造器,内部会帮你构造出对象来,Builder又是建造者模式。线程池创建出来了,下面代码是我使用线程池实现音频播放的FirstMusicThread类的代码:

FirstMusicThread:

package com.gzcodestudio.soundeffects.event;
​
​
import com.gzcodestudio.soundeffects.util.ThreadUtil;
import sun.audio.AudioPlayer;
import sun.audio.AudioStream;
​
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ScheduledThreadPoolExecutor;
​
​
/*** 音效播放监听** @author hkq*/
public class FirstMusicThread implements MusicListener {
​/*** 停止任务*/public static final int STOP = 0;/*** 任务运行中*/public static final int RUNNING = 1;/*** 重新运行*/public static final int RESUME = 2;private ScheduledThreadPoolExecutor poolExecutor;private volatile int resume = RESUME;private volatile boolean isRun = true;private Runnable runnable;private InputStream url;private AudioStream audioStream;private Object callback;
​/*** 初始化*/@Overridepublic void init() {url = null;audioStream = null;poolExecutor = ThreadUtil.newExecutorService ( 1 , this.getClass ( ).getName ( ) );}
​/*** 监听*/@Overridepublic void listening() {if (runnable == null) {newTask ( );poolExecutor.submit ( runnable );} else {throw new IllegalArgumentException ( "listening() 仅允许执行一次" );}}
​/*** 监听** @param callbacks 回调*/@Overridepublic void callback(Object callback) {this.callback = callback;}
​
​/*** 创建播放线程*/public void newTask() {runnable = () -> {
​while (isRun) {if (resume == RESUME) {// 播放
​try {
​url = new FileInputStream ( FirstMusicThread.class.getClassLoader ( ).getResource ( "a1.wav" ).getPath ( ) );// 创建音频流对象audioStream = new AudioStream ( url );// 使用音频播放器播放声音AudioPlayer.player.start ( audioStream );
​} catch (FileNotFoundException e) {e.printStackTrace ( );} catch (IOException e) {e.printStackTrace ( );} finally {//如果资源为空,关闭资源try {if (url == null) {url.close ( );}if (audioStream == null) {audioStream.close ( );}
​} catch (IOException e) {e.printStackTrace ( );}
​}// 复位resume = RUNNING;
​}if (resume == RUNNING) {/** 处理音频事件* https://stackoverflow.com/questions/10684631/key-listener-written-in-java-jna-cannot-stop-the-thread* PeekMessage 非阻塞* GetMessage  阻塞* */
​}}};}
​/*** 取消监听*/@Overridepublic void unListening() {resume = STOP;}
​/*** 恢复监听*/@Overridepublic void resume() {resume = RESUME;}
​/*** 结束*/@Overridepublic void destroy() {isRun = false;unListening ( );poolExecutor.shutdown ( );
​}
}

上述代码是我复用线程池写的代码,代码里我用上了一些接口,目的是更好地了解当前音频播放的状态,所以我下面就是我的音频播放监听接口的代码,新创建一个接口类MusicListener

MusicListener:

package com.gzcodestudio.soundeffects.event;
​
/*** 音效监听接口* @author hkq*/
public interface MusicListener<T> {/*** 初始化*/void init();
​/*** 监听*/void listening();
​/*** 监听** @param callback 回调*/void callback(T callback);
​/*** 取消监听*/void unListening();
​/*** 恢复监听*/void resume();
​/*** 结束*/void destroy();
}
​
​
​

下面就调用复用线程池类FirstMusicThread的方法实现音频播放效果,本来我是打算使用以下代码调用的,结果发现不行啊,算了,代码贴上来,给大佬们看看

Test:

package com.gzcodestudio.soundeffects.event;
​
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
​
@SuppressWarnings("all")
public class Test {
​private FirstMusicThread firstMusicThread;
​
​@Beforepublic void before() {firstMusicThread = new FirstMusicThread ( );}
​@Testpublic void listening() {firstMusicThread.init ( );firstMusicThread.callback(types,callback->{switch(types){case FirstMusicThread.RUNNING://开启音频播放firstMusicThread.unListening();break;case  FirstMusicThread.STOP:firstMusicThread.resume();break;default:}});firstMusicThread.listening ( );new Thread ( () -> {while (true) {try {Thread.sleep ( 5000 );} catch (InterruptedException e) {e.printStackTrace ( );}firstMusicThread.resume ( );System.out.println ( "测试自动恢复" );}} ).start ( );
​while (true) {
​}}
​
​@Afterpublic void after() {
​firstMusicThread.unListening ( );firstMusicThread.destroy ( );}
}

使用上面Test类这个代码调用音频播放就要再创建一个MusicCallback接口类,实现监听回调,代码如下:

MusicCallback:

package com.gzcodestudio.soundeffects.event;
/*** 音效监听回调** @author dhb*/
@FunctionalInterface
public interface MusicCallback<T> {
​/*** 回调** @param type 键盘事件类型* @param t    类型*/void callback(int types, T t);
​
}
​

我断点调了老半天,还是没能解决这个问题,所以我就没有用那个音效监听回调的MusicCallback类,而是直接在测试类里调用,效果达到我的预期效果,直接在需要使用的位置添加:

                    //取消监听firstMusicThread.unListening();//开启音频播放监听firstMusicThread.listening();//恢复音频播放监听firstMusicThread.resume();//结束监听firstMusicThread.destroy();

学无止境啊!!!

Java线程池实现音频播放相关推荐

  1. 四种Java线程池用法解析

    四种Java线程池用法解析 本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 ...

  2. 面试必问---Java线程池8大拒绝策略

    前言 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了多线程代码的开发.而不论你用Fix ...

  3. Java线程池使用与原理

    线程池是什么? 我们可以利用java很容易创建一个新线程,同时操作系统创建一个线程也是一笔不小的开销.所以基于线程的复用,就提出了线程池的概念,我们使用线程池创建出若干个线程,执行完一个任务后,该线程 ...

  4. Java线程池实现原理及其在美团业务中的实践

    来自:美团技术团队 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流.使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器.J.U.C提供的线程池ThreadPoolExecuto ...

  5. Java线程池详解学习:ThreadPoolExecutor

    Java线程池详解学习:ThreadPoolExecutor Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) - zhangpeterx的博客 在源码的目录java/util/ ...

  6. Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...

    Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...

  7. JAVA线程池ThreadPoolExecutor与阻塞队列BlockingQueue .

    2019独角兽企业重金招聘Python工程师标准>>> 从Java5开始,Java提供了自己的线程池.每次只执行指定数量的线程,java.util.concurrent.Thread ...

  8. Java线程池了解一下

    前言 马上就要过年了,还在岗位上坚守"swimming"的小伙伴们顶住.博主给大家带来一篇线程池的基本使用解解闷. 为什么需要使用线程池 1.减少线程创建与切换的开销 在没有使用线 ...

  9. java线程池拒绝策略_Java核心知识 多线程并发 线程池原理(二十三)

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后 启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕, 再从队列中取出任务来执行.他 ...

最新文章

  1. 红帽企业虚拟化平台RHEV中WINDOWS 虚拟机如何安装 GUEST代理和驱动
  2. 创建PG全球生态!PostgresConf.CN2019大会盛大召开
  3. mac计算机属性怎么找,删除Mac OS文件系统的附加属性
  4. mysql插入反斜杠“\“丢失的解决方法
  5. AngularJS 技术总结
  6. [Java] 蓝桥杯ALGO-118 算法训练 连续正整数的和
  7. Android报错:FAILED:_nl_intern_locale_data: ?? ‘cnt < (sizeof (_nl_value_type_LC_TIME)
  8. 面试整理—计算机及网络工程师常见问题
  9. 小公司代理记账报税常见问题
  10. 墨卡托与经纬度转换工具
  11. 动态规划背包问题之完全背包详解
  12. 自适应页界面HTML源码
  13. 安卓游戏广告加速插件_【安卓】玩LOL手游吗?这有教程
  14. QQ互联第三方登录多应用用户登录打通
  15. 手机评测 三星V-208
  16. KiCad 5.1.6 泪滴插件安装与使用
  17. 情感分析与观点挖掘第五章笔记(上)/基于方面的情感分析/SentimentAnalysis-and-OpinionMining by Bing Liu
  18. Python可视化案例:Mutual Funds和ETFs对比
  19. Rust 从入门到精通01-简介
  20. 脱机使用计算机是什么意思,电脑脱机状态是什么意思

热门文章

  1. 软件工程的未来发展趋势[转载]
  2. 字符串——垂直柱状图(洛谷 P1598)
  3. 二分查找——A-B数对(洛谷 P1102)
  4. 計算機二級-java08
  5. 服务器千兆网卡接百兆交换机不通_一文搞懂监控工程中百兆交换机和千兆交换机的区别在哪?...
  6. mysql触发器对同一张表做操作_mysql的触发器同数据库 多表的数据操作
  7. Oracle RAC环境下如何定位并杀掉最终阻塞的会话
  8. 知道ThreadLocal吗?一起聊聊到底有啥用
  9. 论文解读丨基于局部特征保留的图卷积神经网络架构(LPD-GCN)
  10. 扎根CNCF社区贡献五年是怎样的体验?听听华为云原生开源团队的负责人怎么说