【JAVA学习】枚举类,异常处理,多线程等

目录

★枚举类  (需要定义一组常量时,选用枚举类)

★异常处理

异常处理机制

★比较器

★多线程

实现线程的2种方式

继承Thread类

      2.实现Runnable接口(常用)

synchronized关键字

wait()和notify()  属于Object类

生产者-消费者模型

哲学家就餐问题

并发计算模拟

编程题

筛法求素数

二次方程求解

矩阵四则运算

Shape

完全数(Perfect number)

约瑟夫环问题


★枚举类  (需要定义一组常量时,选用枚举类)

枚举类---->类的对象只有有限个、确定的

jdk5.0后,用enum关键字定义枚举类

说明:enum定义的枚举类默认继承于java.lang.En

常量顺序:枚举常量的声明必须放在枚举体的开头,多个常量之间用", " 隔开, 末尾常量";"结束

toString 和 ordinal 方法:每个枚举都有默认的 toString() 和 ordinal() 方法,分别返回枚举的名字和位置序号(从0开始)

以下关于枚举类型enum的叙述错误的是(  C   )

A. 枚举类型本质上也是类              B. 可以修改enum对象的成员变量

C. 枚举类型可以拥有public构造方法    D. enum构造方法也可以重载

解析】

枚举的构造方法是私有的,不能声明为 public 或 protected。

枚举实例是在类加载时自动创建的,无法外部实例化。

枚举不能使用继承(因为编译器会自动为我们继承Enum抽象类而Java只支持单继承,因此枚举类是无法手动实现继承的)

B选项:不能修改对象,因为对象是常量,但对象内的属性是可以修改的

7. 已知枚举类型Seasons的定义如下:

enum Seasons{
    SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
    private String desc = "";
    public void setDesc(String desc){
        this.desc = desc;
    }
    public Seasons(String desc){
        setDesc(desc);
    }
}

判断上述代码段是否正确?如果不正确,请指出错误之处,并修改。

【解析】

上述代码不正确,枚举类中的构造方法必须是私有的,不能为public

代码应该修改为:

enum Seasons{
    SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");


    private String desc = "";
    public void setDesc(String desc){
        this.desc = desc;
    }

    // 枚举类中构造方法必须是私有的
    private Seasons(String desc){
        setDesc(desc);
    }

}

扩展:

//使用enum关键字来定义枚举类
enum Seasons{
    /*初始自定义枚举类的格式应该为:
    class Seasons{
        private String desc;
        private Seasons(String desc){
            this.desc = desc;
        }
        public static final Seasons SPRING = new Seasons("春天");
        public static final Seasons SUMMER = new Seasons("夏天");
        public static final Seasons AUTUMN = new Seasons("秋天");
        public static final Seasons WINTER = new Seasons("冬天");
    }
    */

    //默认情况下,枚举对象是public static final修饰的
    SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");

    //枚举对象是常量不可以修改,但对象内的属性是可以修改的
    private String desc = "";
    public void setDesc(String desc){
        this.desc = desc;
    }

    // 枚举类中构造方法必须是私有的
    private Seasons(String desc){
        setDesc(desc);
    }

    public String getDesc(){
        return desc;
    }

    public static void main(String[] args){
        Seasons s = Seasons.SPRING;
        s.setDesc("春天(副本)");
        System.out.println(s.getDesc());
    }
}

★异常处理

Exception:java.lang包下,称为异常类,表示程序本身可以处理的问题

12. 下列属于未检查异常的是(  D   )。

A. java.io.IOException       B. java.net.SocketException

C. java.io.FileNotFoundException    D. java.lang.NullPointerException(空指针的访问)

未检查异常---运行时异常

已检查异常---编译时异常

异常处理机制

异常的处理:抓抛模型

  • 过程一:"抛":程序在正常执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。一旦抛出对象以后,其后的代码就不再执行
  • 过程二:"抓”,可以理解为异常的处理方式  1)try-catach-finally 2)throws

1.捕获异常 try… catch…finally

try{

   //可能发生异常的代码块

}catch(异常类型1 变量名1){

    //处理异常的方式1

}catch(异常类型1 变量名1){

    //处理异常的方式1

}finally{//可选

    //无论是否发生异常,都会执行

}

2.throws+异常类型

在方法上使用throws关键字可以声明该方法可能会抛出的异常

调用改方法的"调用者",需要处理此异常,否则编译不通过

★比较器

java中,对集合对象和数组对象进行排序,有以下两种实现方式:

  1. 1)实现Comparable接口,实现compareTo()方法
  2. 若一个类实现了Comparable接口,就意味着该类支持排序。
  3. 实现了 Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
  4. 2)自定义比较器,实现Comparator接口,实现compare()方法 

Arrays.sort(数组类型,Comparator对象)

//Comparator 定义

public interface Comparator {

    int compare(T o1, T o2);   
    boolean equals(Object obj); //可以不实现
}

8. 已知抽象类Shape定义如下:

public abstract class Shape{

public abstract double getArea(); //计算面积

}

为Shape添加一个默认比较器,实现对面积的降序排序。请写出修改后的完整Shape类代码。

【解析】

public abstract class Shape {

    //计算面积的抽象方法(抽象方法没有方法体)
    public abstract double getArea();

    //默认比较器,按面积从大到小排序
    public static final Comparator<Shape> areaComparator = new Comparator<Shape>() {
        @Override
        public int compare(Shape o1, Shape o2) {
            return Double.compare(o2.getArea(), o1.getArea());
        }
    };
}

 Staff, Teacher, SecurityGuard, Dean

(1)定义Staff类(职工),添加如下属性(name, address, age, sex, salary, dateHired),类型自定,其中salary为工资,dateHired为雇佣日期(java.util.Date类型)。生成相应的setter/getter方法。

(2)编写Teacher类(教师),继承自Staff类,包含属性:department(系), speciality(专业), postAllowance(岗位津贴)。

(3)编写SecurityGuard类(保安),继承自Staff类,包含属性:skills(专技), dangerousAllowance(高危津贴)。

(4)编写Teacher的一个子类Dean(院长),包含属性:adminAward(行政奖金)。

(5)定义上述各类的getter/setter方法,并添加合适的构造方法。

(6)编写一个测试类,在测试类中添加若干个Staff, Teacher, SecurityGuard, Dean实例(个数及内容自定),并在测试类中定义并测试如下方法:

编写一个方法private static void printName(Staff[] persons)打印出每个人的名字;

编写一个方法private static void printSalary(Staff[] staffs)打印出Staff类或者其子类对象的薪水(注意:Staff的薪水只有salary,Teacher的薪水为salary+postAllowance,SecurityGuard的薪水为salary+dangerousAllowance,而Dean的薪水则为salary+postAllowance+adminAward);

编写一方法private static void sortBySalary(Staff[] staffs),支持对Staff类及其子类按照各自的薪水降序排序;

编写一方法private static void sortByAge(Staff[] staffs),对Staff对象按照年龄升序排序,再编写一个方法按name升序进行排序;

1)Staff类

package B4;

import java.util.Date;

//职工类
public class Staff {
    private String name;
    private String address;
    private int age;
    private String  sex;
    private double salary;
    private Date dateHired;

    //对应setter和getter方法

    public Staff(String name,String address,int age,String sex,double salary,Date dateHired){
        this.name=name;
        this.address=address;
        this.age=age;
        this.sex=sex;
        this.salary=salary;
        this.dateHired=dateHired;
    }

    
    public int getAge(){
        return this.age;
    }

    public String getName(){
        return this.name;
    }

    public double getSalary(){
        return this.salary;
    }

    public double getResultSalary(){
        return this.getSalary();
    }
}

2)Teacher类

package B4;

import java.util.Date;

public class Teacher extends Staff{
    private String department;
    private String speciality;
    private double postAllowance;

   //对应setter和getter方法
    public double getPostAllowance() {
        return this.postAllowance;
    }


    public Teacher(String name, String address, int age, String sex, double salary, Date dataHired,String department,String speciality,double postAllowance){
        super(name,address,age,sex,salary,dataHired);
        this.department=department;
        this.speciality=speciality;
        this.postAllowance=postAllowance;
    }

    public double getResultSalary(){
        return this.getSalary()+this.getPostAllowance();
    }

}

3)SecurityGuard类

package B4;

import java.util.Date;

//保安类
public class SecurityGuard extends Staff{
    private String skills;
    private double dangerousAllowance;

    //对应setter和getter方法

    public SecurityGuard(String name , String address, int age, String sex, double salary, Date dateHired,String skills,double dangerousAllowance){
        super(name,address,age,sex,salary,dateHired);
        this.skills=skills;
        this.dangerousAllowance=dangerousAllowance;
    }

    public double getDangerousAllowance(){
        return this.dangerousAllowance;
    }

    public double getResultSalary(){
        return this.getSalary()+this.getDangerousAllowance();
    }
}

4)Dean类

package B4;

import java.util.Date;

//院长类
public class Dean extends Teacher{

    private double adminAward;

    //对应setter和getter方法

    public Dean(String name, String address, int age, String sex, double salary, Date dateHired, String department,String speciality,double postAllowance,double adminAward){
        super(name,address,age,sex,salary,dateHired,department,speciality,postAllowance);
        this.adminAward=adminAward;
    }

    public double getResultSalary(){
        return super.getResultSalary()+this.adminAward;
    }
}

5)测试类

package B4;

import java.util.Comparator;
import java.util.Date;
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {

        Staff[] staffs = new Staff[4];
        staffs[0] = new Staff("Pan", "厦门",20,"女",100,new Date(2023,9,1));
        staffs[1]=new Teacher ("Lin","泉州",20,"女",200,new Date(2022,9,1),"文学系","语文",100);
        staffs[2]=new SecurityGuard("Hu","南平",18,"男",50,new Date(2021,9,1),"拳击",10);
        staffs[3]=new Dean("Liu","福州",40,"女",300,new Date(2020,9,1),"理学院","数学",100,300);

        //打印每个人的名字
        printName(staffs);
        System.out.println();
        //打印薪水
        printSalary(staffs);
        System.out.println();
        //按薪水排序(降序)
        sortBySalary_2(staffs);
        printSalary(staffs);
        System.out.println();
        //按年龄升序
        sortByAge_2(staffs);
        printAge(staffs);
        System.out.println();
        //按名字排序(升序)
        sortByName_2(staffs);
        printName(staffs);

    }

    //打印姓名
    private static void printName(Staff[] persons){
        for(Staff staff:persons){
            String name=staff.getName();
            System.out.println(name);
        }
    }

    //打印工资
    private static void printSalary(Staff[] staffs){
        for(Staff staff:staffs){
            double salary=staff.getResultSalary();
            System.out.println("Salay:"+salary);
        }
    }

    //按工资降序排序
    //写法1
    private static final Comparator<Staff> SalaryComptator= new Comparator<Staff>(){
        public int compare(Staff s1,Staff s2){
            return Double.compare(s2.getResultSalary(),s1.getResultSalary());
        }
    };
    private static void sortBySalary(Staff[] staffs){
        Arrays.sort(staffs,SalaryComptator);
    }
    //写法2
    private static void sortBySalary_1(Staff[] staffs){
        Arrays.sort(staffs,new Comparator<Staff>(){
           public int compare(Staff s1,Staff s2){
               return Double.compare(s2.getResultSalary(), s1.getResultSalary());
           }
        });
    }
    //写法3
    private static void sortBySalary_2(Staff[] staffs){
        Arrays.sort(staffs,(s1,s2)->Double.compare(s2.getResultSalary(),s1.getResultSalary()));
    }

    //按年龄排序(升序)
    //写法1
    private static void sortByAge(Staff[] staffs){
        Arrays.sort(staffs,AgeComparator);
    }

    private static final Comparator<Staff> AgeComparator=new Comparator<Staff>(){
        public int compare(Staff s1,Staff s2){
            return s1.getAge()-s2.getAge();
        }
    };
    //写法2
    private static void sortByAge_1(Staff[] staffs){
        Arrays.sort(staffs,new Comparator<Staff>(){
           public int compare(Staff s1,Staff s2){
               return Integer.compare(s1.getAge(), s2.getAge());
           }
        });
    }
    //写法3
    private static void sortByAge_2(Staff[] staffs){
        Arrays.sort(staffs,(s1,s2)->Integer.compare(s1.getAge(),s2.getAge()));
    }

    private static void printAge(Staff[] staffs){
        for(Staff staff:staffs){
            int age=staff.getAge();
            System.out.println("Age="+age);
        }
    }

    //对名字进行排序
    //写法1
    private static void sortByName(Staff[] staffs){
        Arrays.sort(staffs,NameComparator);
    }
    private static final Comparator<Staff> NameComparator=new Comparator<Staff>(){
        public int compare(Staff s1,Staff s2){
            return s1.getName().compareTo(s2.getName());
        }
    };
    //写法2
    private static void sortByName_1(Staff[] staffs){
        Arrays.sort(staffs,new Comparator<Staff>(){
            public int compare(Staff s1,Staff s2){
                return s1.getName().compareTo(s2.getName());
            }
        });
    }
    //写法3 使用Lambda表达式
    /*
    Lambda表达式的语法格式:(参数列表)->主体
    1.只有一个参数,可省略括号
    2.没有参数,需要空括号
     */
    private static void sortByName_2(Staff[] staffs){
        Arrays.sort(staffs,(s1,s2)->s1.getName().compareTo(s2.getName()));
    }
}

★多线程

5.试分析下列程序。

public class Test {
    private static long total = 0L;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for(int i = 1;i <= 10000;i++) total += i;
        });
        t.start();
        System.out.println("Total=" + total);
    }
}

当尝试使用线程计算1到10000的累加时,几乎每次都得不到正确的结果,试分析原因,并给出修改的方法。

【解析】

1)分析原因:total为共享变量,被多个线程访问,可能存在主线程比创建的新线程跑得快,在创建的新线程还没跑完,主线程就已经跑完并且输出了total的值,导致total值输出不正确

2)修改方法:使用join方法,让主线程在创建的线程跑完以后再输出total的值

public class Test {
    private static long total = 0L;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for(int i = 1;i <= 10000;i++) total += i;
        });
        t.start();
        try {
           t.join();
        }catch(InterruptedException e){
           e.printStackTrace();
        }
        System.out.println("Total=" + total);
    }
}

6. 试分析下列程序。

public class Test {
    private static long total = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for(int i = 1;i <= 50001;i++) total += i;
        });
        Thread t2 = new Thread(()->{
            for(int i = 50001;i <= 100000;i++) total += i;
        });
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Total=" + total);
    }
}

当尝试使用2个线程协作计算1到100000的累加时,几乎每次都得不到正确的结果,试分析原因,并给出修改的方法。

【解析】

分析原因:存在竞争条件,当两个线程要同时访问并修改共享变量total的时候,程序中没有适当的同步机制来确保这种访问是安全的。具体来说就是可能出现两个线程几乎同时读取total的值,对其进行增加操作后再写回时,可能会覆盖彼此的操作,导致一些增加操作被丢失,从而导致最终结果错误

修改方法:使用同步块,确保每次访问total变量时都使用相同的锁对象进行同步,这样可以有效避免多个线程同时修改total的值导致程序结果错误的问题

public class Test  {
    private static long total = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 线程1
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <= 50000; i++) {
                synchronized (lock) {
                    total += i;
                }
            }
        });

        // 线程2
        Thread t2 = new Thread(() -> {
            for (int i = 50001; i <= 100000; i++) {
                synchronized (lock) {
                    total += i;
                }
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("Total: " + total);
    }
}

进一步改进:

public class Test1{
    private static long total = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 线程1
        Thread t1 = new Thread(() -> {
            long localSum = 0;
            for (int i = 1; i <= 50000; i++) {
                localSum += i;
            }
            synchronized (lock) {
                total += localSum;
            }
        });

        // 线程2
        Thread t2 = new Thread(() -> {
            long localSum = 0;
            for (int i = 50001; i <= 100000; i++) {
                localSum += i;
            }
            synchronized (lock) {
                total += localSum;
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Total: " + total);
    }
}

实现线程的2种方式

  1. 继承Thread类

创建MyThread类(继承Thead类)->重写run()方法->创建MyTread类的实例化对象t1->调用t1.start()方法启动线程

每个线程对应一个MyThread的实例

public class 继承Thread类 {
    //创建MyThread类继承Thread类
    static class MyThread extends Thread{
        //重写run方法
        public void run(){
            System.out.println("线程执行了");
        }
    }
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread t1=new MyThread();
        //调用start方法
        t1.start();
    }
}
  1.       2.实现Runnable接口(常用)

  2. 创建MyThread类(实现Runnable接口)->重写run()方法->将Runnable对象传给Thread类的构造函数来创建线程
  3. public class 实现Runnable接口 {
        static class MyThread implements Runnable{
            @Override
            public void run() {
                System.out.println("实现Runnable接口");
            }
        }
        public static void main(String[] args) {
            //创建Thread对象
            Thread t1=new Thread(new MyThread());
            //调用start方法
            t1.start();
        }
    }

注:只能调用start()方法启动线程

  线程的几个重要方法

1.start() 启动线程并调用线程的run()方法。每个线程只能调用一次start()方法

注:直接调用run方法不会启动新线程,它只是会像普通方法一样去执行

2.join() 

  • 当前线程等待调用join()方法的线程执行完毕后再继续执行。
  • 可以指定等待时间,如果超过指定时间,当前线程将继续执行。
  • 如果不指定时间,当前线程将一直等待,直到调用join()方法的线程结束。
  • 同样可能抛出InterruptedException

3.Thread.sleep(long mills) 

  • 使当前线程暂停执行指定的时间,并交出CPU执行权。
  • 不会释放对象锁,其他线程无法访问该线程持有的同步资源。
  • 时间结束后,线程自动恢复到可运行状态,但不一定立即执行,需要等待CPU调度。
  • 可能会抛出InterruptedException,如果线程在睡眠过程中被中断。

4.Thread.yield()

  • 暂停当前正在执行的线程,让同等优先级或更高优先级的线程获得执行机会。
  • 不会释放对象锁,与sleep()类似,但不会阻塞线程,而是让线程重回就绪状态。
  • 使用yield()后,线程需要与其他线程重新争夺CPU资源。

下列(  A   )方法无法使线程从运行状态进入到非运行状态

A. notify      B. wait      C. sleep      D. yield

补充:线程的生命周期

1、新建状态(new):新建一个线程对象

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行的线程池中,变得可运行,等待获取CPU的使用权

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

synchronized关键字

锁的类型

  1. 类锁 (synchronized(Main.class))

用于锁定整个类,适用于保护静态成员或当多个线程需要协调对相同资源的访问时

  1. 实例锁 (synchronized(this))

锁定当前对象,适用于保护非静态成员,确保同一时间只有一个线程可以访问该对象的方法或属性

  1. 对象锁 锁定某些特定的操作或者代码块

private final Object lock =new Object();

synchronized(lock){//同步代码块;}

  public class Test {
    private static long total=0;
    private static final Object lock=new Object();

    public static void main(String[] args) throws InterruptedException{

        // 线程1
        Thread t1=new Thread(()->{
            synchronized (lock){
                for(int i=1;i<50000;i++){
                    total+=i;
                }
            }
        });

        // 线程2
        Thread t2=new Thread(()->{
            synchronized(lock){
                for(int i=50000;i<100000;i++){
                    total+=i;
                }
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Total"+total);

    }

}

wait()和notify()  属于Object类

wait() 使当前线程等待,直到其他线程对象调用该对象的notify()或notifyAll()方法

notify()随机唤醒一个等待该对象锁的线程

notifyAll()唤醒所有等待的线程

注:这些方法都必须在synchronized代码块或者方法中调用,因为他们涉及到对象锁的操作

生产者-消费者模型

7. 生产者-消费者模型

使用多线程模拟生产者-消费者模型。已知物品容量为10,生产者每2秒生产5个产品,而消费者每秒消耗2个产品。假定生产者生产时,消费者不能消费;同样消费者消费时,生产者不能生产。请使用wait/notify模拟该模型。

//生产者-消费者模型
/*问题描述:
已知仓库容量为10
生产者每2秒生产5个产品,消费者每1秒消费2个产品
规定:生产者生产时,消费者不能消费;同理,消费者消费时,生产者不能生产
请用wait()/notify()描述该模型
 */
public class Test {
    public static void main(String[] args){
        Store store=new Store();
        Thread producer=new Thread(()->{
            while(true){
                try{
                    store.produce();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        Thread consumer=new Thread(()->{
            while(true){
                try{
                    store.consume();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        producer.start();
        consumer.start();
    }
}
public class Store {
    //数量
    private int count;
    //最大数量
    private static final int MAX=10;
    //设置起始状态
    private boolean isProducing =true;

    //生产者方法
    //使用synchronized关键字确保同一时间只有一个线程可以执行此方法
    public synchronized void produce() throws InterruptedException {
        while(true){
            if(!isProducing){//没有在生产
                this.notify();//唤醒等待的线程(消费者)
                this.wait();//调用时,生产者线程进入waiting队列
                continue;
            }
            count=count+5;
            System.out.println("生产者生产了5个产品,当前库存:"+count);
            Thread.sleep(2000);
            if(count>=MAX){
                isProducing=false;//结束生产
            }
        }
    }

    //消费者
    public synchronized void consume() throws InterruptedException{
        while(true){
            if(isProducing){//没有消费(没有商品了,无法消费)
                this.notify();//唤醒等待的线程(生产者)
                this.wait();//调用时,消费者线程进入waiting队列
                continue;
            }
            count=count-2;
            System.out.println("消费者消费了2个产品,当前库存:"+count);
            Thread.sleep(1000);
            if(count<=0){
                isProducing=true;//结束消费
            }
        }
    }
}

哲学家就餐问题

6. 哲学家就餐问题(wait/notify)

哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题。

一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。

请尝试使用多线程模拟,确保不会出现“饿死”状态。

说明:本题有很多解法,但优先使用wait/notify模型,绘制类图

【解析】

哲学家类 (Phy)

package 多线程.哲学家就餐问题;

public class Phy implements Runnable{
    private Servant servant;
    private int id;
    public Phy(int id,Servant servant){
        this.id=id;
        this.servant=servant;
    }

    public void run(){
        while(true){
            try{
                //System.out.println("哲学家:"+id+"在思考.....");
                //Math.random() 会生成一个范围在[0.0,1.0)的随机double数值
                Thread.sleep((int) (1+Math.random()*5)*1000);

                servant.take(id);
                System.out.println("哲学家:"+id+"在吃饭.....");
                Thread.sleep((int)(1+Math.random()*5)*1000);

                servant.put(id);

            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

服务类(Servant)

package 多线程.哲学家就餐问题;

public class Servant {
    private boolean[] chops;
    private int n;

    public Servant(int n){
        this.n=n;
        //n个筷子
        chops =new boolean[n];//默认值是false(这里表示所有筷子都未被占用)
    }

    /*synchronized 作用
    1.锁定对象:当一个线程调用take方法时,它会获取this对象的锁。
    也就是说,同一个时刻只能有一个线程在执行take方法中的代码,其他线程必须等当前线程执行完成后才能执行
    2.保证互斥:要是有多个线程同时调用take方法,Java会确保只有一个线程能在某个时刻执行方法的内容。
     */
    //要拿筷子
    public synchronized void take(int pid)throws InterruptedException{
        //如果筷子已经被占用,则进入等待
        while(chops[pid]||chops[(pid+1)%this.n]){
            /*唤醒其他线程:具体来说就是,当前线程的条件不满足要执行拿筷子这个操作的所需要的条件,这个线程就不执行了,一边等着去,
                         然后在这个线程里面调用notify(),去唤醒其他正在等待使用这个方法的线程(因为这个方法已经被整个锁起来了),让它们来调用这个方法
            */
            notify();
            wait();//让当前的线程进入等待状态,直到其他线程通过notify()唤醒它
        }
        //如果筷子没有被占用,那就赋值true,表示这个筷子被当前调用take方法的这个哲学家给拿了
        chops[pid]=true;
        chops[(pid+1)%this.n]=true;
    }

    //放下筷子(前提是调用这个方法的哲学家拿到了筷子)
    //哲学家拿到筷子吃完之后就自然的放下了筷子
    public synchronized void put(int pid){
        chops[pid]=false;
        chops[(pid+1)%this.n]=false;
        notify();//唤醒其他也已经吃完饭了,但是还没有放下筷子,还在等待拿到该方法使用权的线程
    }
}

测试类

package 多线程.哲学家就餐问题;

/*哲学家就餐问题
问题表述:
一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。--->圆桌 5个哲学家,5只筷子
哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。
述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。
请尝试使用多线程模拟,确保不会出现“饿死”状态。
wait/notify模型实现
 */

public class Test {
    public static void main(String[] args){
        final int  N=5;//5个哲学家
        Servant servant=new Servant(5);
        for(int i=0;i<N;i++){
            new Thread(new Phy(i,servant)).start();
        }
    }
}

并发计算模拟

5. 并发计算模拟

计算从1到1亿整型数相加。要求使用并发程序处理,即采用多线程实现,在主线程中将计算结果累加(不能使用累加公式)。

(1)编写SumWorker类,实现Runnable接口,计算从m到n的和,其中m,n由构造方法传入;绘制类图

(2)在主程序中每次开启若干个SumWorker线程(具体数量由程序指定或者用户输入),计算完成之后,将部分结果累加,然后再启动另一批线程,直到计算完成;注意并非所有线程都同时启动,允许分批执行

【解析】

SumWorker类

package 多线程.并发计算模拟;

public class SumWorker implements Runnable{
    private final int from,to;

    //构造方法
    public SumWorker(int from,int to){
        this.from=from;
        this.to=to;
    }

    public void run(){
        long localSum=0;
        for(int i=from;i<=to;i++){
            localSum=localSum+i;
        }

        //使用类锁来保证线程安全
        //sum 是静态变量,属于类本身 而不是类的对象
        synchronized(Test.class){
            Test.sum+=localSum;
        }
    }
}

Test类

package 多线程.并发计算模拟;

//计算从1到1亿的整数和

import java.util.ArrayList;
import java.util.List;

public class Test {

    //sum 属于Test类
    //sum是线程共享的数据
    //要确保同一时间只有一个线程在修改sum的值
    public static long sum=0;

    //当我们知道某个方法可能会抛出异常的时候,但是不想在方法内部处理,就可以使用throws关键字来声明该方法可能抛出的异常
    //t.join ---->InterruptedException
    public static void main(String[] args) throws InterruptedException{
        int start=1;
        int end=100000000;
        //手动设置线程数量
        int threadCount=10;

        //List 是一个接口(Collection集合里面的一个接口) 代表的是有序的集合(列表),允许重复元素
        //ArrayList是实现List接口的具体类,提供了基于数组实现的动态列表
        List<Thread> threads=new ArrayList<>();
        //分配任务给线程
        int step=(end-start+1)/threadCount;
        int remainder=(end-start+1)%threadCount;//剩下的
        int s=start;
        for(int i=1;i<=threadCount;i++){
            int e=s+step-1;
            if(i==threadCount){
                //讲剩下的数分配给最后一个线程
                end+=remainder;
            }

            //Thread t=new Thread(实现Runnable接口的实例)
            Thread t=new Thread(new SumWorker(s,e));
            threads.add(t);
            t.start();
            s=e+1;
        }

        //等待所有线程完成
        for(Thread t:threads){
            t.join();//主线程等待子线程结束
        }

        //输出结果
        System.out.println("Sum="+sum);
    }
}

编程题

筛法求素数

1. 筛法求素数

编写一个程序,从键盘读取正整数n,使用筛法求不大于n的所有素数(或称质数,Prime Number),并逐个打印出来。输出结果为:

n=10

The prime numbers are 2,3,5,7

具体做法是:给出要筛数值的范围n,先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个素数5筛,把5留下,把5的倍数剔除掉;不断重复下去……

(1)编写函数

编写public static void printPrimeNumbers(int[] n)方法,将不大于n的每个素数输出;

编写public static int[] getPrimeNumbers(int n)方法,将每个不大于n的素数放入数组,并返回。

(2)编写测试代码

在main方法中输入n,调用getPrimeNumbers方法获取素数数组,再调用printPrimeNumbers方法输出这些素数。

【解析】

public class 筛法求素数 {
    public static void main(String[] args){
        printPrimeNumbers(getPrimeNumbers(10));
    }

    public static int[] getPrimeNumbers(int n){
        int[] temp=new int[n+1];
        temp[0]=-1;
        temp[1]=-1;
        for(int i=2;i<=n;i++){
            temp[i]=0;
        }
        int count=0;//记录n以内的素数总数
        for(int i=2;i<=n;i++){
            if(temp[i]==0){
                count++;
                for(int j=i*2;j<=n;j=j+i){
                    temp[j]=-1;
                }
            }
        }

        int[] result=new int[count];
        int k=0;
        for(int i=0;i<temp.length;i++){
            if(temp[i]!=-1){
                result[k]=i;
                k++;
            }
        }
        return result;
    }

    public static void printPrimeNumbers(int[] result){
        System.out.print("The prime numbers are ");
        int length=result.length;
        for(int i=0;i<length;i++){
            System.out.print(result[i]+" ");
        }
    }
}

二次方程求解

2. 二次方程式求解

为二次方程ax2+bx+c=0设计一个名为QuadraticEquation的类。这个类包括:

(1)代表三个系数的私有数据成员a,b和c;

(2)一个参数为a,b,c的构造方法;

(3)一个名为getDiscriminant()的方法,返回判别式:b2-4ac

(4)名为getRoot1()和getRoot2()的方法,返回二次方程的两个根:r1=(-b+(b2-4ac)1/2)/2ar2=(-b-(b2-4ac)1/2)/2a。这两个方法只有在判别式为非负数时才有用。

实现这个类。编写一个测试程序,提示用户输入a,b和c的值,然后显示判别式的值。如果判别式为正数,显示两个根;如果判别式为0,则显示一个根,否则显示"The equation has no roots."字样。

【解析】

同一个包下

QuadraticEquation类

public class QuadraticEquation {
    private double a,b,c;
    public QuadraticEquation(double a,double b,double c){
        this.a=a;
        this.b=b;
        this.c=c;
    }
    public double getDiscriminant(){
        return b*b-4*a*c;
    }
    public double getRoot1(){
        // (-b+Math.sqrt(getDiscriminant()))/2*a; 不能这样写 这样写会先/2 在整体*a
        return (-b+Math.sqrt(getDiscriminant()))/(2*a);
    }
    public double getRoot2(){
        return (-b-Math.sqrt(getDiscriminant()))/(2*a);
    }
}

Test类

import java.util.Scanner;
public class Test {
    public static void main(String[] args){
        //获取a,b,c的值
        Scanner scanner=new Scanner(System.in);
        double a,b,c;
        System.out.println("请输入a,b,c的值:");
        a=scanner.nextDouble();//记得最后有个()
        b=scanner.nextDouble();
        c=scanner.nextDouble();
        //求解
        QuadraticEquation q=new QuadraticEquation(a,b,c);
        double temp=q.getDiscriminant();
        System.out.println("判别式的值为:"+temp);
        if(temp>0){
            System.out.println("root1="+q.getRoot1()+","+"root2="+q.getRoot2());
        }
        else if(temp==0){
            System.out.println("root="+q.getRoot1());
        }
        else{
            System.out.println("The equation has no roots");
        }
        //关闭Scanner对象
        scanner.close();
    }
}

矩阵四则运算

3. 矩阵四则运算

定义矩阵类Matrix,包括:

(1)代表矩阵的行数rows(或m)、列数cols(或n),以及二维数组data;

(2)一个参数为rows,cols的构造方法,实现初始化操作,并将矩阵元素全部置为0;

(3)public void setElement(int row, int col, double value);方法,用于设置第row行,第col列元素的值;

(4)public Matrix add(Matrix m);方法,实现当前矩阵与m矩阵相加,并返回新的矩阵;若无法相加,则返回null;

(5)public Matrix minus(Matrix m);方法,实现当前矩阵减去m矩阵,并返回新的矩阵;若无法相减,则返回null;

(6)public Matrix multiply(Matrix m);方法,实现当前矩阵乘以m矩阵,并返回新的矩阵;若无法相乘,则返回null;

(7)public Matrix transpose();方法,实现矩阵转置,并返回新的矩阵;

(8)public void display();方法,打印当前矩阵。

实现该类。编写一个测试程序,随机生成矩阵元素或者由程序中用常量设置(可不必由键盘输入),测试上述四则运算,打印运算结果。

【解析】

同一个包下

Matrix类

//矩阵类
public class Matrix {
    private int rows;
    private int cols;
    private double[][] data;

    //构造方法
    //无参
    public Matrix(){};
    //有参
    public Matrix(int rows,int cols){
        this.rows=rows;
        this.cols=cols;
        this.data=new double[rows][cols];
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                data[i][j]=0;
            }
        }
    }

    //获取矩阵特定行,特定列的元素
    public double getElement(int row,int col) {
        if (0 <= row && row < rows && 0 <= col && col < cols) {
            return data[row][col];
        }
        return -1;
    }

    //设定矩阵特定行,特定列的元素的值
    public void setElement(int row,int col,double value){
        if(0<=row&&row<rows&&0<=col&&col<cols) {
            this.data[row][col] = value;
        }
    }

    //实现矩阵相加
    //要记得先判定能不能加
    //注意方法的返回值类型:Matrix
    public Matrix add(Matrix m){
        if(m.rows==rows&&m.cols==cols){
            Matrix result=new Matrix(rows,cols);
            for(int i=0;i<rows;i++){
                for(int j=0;j<cols;j++){
                    result.data[i][j]=data[i][j]+m.data[i][j];
                }
            }
            return result;
        }
        return null;
    }

    //实现两矩阵相减
    public Matrix minus(Matrix m){
        if(m.cols==cols&&m.rows==rows){
            Matrix result=new Matrix(rows,cols);
            for(int i=0;i<rows;i++){
                for(int j=0;j<cols;j++){
                    result.data[i][j]=data[i][j]-m.data[i][j];
                }
            }
            return result;
        }
        return null;
    }

    //实现两矩阵相乘
    public Matrix multiply(Matrix m){
        if(cols==m.rows){
            Matrix result=new Matrix(rows,m.cols);
            for(int i=0;i<rows;i++){
                for(int j=0;j<m.cols;j++){
                    double sum=0;
                    for(int k=0;k<cols;k++) {
                        sum = sum + data[i][k] * m.data[k][j];
                    }
                    result.data[i][j]=sum;
                }
            }
            return result;
        }
        return null;
    }

    //实现矩阵的转置
    public Matrix transpose(){
        Matrix result=new Matrix(cols,rows);
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                result.data[j][i]=data[i][j];
            }
        }
        return result;
    }

    public void display(){
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                System.out.print(data[i][j]+" ");
            }
            System.out.println();
        }
        System.out.println();
    }
}

Test类

public class Test {
    public static void main(String[] args){
        //矩阵1
        Matrix m1=new Matrix(2,2);
        m1.setElement(0,0,1);
        m1.setElement(0,1,1);
        m1.setElement(1,0,2);
        m1.setElement(1,1,2);
        m1.display();
        //矩阵2
        Matrix m2=new Matrix(2,2);
        m2.setElement(0,0,2);
        m2.setElement(0,1,2);
        m2.setElement(1,0,1);
        m2.setElement(1,1,1);
        m2.display();

//        m1.display();
//        m2.display();
//        (m1.add(m2)).display();
//        (m1.minus(m2)).display();
//        (m1.multiply(m2)).display();
//        (m1.transpose()).display();
//        (m2.transpose()).display();

        //记得做非空检查
        Matrix result=new Matrix();
        //加法
        result=m1.add(m2);
        if(result!=null){
            result.display();
        } else{
            System.out.println("当前两矩阵无法实现相加操作!");
        }
        //减法
        result=m1.minus(m2);
        if(result!=null){
            result.display();
        }else{
            System.out.println("当前两矩阵无法实现减法操作!");
        }
        //乘法
        result=m1.multiply(m2);
        if(result!=null){
            result.display();
        }else{
            System.out.println("当前矩阵无法实现乘法操作!");
        }

        //转置
        (m1.transpose()).display();
        (m2.transpose()).display();

    }

Shape

9. Shape

(1)定义抽象类Shape(图形),定义常量PI,添加两个抽象方法: double getPerimeter()和double getArea(),分别用于求图形的周长和面积。

(2)编写Rectangle(长方形)、Triangle(三角形)和Circle(圆形),均继承自Shape类;为各个子类添加合适的成员、构造方法和getters/setters。

(3)实现3个子类的求周长、面积的方法。

(4)添加默认比较器,实现按面积的降序排序。

(5)编写一个测试类,在测试类中添加若干个Rectangle, Triangle, Circle实例(个数及内容自定),并在测试类中定义并测试如下方法:

①编写方法private static void printPerimeter(Shape[] shapes),打印出每个图形的周长;

②编写方法private static void sortByArea(Shape[] shapes),实现对面积的降序排序;

➂编写方法private static void sortByPerimeter(Shape[] shapes),实现对周长的升序排序。

附:海伦公式求三角形面积。已知边a,b,c的三角形,其面积公式如下:

其中,p=(a+b+c)/2

【解析】

抽象类Shape

package B9;

import java.util.Comparator;

//定义抽象类Shape
public abstract class Shape {
    public static final double PI=3.14;

    public abstract double getPerimeter();
    public abstract double getArea();

    //默认比较器,实现按面积的降序排序
    public static final Comparator<Shape>  areaComparator=new Comparator<>(){
        public int compare(Shape s1,Shape s2){
            return Double.compare(s2.getArea(),s1.getArea());
        }
    };

    //默认比较器,实现按周长的升序排序
    public static final Comparator<Shape> perimeterComparator=new Comparator<Shape>(){
        public int compare(Shape s1,Shape s2){
            return Double.compare(s1.getPerimeter(),s2.getPerimeter());
        }
    };
}

长方形类Rectangle

package B9;

public class Rectangle extends Shape{
    private double x;
    private double y;
    private double h;

    public Rectangle(double x,double y,double h){
        this.x=x;
        this.y=y;
        this.h=h;
    }

    //setter和getter方法
    public void setx(double x){
        this.x=x;
    }


    @Override
    public double getPerimeter() {
        return 4*(x+y+h);
    }

    @Override
    public double getArea() {
        return 2*(x*y+x*h*y*h);
    }
}

三角形类Triangle

package B9;

public class Triangle extends Shape{
    private double a;
    private double b;
    private double c;

    public Triangle(double a,double b,double c){
        this.a=a;
        this.b=b;
        this.c=c;
    }

    public double getPerimeter(){
        return a+b+c;
    }

    public double getArea(){
        double p=(a+b+c)/2;
        double result= Math.sqrt(p*(p-a)*(p-b)*(p-c));
        return result;
    }
}

圆形类Circle

package B9;

public class Circle extends Shape {
    private double r;

    public Circle(double r){
        this.r=r;
    }

    public double getPerimeter(){
        return PI*r*2;
    }

    public double getArea(){
        return PI*r*r;
    }
}

Test类

package B9;

import java.util.Arrays;

public class Test {
    public static void main(String[] args){

        //长方形
        Rectangle r1=new Rectangle(1,1,1);
        Rectangle r2=new Rectangle(1,1,2);
        Rectangle r3=new Rectangle(1,2,2);
        //三角形
        Triangle t1=new Triangle(1,1,1);
        Triangle t2=new Triangle(1,1,2);
        Triangle t3=new Triangle(1,2,2);
        //圆形
        Circle c1=new Circle(1);
        Circle c2=new Circle(2);
        Circle c3=new Circle(3);

        Shape[] shapes=new Shape[9];
        shapes[0]=r1;shapes[1]=r2;shapes[2]=r3;
        shapes[3]=t1;shapes[4]=t2;shapes[5]=t3;
        shapes[6]=c1;shapes[7]=c2;shapes[8]=c3;

        printPerimeter(shapes);
        System.out.println();

        sortByArea(shapes);
        printPerimeter(shapes);
        System.out.println();

        sortByPerimeter(shapes);
        printPerimeter(shapes);

    }

    private static void printPerimeter(Shape[] shapes){
        for(Shape shape:shapes){
            System.out.println("Perimeter:"+shape.getPerimeter());
        }
    }




    private static void sortByArea(Shape[] shapes){
        //Arrays.sort方法接受两个参数:一个是待排序的数组,另一个是用于定义排序规则的Comparator
        Arrays.sort(shapes,Shape.areaComparator);
    }
    private static void sortByPerimeter(Shape[] shapes){

        Arrays.sort(shapes,Shape.perimeterComparator);
    }
}

完全数(Perfect number)

10. 完全数(Perfect number)

完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数。它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身。

请编写程序,输出n以内所有完全数(n由控制台输入)。

【解析】

package B10;

import java.util.Scanner;

public class PerfectNumbers {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入一个正整数 n: ");
        int n = scanner.nextInt();

        System.out.println("n 以内的完全数有:");
        for (int i = 1; i <= n; i++) {
            if (isPerfectNumber(i)) {
                System.out.println(i);
            }
        }
    }

    // 判断一个数是否为完全数
    public static boolean isPerfectNumber(int number) {
        int sum = 0;
        for (int i = 1; i <= number / 2; i++) {
            if (number % i == 0) {
                sum += i;
            }
        }
        if( sum == number) return true;
        else  return false;
    }
}

约瑟夫环问题

8. 约瑟夫环(Josephus Ring)

编号为1到n的n个人围成一圈。从编号为1的人开始报数,报到m的人离开。下一个人继续从1开始报数。n-1轮结束后,只剩下一个人,问最后留下的人编号是多少?

package B8;

import java.util.LinkedList;
import java.util.Scanner;

public class Test {

    public static int findLastPerson(int n, int m) {
        // 创建一个 LinkedList,存储编号为 1 到 n 的所有人
        LinkedList<Integer> circle = new LinkedList<>();
        for (int i = 1; i <= n; i++) {
            circle.add(i);
        }

        // 当前报数的索引
        int index = 0;

        // 循环直到只剩一个人
        while (circle.size() > 1) {
            // 计算报数到第 m 个人的索引
            index = (index + m - 1) % circle.size();
            // 删除该人
            circle.remove(index);
        }

        // 返回最后剩下的人的编号
        return circle.get(0);
    }

    public static void main(String[] args) {
        // 从键盘输入 n 和 m
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入人数 n: ");
        int n = scanner.nextInt();
        System.out.print("请输入报数的间隔 m: ");
        int m = scanner.nextInt();

        // 求解并输出最后剩下的人的编号
        int result = findLastPerson(n, m);
        System.out.println("最后剩下的人的编号是: " + result);

        scanner.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A林玖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值