声明:由于学习所用环境为JDK1.8,所有java代码均在JDK1.8中测试通过,如果环境发生改变,可能会有错误发生!
一、通过Callable接口实现多线程
1. Callable接口介绍
java.util.concurrent.Callable是一个泛型接口,只有一个call()方法
call()方法抛出Exception异常,且返回一个指定的泛型类的对象
2. Callable接口实现线程的应用场景
当父线程想要获取子线程的运行结果时。
3. 使用Callable接口实现多线程的步骤
第一步:创建Callable子类的实例化对象。
第二步:创建FutureTask 对象,并将Callable对象传入FutureTask的构造方法中(注意:FutureTask实现了Runnable接口和Future接口)
第三步:实例化Thread对象,并在构造方法中传入FutureTask对象
第四步:启动线程
package callable;
import java.util.concurrent.FutureTask;
public class CallableDemo {
public static void main(String[] args) {
GeneralCallable call=new GeneralCallable(); // 创建Callable子类的实例化对象
FutureTask<String> task=new FutureTask<String>(call); // 创建FutureTask 对象,
并将Callable对象传入FutureTask的构造方法中
Thread t=new Thread(task,"魏延线程"); // 实例化Thread对象,并在构造方法中传入FutureTask对象
t.start(); // 启动线程
System.out.println(Thread.currentThread().getName()+"进入休眠状态(休眠5秒)...");
try {
Thread.sleep(5000);
String report=task.get(); // 获取子线程的返回值
System.out.println("子线程的返回结果是:"+report);
} catch (Exception e) {
e.printStackTrace();
}
}
}
二、生产者-消费者问题
生产者线程不断生产,消费者线程不断取走生产者生产的产品。
Object中的几个方法支持:
wait() 线程等待,当前线程进入调用对象的线程等待池
notify() 唤醒1个等待线程
notifyAll() 唤醒全部等待的线程
注意:以上三个方法都必须在同步机制(同步方法和同步代码)中调用
1、“一对多”
package onetoonepc;
public class Breakfast {
private String food;
private String drink;
private boolean flag=false;
public Breakfast() {
}
public Breakfast(String food, String drink) {
super();
this.food = food;
this.drink = drink;
}
public synchronized void makeBreakfast(String food, String drink) {
if(flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.food = food;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.drink = drink;
flag=true;
notify();
}
public synchronized void haveBreakfast() {
if(!flag){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(food + "=====》" + drink);
flag=false;
notify();
}
}
package onetoonepc;
public class Producer implements Runnable{
private Breakfast bf;
public Producer(Breakfast bf){
this.bf=bf;
}
@Override
public void run() {
for (int i = 1; i <= 7; i++) {
if(i%2==0){
this.bf.makeBreakfast("馒头", "稀饭");
}else {
this.bf.makeBreakfast("bread", "milk");
}
}
}
}
package onetoonepc;
public class Consumer implements Runnable {
private Breakfast bf;
public Consumer(Breakfast bf){
this.bf=bf;
}
@Override
public void run() {
for (int i = 1; i <=7; i++) {
this.bf.haveBreakfast();
}
}
}
package onetoonepc;
public class Test {
public static void main(String[] args) {
Breakfast bf=new Breakfast();
new Thread(new Producer(bf)).start();
new Thread(new Consumer(bf)).start();
}
}
2、“多对多”package many2many;
public class Product {
private int count=0;
private final int MAX=5;
public synchronized void makeProduct(){
String threadName=Thread.currentThread().getName();
if(count>=MAX){
System.out.println(threadName+"货物已满");
try {
notifyAll();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(threadName+"生产了一件商品,当前商品数量是"+count);
notifyAll();
}
}
public synchronized void sellProduct(){
String threadName=Thread.currentThread().getName();
if(count<=0){
System.out.println(threadName+"商品暂无库存,请等待");
try {
notifyAll();
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(threadName+"消费了一件商品,剩余商品数量为"+count);
}
}
}
package many2many;
public class Producer implements Runnable {
private Product pro = new Product();
public Producer(Product pro) {
this.pro = pro;
}
@Override
public void run() {
while(true){
pro.makeProduct();
}
}
}
package many2many;
public class Consumer implements Runnable {
private Product pro;
public Consumer(Product pro) {
this.pro = pro;
}
@Override
public void run() {
while(true){
pro.sellProduct();
}
}
}
package many2many;
public class Test {
public static void main(String[] args) {
Product product=new Product();
new Thread(new Producer(product),"生产者1").start();
new Thread(new Producer(product),"生产者1").start();
new Thread(new Consumer(product),"消费者1").start();
new Thread(new Consumer(product),"消费者1").start();
}
}
三、多线程下载(复制)文件使用RandomAccessFile与InputStream的skip(long n)方法使每个线程负责文件的每一部分读写。
package download;
import java.io.*;
public class DownRunnable implements Runnable{
private File srcFile;
private long partTask;
private long startPos;
private RandomAccessFile raf;
public DownRunnable(File srcFile,long partTask,long startPos,RandomAccessFile raf){
this.srcFile=srcFile;
this.partTask=partTask;
this.startPos=startPos;
this.raf=raf;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"从"+startPos+"个字节开始读");
InputStream input=null;
try {
input=new FileInputStream(srcFile);
byte[] b=new byte[1024*1024*10];
int len=0;
int count=0;
while((len=input.read(b))!=-1 && count<partTask){
raf.write(b,0,len);
count+=len;
System.out.println(Thread.currentThread().getName()+"已经写了"+count+"个字节");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package download;
import java.io.File;
import java.io.RandomAccessFile;
public class Client {
public static void main(String[] args) throws Exception{
File srcFile=new File("D:"+File.separator+"Qiyi"+File.separator+"Offline"+File.separator+"123.qsv");
long partTask=srcFile.length()/8;
RandomAccessFile raf=null;
raf=new RandomAccessFile("e:"+File.separator+srcFile.getName(),"rw");
for (int i = 0; i <8; i++) {
long startPos=i*partTask;
DownRunnable dr=new DownRunnable(srcFile, partTask, startPos, raf);
new Thread(dr,i+"线程").start();
}
}
}
一般情况下,会将耗时操作放入线程中。