java节拍器 定时任务 ScheduledExecutorService 总结

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有两个主要的运行方法,schedulescheduleAtFixedRate,有细微差距。schedulescheduleAtFixedRate方法一般情况下是没什么区别的,只在某个情况出现时会有区别--当前任务没有来得及完成下次任务又交到手上。

我们来举个例子:

暑假到了老师给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);

使用的话,有三个方法schedulescheduleAtFixedRatescheduleWithFixedDelay

  • 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();
	}
}
上一篇:我的博客即将入驻“云栖社区”


下一篇:【自然框架】用CMS的栏目举例,聊一聊从“一层”到“三层”的变化