7-线程

实验报告

一、目的

掌握线程的创建与启动;了解线程同步与互斥。

二、实验内容与设计思想

设计思路

(1) 线程的创建与启动

(2) 并发计算模拟

(3) 哲学家就餐问题

主要数据结构

问题一
public class PCObject;
public class ReceiveMailRunnable implements Runnable{private PCObject pc;}
public class SendMailRunnable implements Runnable
{private PCObject pc;}
对象类 接收邮件 发送邮件
问题二
public class PCObject;

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不吃了,放下了侉子
就餐完毕

五、实验小结

实验中遇到的问题及解决过程

实验中产生的错误及原因分析

实验体会和收获

  1. 掌握线程的创建与启动;
  2. 了解线程同步与互斥。
posted @ 2023-08-22 20:25  jijfurhg  阅读(6)  评论(0编辑  收藏  举报