java节拍器 定时任务 ScheduledExecutorService 总结
起因
明明干啥都是个小菜鸡,还那么多事事。是这样的,想学吉他,手机上的节拍器软件嫌声音小,电脑上没仔细找好用的节拍器,但是电脑有音箱,也用电脑看谱子练练啥的,就想有个电脑好用的节拍器。
简介
从网上复制了一个播放WAV的代码, 播放WAV的声音。
使用ScheduledExecutorService 来定时任务。
掉进了很多坑
先用多线程,会卡。
又用线程池,还是会卡。
查了资料可以用ScheduledExecutorService ,为了学习,先用了Timer和TimerTask,慢的速度可以,快速就不行。
最后用了ScheduledExecutorService。
还有个极大的坑,那就是我刚开始一直连接的蓝牙音箱,速度快多线程,线程池都不卡,慢的时候有时候会有拍子不响,真是奇了怪。可是巧了,误打误撞,最后用ScheduledExecutorService 的时候,用的笔记本自带扬声器,很顺利,然后连上了蓝牙音箱,速度慢音箱放的声音会卡。卡不卡和播放设备很有关系。就试了之前用的线程那些方法,果然还是卡,用ScheduledExecutorService 就对了。
多线程
将写一个播放的类,继承Thread或者实现Runnable接口,然后
while(true) { //diPath, rate 文件路径和延时时间
Thread thread1 = new Thread(new MyPlayer(new FileInputStream(diPath)));
Thread thread2 = new Thread(new MyPlayer(new FileInputStream(daPath)));
Thread thread3 = new Thread(new MyPlayer(new FileInputStream(daPath)));
Thread thread4 = new Thread(new MyPlayer(new FileInputStream(daPath)));
thread1.start();
Thread.sleep(rate);
thread2.start();
Thread.sleep(rate);
thread3.start();
Thread.sleep(rate);
thread4.start();
Thread.sleep(rate);
}
}
没有想到别的办法,在循环里每次创建。这很垃圾,我也知道。
线程池
用线程池,理论效果应该减少了系统的开销,可是实际上没有效果。
思路还是刚刚的思路就是用了线程池,还是垃圾代码。
ThreadPoolExecutor tpool = new ThreadPoolExecutor(16, 20, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
while (true) { //diPath, rate 文件路径和延时时间
tpool.execute(new MyPlayerSu(diPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
}
Timer和TimerTask
使用方法,创建一个类继承TimerTask,和继承线程类差不多,然后重写里面的run()
方法,然后创建Timer对象,调用相应的方法,传入相关对象和数值,也可以使用匿名对象。
Timer timer = new Timer();// 实例化Timer类
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
//代码
},xx,xx)
Timer有两个主要的运行方法,schedule
和scheduleAtFixedRate
,有细微差距。schedule
和scheduleAtFixedRate
方法一般情况下是没什么区别的,只在某个情况出现时会有区别--当前任务没有来得及完成下次任务又交到手上。
我们来举个例子:
暑假到了老师给schedule和scheduleAtFixedRate两个同学布置作业。
老师要求学生暑假每天写2页,30天后完成作业。
这两个学生每天按时完成作业,直到第10天,出了意外,两个学生出去旅游花了5天时间,这5天时间里两个人都没有做作业。任务被拖延了。
这时候两个学生采取的策略就不同了:
schedule重新安排了任务时间,旅游回来的第一天做第11天的任务,第二天做第12天的任务,最后完成任务花了35天。
scheduleAtFixedRate是个守时的学生,她总想按时完成老师的任务,于是在旅游回来的第一天把之前5天欠下的任务以及第16天当天的任务全部完成了,之后还是按照老师的原安排完成作业,最后完成任务花了30天。
用Timer这个方法,太快了的时候,滴答的声音会播放不完,从而会继续等播放结束后才能播放下一个声音,所以假如声音时间长,或者太快就会不准了。
ScheduledExecutorService 代替Timer
ExecutorService可以调度命令在给定的延迟之后运行,或定期执行。 详解去百度,百度上用一些。
创建ScheduledExecutorService
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
使用的话,有三个方法schedule
,scheduleAtFixedRate
,scheduleWithFixedDelay
。
-
schedule(Callable<V> callable, long delay, TimeUnit unit)
创建并执行在给定延迟后启用的ScheduledFuture。 -
schedule(Runnable command, long delay, TimeUnit unit)
创建并执行在给定延迟后启用的单次操作。 -
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行将在initialDelay
之后开始,然后是initialDelay+period
,然后是initialDelay + 2 * period
,等等。 -
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。
看方法描述,用scheduleAtFixedRate
,传入的runnable会自己循环运行,就传入一次就行。
我先用循环创建几个Thread对象
/**
* @param di 滴的文件路径
* @param da 哒的文件路径
* @param patNum 一小节响几次
* @return 创建的播放声音的线程数组
*/
public Thread[] getThread(String di, String da, int patNum) {
Thread[] threads = new Thread[patNum];
threads[0] = new Thread(new MyPlayerWAV(di));
for (int i = 1; i < threads.length; i++) {
threads[i] = new Thread(new MyPlayerWAV(da));
}
return threads;
}
然后,创建对象,传入数值,执行方法就OK
PalyerPat palyer = new PalyerPat();
Thread[] threads = palyer.getThread(diPath, daPath, patNum);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < threads.length; i++) {
service.scheduleAtFixedRate(threads[i], rate * i, rate * patNum, TimeUnit.MILLISECONDS);
}//rate * i 头一次响的开始时间
//rate * patNum 延时多久。响一次需要延时一小节。
播放WAV方法
java原生就支持,不用第三方。(copy大佬的)
import java.io.*;
import java.util.concurrent.*;
import javax.sound.sampled.*;
public void player(String filename) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream stream;
stream = AudioSystem.getAudioInputStream(new File(filename));
AudioFormat target = stream.getFormat();
DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, target);
SourceDataLine line = null;
int len = -1;
line = (SourceDataLine) AudioSystem.getLine(dinfo);
line.open(target);
line.start();
byte[] buffer = new byte[1024];
while ((len = stream.read(buffer)) > 0) {
line.write(buffer, 0, len);
}
line.drain();
line.stop();
line.close();
stream.close();
}
播放mp3
需要包mp3spi。
maven依赖
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.5.4</version>
</dependency>
Player player;
player = new Player(new FileInputStream(filename));//网上的代码外面还有一层BufferedInputStream,当时找播放卡的问题看了源码,原理里面有创建一个BufferedInputStream,我就省掉了。
player.play();
如果用这个播放,会卡,还是卡,啊,刚刚试了一下,原因以后再找。这只是播放的,在节拍器中会卡的。原来还是有坑。坑太多了。
end
非音乐专业,不专业,勿吐槽。非大佬,小菜鸡一只,望大佬指点。
附上全部代码
import java.io.File;
import java.io.IOException;
import java.util.concurrent.*;
import javax.sound.sampled.*;
public class PalyerPat {
public static void main(String[] args) {
String diPath = "src/main/resources/tone1.wav";
String daPath = "src/main/resources/tone1_1.wav";
int patNum = 4;
int speed = 160;//输入的速度
int rate = (int) (60F / speed * 1000);//转成每响一次延时的毫秒。
PalyerPat palyer = new PalyerPat();
Thread[] threads = palyer.getThread(diPath, daPath, patNum);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < threads.length; i++) {
service.scheduleAtFixedRate(threads[i], rate * i, rate * patNum, TimeUnit.MILLISECONDS);
}
}
/**
* @param di 滴的文件路径
* @param da 哒的文件路径
* @param patNum 一小节响几次
* @return 创建的播放声音的线程数组
*/
public Thread[] getThread(String di, String da, int patNum) {
Thread[] threads = new Thread[patNum];
threads[0] = new Thread(new MyPlayerWAV(di));
for (int i = 1; i < threads.length; i++) {
threads[i] = new Thread(new MyPlayerWAV(da));
}
return threads;
}
}
class MyPlayerWAV implements Runnable {
String filename = "src/main/resources/tone1.wav";
public MyPlayerWAV() {
}
public MyPlayerWAV(String filename) {
super();
this.filename = filename;
}
public void run() {
try {
playerWAV(filename);
// System.out.println(filename);
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
//播放WAV文件方法
public void playerWAV(String filename) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream stream;
stream = AudioSystem.getAudioInputStream(new File(filename));
AudioFormat target = stream.getFormat();
DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, target);
SourceDataLine line = null;
int len = -1;
line = (SourceDataLine) AudioSystem.getLine(dinfo);
line.open(target);
line.start();
byte[] buffer = new byte[1024];
while ((len = stream.read(buffer)) > 0) {
line.write(buffer, 0, len);
}
line.drain();
line.stop();
line.close();
stream.close();
}
}