实验报告
一、目的
掌握线程的创建与启动;了解线程同步与互斥。
二、实验内容与设计思想
设计思路
(1) 线程的创建与启动
(2) 并发计算模拟
(3) 哲学家就餐问题
主要数据结构
问题一 |
| 对象类 接收邮件 发送邮件 |
问题二 |
|
public class SumWorker implements Runnable
{
long sum = 0;
private int m, n;
private PCObject pc;
}
public class PCObject{
private boolean[] isEating = {false, false, false, false, false};}
public class PhilosopherEat implements Runnable{
private PCObject pc;
private int name;}
public class PhilosopherStop implements Runnable{
private PCObject pc;
private int name;}
对象类 哲学家吃饭类 哲学家停止吃饭类
主要代码结构

三、实验使用环境
软件:java version “18.0.2”
EclipseIDE 2022-06
平台:win10
四、实验步骤和调试过程
exp1:线程的创建与启动
需求
邮件客户端(如foxmail)支持多帐号多线程,可以在接收邮件的同时进行邮件发送。不考虑多帐号的问题,编写ReceiveMailRunnable与SendMailRunnable类,均实现Runnable接口。这两个类主要有如下功能:
(1)分别打印"I am receiving emails" 和"I am sending emails";
(2)休眠1000ms-2000ms间的随机时间(Thread.sleep);
(3)循环执行上述(1)、(2)内容,循环10-20次(使用随机数实现);
(4)退出时打印"当前线程XXX正要退出",XXX为当前线程的名称(Thread.currentThread.getName())。
编写TestThread类,在main方法中启动3个ReceiveMailRunnable与3个SendMailRunnable线程,让它们并发执行。希望当这3个ReceiveMailRunnable与3个SendMailRunnable线程结束,才执行最后一句代码,打印"foxmail任务结束"(使用join)。
实验步骤
PCObject类
package exp1;
import java.util.Random;
public class PCObject
{
public void receiveMail() throws InterruptedException
{
System.out.println("I am receiving emails");
Thread.sleep(new Random().nextInt(1000) + 1000);
}
public void sendMail() throws InterruptedException
{
System.out.println("I am sending emails");
Thread.sleep(new Random().nextInt(1000) + 1000);
}
}
ReceiveMailRunnable 类
package exp1;
import java.util.Random;
public class ReceiveMailRunnable implements Runnable
{
private PCObject pc;
public ReceiveMailRunnable(PCObject pc)
{
this.pc = pc;
}
@Override
public void run()
{
int cnt = new Random().nextInt(10)+10;
for(int i=0;i<cnt;i++)
{
try
{
pc.receiveMail();
} catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
System.out.println("当前线程" + Thread.currentThread().getName() + "正要退出");
}
}
SendMailRunnable 类
package exp1;
import java.util.Random;
public class SendMailRunnable implements Runnable
{
private PCObject pc;
public SendMailRunnable(PCObject pc)
{
this.pc = pc;
}
@Override
public void run()
{
int cnt = new Random().nextInt(10)+10;
for(int i=0;i<cnt;i++)
{
try
{
pc.sendMail();
} catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
System.out.println("当前线程" + Thread.currentThread().getName() + "正要退出");
}
}
TestThread 类
package exp1;
import java.util.ArrayList;
public class TestThread
{
public static void main(String[] args) throws InterruptedException
{
//object
PCObject pc = new PCObject();
//runnable
ArrayList<Runnable> runnable = new ArrayList<>();
for (int i = 0; i < 3; i++)
{
runnable.add(new ReceiveMailRunnable(pc));
runnable.add(new SendMailRunnable(pc));
}
//thread
ArrayList<Thread> threads = new ArrayList<>();
for (Runnable i : runnable)
{
threads.add(new Thread(i));
threads.get(threads.size() - 1).start();
}
for (Runnable i : runnable)
{
threads.get(threads.size() - 1).join();
}
//over
System.out.println("foxMail任务结束");
}
}
测试结果
I am receiving emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am receiving emails
当前线程Thread-3正要退出
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am sending emails
当前线程Thread-2正要退出
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
当前线程Thread-1正要退出
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am sending emails
I am receiving emails
当前线程Thread-5正要退出
foxMail任务结束
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
当前线程Thread-4正要退出
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
I am receiving emails
当前线程Thread-0正要退出
exp2:并发计算模拟(共享式)
需求
计算从1到1亿整型数相加。要求使用并发程序处理,即采用多线程实现,在主线程中将计算结果累加(不能使用累加公式)。
大致思路:
(1)编写SumWorker类,实现Runnable接口,计算从m到n的和,其中m,n由构造方法传入;
(2)在主程序中每次开启若干个SumWorker线程(具体数量由程序指定或者用户输入),计算完成之后,将部分结果累加,然后再启动另一批线程,直到计算完成;注意应该写成通用的程序模块,根据参数自动分隔任务、启动线程等;
(3)分别使用共享式sum与独立式sum两种方式完成上述功能,在同样条件下描述这两种方式的性能差异,并分析其原因;
(4)扩展(选做):学有余力的同学可以尝试研究参数对并行性能的影响,如:同时并发线程数、每线程计算数量等,并尝试分析其原因。
说明:本题有点类似"网格计算"或者"云计算"。
实验步骤
第一种写法:(在原先提交的代码上进行改动)
PCObject类
package exp2_shared;
public class PCObject
{
private static long sum = 0;
public static long getSum()
{
return sum;
}
public static void setSum(int sum)
{
PCObject.sum = sum;
}
synchronized public void Sum(int m, int n)
{
for (int i = m; i <= n; i++)
sum += i;
}
}
SumWorker类
package exp2_shared;
public class SumWorker implements Runnable
{
long sum = 0;
private int m, n;
private PCObject pc;
public long getSum()
{
return sum;
}
SumWorker(int m, int n, PCObject pc)
{
this.m = m;
this.n = n;
this.pc = pc;
}
@Override
synchronized public void run()
{
pc.Sum(m, n);
}
}
MainThread类
package exp2_shared;
import java.util.ArrayList;
import java.util.Scanner;
public class MainThread
{
private static int[] input()
{
//input
System.out.println("请输入累加的起点数值和终点数值:");
Scanner in = new Scanner(System.in);
int min = in.nextInt();
int max = in.nextInt();
System.out.println("请输入线程数:");
int num = in.nextInt();
//slicing
int threadNum = max / num;
int[] ThreadStartAndEnd = new int[num + 1];
ThreadStartAndEnd[0] = min;
for (int i = 1; i < num; i++)
ThreadStartAndEnd[i] = ThreadStartAndEnd[i - 1] + threadNum;
ThreadStartAndEnd[num] = max+1;
return ThreadStartAndEnd;
}
public static void main(String[] args) throws InterruptedException
{
//input
int[] ThreadStartAndEnd = input();
long startTime=System.nanoTime(); //获取开始时间
//object
PCObject pc = new PCObject();
//runnable
ArrayList<SumWorker> sumWorkers = new ArrayList<>();
for (int i = 0; i < ThreadStartAndEnd.length - 1; i++)
{
int m = ThreadStartAndEnd[i], n = ThreadStartAndEnd[i + 1] - 1;
sumWorkers.add(new SumWorker(m, n, pc));
}
//thread
ArrayList<Thread> threads = new ArrayList<>();
for (SumWorker sumWorker : sumWorkers)
{
Thread thread = new Thread(sumWorker);
threads.add(thread);
threads.get(threads.size() - 1).start();
}
for(Thread thread:threads)
{
thread.join();
}
//result
int sum = 0;
for(SumWorker sumWorker:sumWorkers)
sum+=sumWorker.getSum();
System.out.println(pc.getSum());
long endTime=System.nanoTime(); //获取结束时间
System.out.println("shared程序运行时间: "+(endTime-startTime)+"ns");
}
}
第二种写法:(上课讲述的方式)
package new2_shared;
public class MySum implements Runnable
{
private static Object synObj = new Object();
static public long sum = 0;
private int m, n;
public void setSum(long sum)
{
this.sum = sum;
}
public int getM()
{
return m;
}
public void setM(int m)
{
this.m = m;
}
public int getN()
{
return n;
}
public void setN(int n)
{
this.n = n;
}
public long getSum()
{
return sum;
}
MySum(int m, int n)
{
this.m = m;
this.n = n;
}
@Override
public void run()
{
synchronized (synObj)
{
for (int i = m; i <= n; i++)
{
sum += i;
}
}
}
}
package new2_shared;
import java.util.ArrayList;
import java.util.List;
public class TestSum
{
//共享式线程越少越快
public static void main(String[] args) throws InterruptedException
{
long start = System.nanoTime();
int from = 1, to = 100000000;
int cur = from;
long total = 0L;
List<Thread> threads = new ArrayList<>();
List<MySum> sums = new ArrayList<>();
//切分
int threadBatch = 10, sumCountParThread = 1000000;
do
{
//1.分配任务
int curMax = cur + sumCountParThread - 1;
if (curMax >= to) curMax = to;
MySum sum = new MySum(cur, curMax);
Thread t = new Thread(sum);
threads.add(t);
sums.add(sum);
cur = curMax + 1;
//2.启动线程 & 雷加部分结果
if (threads.size() >= threadBatch || cur > to)
{
//满8个
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join(); //!!!不能合并,start完立马join还是串行
//Clear
threads.clear();
sums.clear();
}
} while (cur <= to);
long end = System.nanoTime();
System.out.println("sum = " + MySum.sum);
System.out.println("Shared:" + (end - start) + "ns");
}
}
测试结果
请输入累加的起点数值和终点数值:
1 100000000
请输入线程数:
10
结果是:5000000050000000
shared程序运行时间: 57978600ns
exp2:并发计算模拟(独立式)
第一种写法:(在原先提交的代码上进行改动)
实验步骤
PCObject类
package exp2_standalone;
public class PCObject
{
private static long sum = 0;
public static long getSum()
{
return sum;
}
public static void setSum(int sum)
{
PCObject.sum = sum;
}
synchronized public void Sum(int m, int n)
{
for (int i = m; i <= n; i++)
sum += i;
}
}
SumWorker类
package exp2_standalone;
public class SumWorker implements Runnable
{
private int m, n;
private PCObject pc;
SumWorker(int m, int n, PCObject pc)
{
this.m = m;
this.n = n;
this.pc = pc;
}
@Override
synchronized public void run()
{
pc.Sum(m, n);
}
}
MainThread类
package exp2_standalone;
import java.util.ArrayList;
import java.util.Scanner;
public class Main
{
private static int[] input()
{
//input
System.out.println("请输入累加的起点数值和终点数值:");
Scanner in = new Scanner(System.in);
int min = in.nextInt();
int max = in.nextInt();
System.out.println("请输入线程数:");
int num = in.nextInt();
//slicing
int threadNum = max / num;
int[] ThreadStartAndEnd = new int[num + 1];
ThreadStartAndEnd[0] = min;
for (int i = 1; i < num; i++)
{
ThreadStartAndEnd[i] = ThreadStartAndEnd[i - 1] + threadNum;
}
ThreadStartAndEnd[num] = max+1;
return ThreadStartAndEnd;
}
public static void main(String[] args) throws InterruptedException
{
//input
int[] ThreadStartAndEnd = input();
long startTime=System.nanoTime(); //获取开始时间
//object
PCObject pc = new PCObject();
//runnable
ArrayList<SumWorker> sumWorkers = new ArrayList<>();
for (int i = 0; i < ThreadStartAndEnd.length - 1; i++)
{
int m = ThreadStartAndEnd[i], n = ThreadStartAndEnd[i + 1] - 1;
sumWorkers.add(new SumWorker(m, n, pc));
}
//thread
ArrayList<Thread> threads = new ArrayList<>();
for (SumWorker sumWorker : sumWorkers)
{
Thread thread = new Thread(sumWorker);
threads.add(thread);
threads.get(threads.size() - 1).start();
}
for(Thread thread:threads)
thread.join();
//result
System.out.println(pc.getSum());
long endTime=System.nanoTime(); //获取结束时间
System.out.println("standalone程序运行时间: "+(endTime-startTime)+"ns");
}
}
第二种写法:(上课讲述的方式)
package new2;
public class MySum implements Runnable
{
private long sum = 0;
private int m, n;
public void setSum(long sum)
{
this.sum = sum;
}
public int getM()
{
return m;
}
public void setM(int m)
{
this.m = m;
}
public int getN()
{
return n;
}
public void setN(int n)
{
this.n = n;
}
public long getSum()
{
return sum;
}
MySum(int m, int n)
{
this.m = m;
this.n = n;
}
@Override
public void run()
{
for (int i = m; i <= n; i++)
sum += i;
}
}
package new2;
import java.util.ArrayList;
import java.util.List;
public class TestSum
{
public static void main(String[] args) throws InterruptedException
{
long start = System.nanoTime();
int from = 1, to = 100000000;
int cur = from;
long total = 0L;
List<Thread> threads = new ArrayList<>();
List<MySum> sums = new ArrayList<>();
//切分
int threadBatch = 10, sumCountParThread = 1000000;
do
{
//1.分配任务
int curMax = cur + sumCountParThread - 1;
if (curMax >= to) curMax = to;
MySum sum = new MySum(cur, curMax);
Thread t = new Thread(sum);
threads.add(t);
sums.add(sum);
cur = curMax + 1;
//2.启动线程 & 雷加部分结果
if (threads.size() >= threadBatch || cur > to)
{
//满8个
for (Thread thread : threads) thread.start();
for (Thread thread : threads) thread.join(); //!!!不能合并,start完立马join还是串行
for (MySum ss : sums)
{
total += ss.getSum();
}
//Clear
threads.clear();
sums.clear();
}
} while (cur <= to);
long end = System.nanoTime();
System.out.println("sum = " + total);
System.out.println("Elapse:" + (end - start) + "ns");
}
}
exp3:哲学家就餐问题
需求
哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题。
一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。
请尝试使用多线程模拟,确保不会出现"饿死"状态。
实验步骤
第一种写法:(在原先提交的代码上进行改动)
PCObject类
package exp3;
import static exp3.Servant.getChopsticks;
import static exp3.Servant.putChopsticks;
public class PCObject
{
private boolean[] isEating = {false, false, false, false, false};
synchronized public void eat(int name) throws InterruptedException
{
while (true)
{
if (isEating[name] || !getChopsticks(name))
{
this.wait();
this.notify();
continue;
}
break;
}
System.out.println("科学家" + name + "饿了,拿到了筷子");
isEating[name] = true;
this.notify();
}
synchronized public void stop(int name) throws InterruptedException
{
while (true)
{
if (!isEating[name] || !putChopsticks(name))
{
this.wait();
this.notify();
continue;
}
break;
}
putChopsticks(name);
System.out.println("科学家" + name + "不吃了,放下了侉子");
isEating[name] = false;
this.notify();
}
}
PhilosopherEat 类
package exp3;
public class PhilosopherEat implements Runnable
{
private PCObject pc;
private int name;
public PhilosopherEat(PCObject pc, int name)
{
this.pc = pc;
this.name = name;
}
public PCObject getPc()
{
return pc;
}
public void setPc(PCObject pc)
{
this.pc = pc;
}
public int getName()
{
return name;
}
public void setName(int name)
{
this.name = name;
}
@Override
public void run()
{
try
{
pc.eat(name);
} catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
PhilosopherStop 类
package exp3;
public class PhilosopherStop implements Runnable
{
private PCObject pc;
private int name;
public PhilosopherStop(PCObject pc, int name)
{
this.pc = pc;
this.name = name;
}
public PCObject getPc()
{
return pc;
}
public void setPc(PCObject pc)
{
this.pc = pc;
}
public int getName()
{
return name;
}
public void setName(int name)
{
this.name = name;
}
@Override
public void run()
{
try
{
pc.stop(name);
} catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
Servant类
package exp3;
public class Servant
{
private static boolean[] chopsticks = new boolean[]{true, true, true, true, true};
public void setChopsticks(int i, boolean chopstick)
{
chopsticks[i] = chopstick;
}
public static boolean getChopsticks(int i)
{
int left = i % 5, right = (i + 1) % 5;
if (chopsticks[left] && chopsticks[right])
{
chopsticks[left] = chopsticks[right] = false;
return true;
}
return false;
}
public static boolean putChopsticks(int i)
{
int left = i % 5, right = (i + 1) % 5;
if(!chopsticks[left]&&!chopsticks[right])
{
chopsticks[left] = chopsticks[right] = true;
return true;
}
return false;
}
}
Main类
package exp3;
import java.util.ArrayList;
public class Main
{
public static void main(String[] args) throws InterruptedException
{
//object
PCObject pc = new PCObject();
//philosophers
ArrayList<Runnable> philosophers = new ArrayList<>();
for (int j = 0; j < 5; j++)
{
philosophers.add(new PhilosopherEat(pc, j));
philosophers.add(new PhilosopherStop(pc, j));
}
//eat and stop
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < philosophers.size(); i++)
{
threads.add(new Thread(philosophers.get(i)));
threads.get(i).start();
}
for (Thread thread : threads)
thread.join();
//result
System.out.println("就餐完毕");
}
}
第二种写法:(上课讲述的方式)
package new3;
public class Phy implements Runnable
{
private Servant servant;
private int name;
public Phy(int name, Servant servant)
{
this.name = name;
this.servant = servant;
}
@Override
public void run()
{
while (true)
{
try
{
// System.out.println("哲学家" + name + "正在思考...");
Thread.sleep(2000 + (int) (1000 * Math.random()));
// System.out.println("哲学家" + name + "思考完毕");
if (servant.get(name))
{
System.out.println("哲学家" + name + "开始吃饭...");
Thread.sleep(2000 + (int) (1000 * Math.random()));
System.out.println("哲学家" + name + "吃饭完毕");
}
servant.put(name);
} catch (InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
}
package new3;
public class Servant
{
private boolean[] chops = new boolean[5];
public synchronized boolean get(int i) throws InterruptedException
{
while (true)
{
if (chops[i] || chops[(i + 1) % 5])
{
wait();
continue;
}
break;
}
chops[i] = chops[(i + 1) % 5] = true;
notify();
//notify可以不用加,因为没有再占用了,相当于释放,会自动notify
//但是不加的话,会进入两个人的死锁,始终是两个人在吃,在放下
return true;
}
//放回筷子
public synchronized boolean put(int i)
{
//对放回来也要进行加锁
//逻辑上的解锁
chops[i] = chops[(i + 1) % 5] = false;
notify();
return true;
}
package new3;
public class Test
{
public static void main(String[] args)
{
Servant servant = new Servant();
for(int i=0;i<5;i++)
{
new Thread(new Phy(i,servant)).start();
}
}
}
测试结果
科学家0饿了,拿到了筷子
科学家3饿了,拿到了筷子
科学家0不吃了,放下了侉子
科学家1饿了,拿到了筷子
科学家3不吃了,放下了侉子
科学家1不吃了,放下了侉子
科学家2饿了,拿到了筷子
科学家2不吃了,放下了侉子
科学家4饿了,拿到了筷子
科学家4不吃了,放下了侉子
就餐完毕
五、实验小结
实验中遇到的问题及解决过程
无
实验中产生的错误及原因分析
无
实验体会和收获
- 掌握线程的创建与启动;
- 了解线程同步与互斥。