Java:基础知识——数据类型、关键字、集合、IO流、类、线程(生产者与消费者)、模式

一、数据类型

  1、 四类八种:
取值范围小的可向大的转,大转小会失去精度

类型 名称 占用字节 取值范围 默认值 包装类
整形 byte 字节型 1(8 Bit) -2^7 ~ 2^7-1 0 Byte
short 短整型 2(16 Bit) -2^15 ~ 2^15-1 0 Short
int 整形 4(32 Bit) -2^31 ~ 2^31 - 1 0 Integer
long 长整型 8(64 Bit) -2^63 ~ 2^63-1 0L Long
浮点型 float 浮点型 4(32 Bit) (7位有效数字) 0.0f Float
double 双精度浮点型 8(64 Bit) (16位有效数字) 0.0d Double
字符型 char 字符型 2(16 Bit) \u0000 ~ \uFFFF \u0000 Character
布尔型 boolean 布尔型 1/8(1 Bit) false、true false Boolean

  2、String 类:字符串,由英文双引号括起来的由0到多个数字、字母、字符共同组成的一个串,可赋值为null。而char是由英文单引号括起来的1个数字或字符或字母。


二、访问权限修饰符、通配符

  1、public:类内部、同个package、子类、任何地方
  2、protected:类内部、同个package、子类。即可以被子类和同一个包内的类访问。
  3、default:类内部、同个package。即只可以被同一个包内的类访问。
  4、peivate:类内部。即只可以被该类内部自己访问。
  5、通配符:使用问号 " ? " 表示所有类型

class Test {
      public void showInfo(List list) {
         
      }
}

  6、有限制的通配符:例如泛型为,表示只允许泛型类型为Person或Person子类的引用调用


三、关键字this、super、static、final、abstract

  1、this:表示引用本类对象的属性、方法。先从自己的类中引用,若找不到,如果有父类,则会从父类里找。

  2、super:子类用super来调用父类的属性、方法或构造器,可用于区分子父类之间同名的成员。super可以一直向上追溯多个层级的父类。

  3、static:类变量(静态变量),不用通过实例化就能调用,直接 类名.该关键字变量名(Person.country),被所有这个类的实例化对象所共享。可修饰属性、方法、代码块和内部类。

public class Person {
    static Stirng country;
    int age;
    Stirng name;

    public static void getInfo() {
       ······
    }
}

  4、final:标记的变量为常量,只能被赋值一次,名称大写。标记的类不能被继承,标记的方法不能被重写

  5、abstract:修饰类或方法,成为抽象类或抽象方法

四、集合、迭代、泛型

  1、Map:具有映射关系的集合,保存具有映射关系的数据,即 key-value对 一对一
        1-①:HashMap是Map的接口的典型实现

Map map = new HashMap();

        1-②:TreeMap对所有的 key-value对 处于有序状态,默认为自然排序

  2、List:有序,可重复的集合
        2-①:ArrayList是List的接口的典型实现

List list= new ArrayList();

  3、Set:无序、不可重复的集合
        3-①:HashSet是Set接口的典型实现,存放值时根据值的hashcode值来决定存放位置。集合中的元素可以为null。

Set set = new HashSet();

        3-②:TreeSet是SortSet接口的实现类,它会调用集合元素的compareTo(Object obj)方法,确保集合元素处于排序状态。必须放入同样类的对象。默认为自然排序,升序排列。

Set set = new TreeSet();

  4、foreach 迭代集合:使用格式为

for(Object obj : 要迭代的对象) {
      System.out.print(obj);
}

  5、Iterator 迭代器遍历集合:使用格式为

Iterator it = set.iterator();
while(it.hashNext()) {
       System.out.print(it.next());
}

  6、foreach与Iterator的区别:Iterator 提供可在遍历时删除集合内元素的方法。

  7、泛型<类/接口>:让集合只能存放相同类型的对象/类(若是数据类型用Integer、String之类的)。

Set set = new HashSett();

interface Generator {
      T test(T t);
}

五、IO(字节流xxxStream与字符流xxxReader/xxxWriter)

flish():关于 flush() 的含义,清空缓冲区。

  1、File类:能新建、删除和重命名文件和目录。(如需要访问文件内容则需要输入/输出流)
  2、Input:计算机把外部数据(磁盘、光盘等储存设备)读到自己的代码程序中为“输入”。(计算机把外部数据输入到自己的程序中)

  3、Output:计算机把代码程序中的数据写到磁盘、光盘等储存设备中为“输出”。(计算机把程序的数据输出到磁盘中)

  4、文件流(数据流的读写是基于文件的操作)计算机与硬盘之间的io操作,读写较慢
        4-①:FileInputStream 字节输入流

public static void main(String[] args) {
    try {
          FileInputStream in = new FileInputStream("文件位置");  
          // 把文件字节输入流放到缓冲字节输入流对象里
          // BufferedInputStream bis = new BufferedInputStream(in);

          // 读取的是字节
          byte[] b = new byte[100];
          in.read(b);
          // 从磁盘中读取xx个字节的数据进入到程序中
          System.out.println(new String(b));

          // 最晚开的最早关
          // bis.cloce(); 
          in.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

        4-②:FileOutputStream 字节输出流

public static void main(String[] args) {
    try {
          // 后面 true 为追加模式(append),避免直接覆盖
          FileOutputStream out = new FileOutputStream("文件位置", true);
          // 把文件字节输入流放到缓冲字节输入流对象里
          // BufferedOutputStream bos = new BufferedOutputStream(out);
          String str = "一段文本";
  
          // 从程序中输出字节到磁盘中保存
          out.write(str.getBytes());
          out.flush();
          // 最晚开的最早关
          // bos.cloce(); 
          in.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

        4-③:FileReader 字符输入流

public static void main(String[] args) {
    try {
          FileReader fr = new FileReader("文件位置");
          // 把文件字节输入流放到缓冲字节输入流对象里
          // BufferedReader br= new BufferedReader (fr);

          // 读取的是字符
          char[] c = new char[45];
          fr.read(c);
          System.out.println(new String(c));
          // 最晚开的最早关
          // br.cloce();
          fr.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

        4-④:FileWriter 字符输出流

public static void main(String[] args) {
    try {
          FileWriter fw = new FileWriter("文件位置", true);
          // 把文件字节输入流放到缓冲字节输入流对象里
          // BufferedWriter bw= new BufferedWriter(fw);
          String str = "一段文本";

          fw.write(str);
          fw.flush();
          // 最晚开的最早关
          // bw.cloce();
          fw.close();
    } catch (Exception e) {
          e.printStackTrace();
    }
}

  5、缓冲流(数据流的读写是基于内存的操作)把数据先缓冲进内存里,能提高数据的读写速度
        5-①BufferedInputStream
        5-②BufferedOutputStream
        5-③BufferedReader
        5-④BufferedWriter

  6、转换流(字节流、字符流之间转换)
        6-①InputStreamReader:字节输入流转换成字符输入流
例:

FileInputStream fis = new FileInputStream("文件位置");
InputStreamReader isr = new InputStreamReader(fis, "编码格式");

        6-②OutputStreamWriter:字节输出流转换成字符输出流

FileOutputStream fos = new FileOutputStream("文件位置");
OutputStreamWriter isr = new OutputStreamWriter(fos, "编码格式");

  7、随机存取流
        7-①:RandomAccessFile类:程序可随机跳到文件的任意地方来读写文件

// "r":只读
RandomAccessFile ra = new RandomAccessFile("文件位置", "r");
// 文件起始点从零开始
ra.seek(0);

// 设置读取字符串的长度
bytep[] b = new byte[100];
System.out.print(new String(b));

  8、对象流
        8-① 序列化 Serialize 将对象转化成字节流储存在硬盘中。用 ObjectInputStream类将一个Java对象(该java对象必须实现Serializable接口)写入IO流中,凡是实现 Serializable 接口的类,都有一个序列化版本标识符的静态变量。若要序列化和反序列化同一个对象,则要保证serialVersionUID是相同的
实现序列化接口的对象 Person.java

import java.io.Serializable;

public class Person implements Serializable{
    // serialVersionUID  用来表明类的不同版本,若不自定义则会自动随机生成
    private static final long serialVersionUID = 1L;
    int age;
    String gender;
    String name ;

    public Person() {}
    public Person(int age, String gender, String name) {
        this.age = age;
        this.gender = gender;
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    public String getGender() {
        return gender;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public void setName(String name) {
        this.name = name;
    }
}

序列化

public static void main(String[] args) {
    Person p = new Person(25, "男", "张三");
    try{
        // 用对象流将java对象转换为字节流,并输出到指定文件位置
        FileOutputStream fos = new FileOutputStream("文件位置");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        // 对象流将对象写入文件中
        oos.writeObject(p);
        // System.out.println(p.getName());
        oos.flush();
        oos.close();
    }catch(Exception e){
        e.getStackTrace();
    }
 }

        8-② 反序列化 Deserialize将字节流转化成对象。用ObjectOutputStream类从IO流中恢复该Java对象。
反序列化

public static void main(String[] args) {
    try{
        // 计算机读取(输入)文件,用对象流将字节流转换为java对象,并保存到对应类型(强制转换)的类中
        FileInputStream fis = new FileInputStream("文件位置");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person p = (Person)ois.readObject();
        ois.close();
        System.out.println("反序列化:" + p.getName());
    }catch(Exception e){
        e.getStackTrace();
    }
 }

六、类

  1、方法重载(同一个类里)
        方法名相同,但参数个数不同、参数的数据类型不同或者返回值的数据类型不同。

  2、方法重写(子类对父类方法的覆盖)
        子类对父类的方法重写,只能重写方法体的代码(不能使用更严格的访问权限、抛出的异常不能大于父类被重写方法的异常、同为static或!static)。

  3、初始化块{······}(无static修饰)
        执行顺序:静态代码块(只执行一次)-->初始化块(每次实例化一个对象都执行一次)-->构造方法,按顺序执行多个初始化块。

  4、抽象类 abstract
        特征通用的父类,不能被实例化,里面的方法必须被子类重写,方法体由子类提供,且重写方法时方法上面需添加 @override

  5、接口 interface
        一个类里可实现多个接口,接口可继承其他接口,实现接口类用 class xxx implements 接口类名{}。接口中所有的成员变量默认都是public static final修饰的,所有方法默认都是public abstract修饰的(这些修饰符可忽略不写),接口没有构造器。

抽象类与接口的区别:抽象是对一类事物的高度抽象,包括属性和方法,形成一个父类;接口是对一系列动作的抽象,实现相应的接口即为完成一系列动作。

  6、枚举类 enum

public enum Season{
      SPRING("春天","百花");
      SUMMER("夏天","热烈");
      AUTUMN("秋天","萧瑟");
      WINTER("冬天","埋葬");

      private final String name;
      private final String desc;

      prinvate Season(Strinf name, String desc) {
        this.name = name;
        this.desc = desc;
      }

      public void showInfo() {
            System.out.print(this.name + ":" + this.desc);
      }
}
public class Test {
      public static void main(String[] args) {
            Season spring = Season.SPRING;
            speing.showInfo();
      }
}

  7、自定义工具类Utils.java
        里面放入许多个需要反复执行的方法。

  8、Collections操作集合的工具类
        是一个操作List、Map、Set等集合的工具类。此类提供了大量方法对集合元素进行排序、查询和修改等操作。也可解决多线程并发访问集合时的线程安全等问题。

  9、Javabean:是一个包装好的可以重复使用的组件,一种特殊的Java类——public 类、有一个无参的公共构造器、private 属性、且有对应的 public get set方法用来修改和获取属性、(也可以有一系列的操作方法和事件处理器)。

七、线程

生命周期:新建(执行start()方法之前,线程实例创建,尚未被启动)、就绪(执行start()方法之后,在就绪队列中排队)、运行(获得CPU资源正在执行run()方法)、阻塞(正在运行的线程让出CPU并暂停自己的执行run()方法,可通过wait()进入等待状态、或通过在获取同步锁失败后进入等待状态、或通过sleep()或join()使线程进入阻塞状态,阻塞状态终止之后会转回就绪状态重新排队)、死亡(自然终止或用stop()方法终止)
  1、程序:某种语言编写的一组指令的集合。
  2、进程:是程序的实体,一个程序至少有一个进程,存在它自身的产生、存在和消亡。
  3、线程:是进程的一个实体,一个线程只能属于一个进程,而一个进程至少有一个线程,线程是程序内部的一条执行路径。(若程序可同一时间执行多个线程,则这个程序就是支持多线程的)
  4、同步与异步:同步是指一个进程在执行某个请求的时,若该请求需要一段时间才能返回信息,那么这个进程会一直等待,直到接收了返回信息才能继续执行下去;异步是指进程不需要一直等待,在接受返回信息的期间,可以继续执行下面的操作,不管其他进程的状态。
  5、创建线程的两种方式
        5-①继承Thread类:自定义子类继承→子类重写 run() →创建Thread子类对象→子类对象调用线程 strat()
执行多次后可发现线程是异步进行的(main线程与ThreadSon线程)

public class Test {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName();
        Thread thread = new ThreadSon();
        thread.start();
        System.out.println(name + " 线程:4");
        System.out.println(name + " 线程:5");
        System.out.println(name + " 线程:6");
    }
}

class ThreadSon extends Thread{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
    }
}

输出结果为:

>> main 线程:4
>> 1
>> main 线程:5
>> 2
>> 3
>> main 线程:6

        5-②实现 Runnable 接口

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new toSleepRunnble());
        // 或者使用如下方式:
        // Runnable run = new toSleepRunnble();
        // Thread thread = new Thread(run);

        thread.start();
        System.out.println("4");
        System.out.println("5");
        System.out.println("6");
    }
}

class toSleepRunnble implements Runnable{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
    }
}

  5、相关方法
        5-① yield() 线程让步:让其它优先级相同或更高且数据量更大的线程优先获得运行机会,此时调用此方法的线程回到就绪状态重新排队(效果有待商榷)
        5-② join():即使是低优先级的线程也可以获得先执行权。即阻塞当前所在类的活动线程,让调用了 .join() 方法的线程插入进来活动,等到join进来的线程执行完毕,再让main方法执行其它被阻塞的线程。

关于join()的使用实例

public class Test {
    public static void main(String[] args) {
        String name = Thread.currentThread().getName();
        Thread thread = new ThreadSon();
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + " 线程:4");
        System.out.println(name + " 线程:5");
        System.out.println(name + " 线程:6");
    }
}

class ThreadSon extends Thread{
    @Override
    public void run() {
        System.out.println("1");
        System.out.println("2");
        System.out.println("3");
    }
}

输出结果为:

>> 1
>> 2
>> 3
>> main 线程:4
>> main 线程:5
>> main 线程:6

若不添加 join() 则:

>> 1
>> main 线程:4
>> main 线程:5
>> 2
>> main 线程:6
>> 3

        5-③ sleep(long millis):让当前活动线程“沉睡”指定的时间,而其他线程先执行,但不会释放占用的同步锁,时间到后释放锁,回到就绪状态重新排队。
        5-④ wait():令当前线程挂起并放弃CPU、同步资源(同步锁),使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。需要使用notify()来唤醒。

关于sleep() 与wait()的区别: wait()(或wait(long))是Object的一个方法,且需放在同步锁的方法中,且须使用notify()(或timing out)才能唤醒该线程占有的同步锁;sleep()是Thread的一个方法,若该线程在使用了sleep()的前面又使用了同步锁,则需要等线程主动醒来才能退出阻塞状态且释放这个锁。

        5-⑤ notify()唤醒正在排队等候同步资源的线程中优先级最高者结束等待。

关于wait() 与 notify() 的简析

        5-⑥ stop():强制线程生命期结束
        5-⑦ boolean isAlive():判断线程是否还活着

关于 wait() 和 notify() 的使用实例——消费者与生产者问题
题目:生产者(Producer)将产品交给店员(Clerk,相当于仓库),消费者(Customer)从店员处取走产品。店员一次只能拿 20 个产品(生产者最大生产量),如果生产者试图生产更多产品,店员会叫生产者停一下,等待下次通知才能生产。当店员拿来产品,店员会通知消费者前来消费。如果店中的产品数量不足,店员会叫消费者等一下,等待下次店员拿来了产品后通知了消费者,消费者才能消费。

一个生产者线程与一个消费者线程

public class TestThread {
    public static void main(String[] args) {
        /**
         * 多个线程排队获取CPU的使用权
         * 情况一:当其中一个线程获取了同步锁,并使用了 wait() 方法后,这个线程从运行状态退出,
         * 进入等待阻塞状态(进入等待队列),同时释放所持有的同步锁(先获取锁再释放锁)
         * 直到被另一个线程使用 notify() 唤醒,其它的线程才从阻塞状态进入到可运行状态。(前提是这几个个线程等待的是同一共享资源(同一个同步锁))
         * 情况二:当其中一个线程获取了锁,根据判断条件若不使用 wait() 方法,那么将直接执行 wait() 下面的代码
         * 等这些步骤执行完,就可以使用 notify()或notifyAll() 方法唤醒(通知)其它线程去排队获取同步锁
         */
        Clerk clerk = new Clerk();  //店员

        Thread producThread = new Thread(new Producer(clerk), "生产者");
        Thread customerThread = new Thread(new Customer(clerk), "消费者");

        producThread.start();
        customerThread.start();
    }
}

//店员
class Clerk{
    // public int totalProduct = 0;    //店员身上有 0 个产品
    // public boolean isEnough = false;    //产品不足
    public int totalProduct = 20;    //店员身上有 20 个产品,初始化时预设店员身上产品充足
    public boolean isEnough = true;    //产品是否足够,false为库存不足,true为库存足够
    public static final int MAX_PRODUCT = 20;   //店员身上最大的产品数为20
}


//生产者
class Producer implements Runnable{
    Clerk clerk;
    String name;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        /**
         * 注意使用 while 在外围包含着同步锁的原因:为了让生产者无线次数地生产产品,消费者不断地消费产品。
         * 若去掉外面的 while 循环的话,生产者线程只能被 notify 一次,即只能执行一次 wait() 后面的操作,造成生产者线程只能执行一次 run() 
         * 当生产者线程再次获取cpu使用权时,因为run方法已经执行完毕,就会自动 exit() 结束线程。徒留消费者线程一直在 wait 中。
         */
        while(true){
            synchronized(clerk){
                try {
                    //获取当前线程的名称
                    name = Thread.currentThread().getName();
                    
                    /**
                     * 当判断产品足够时,执行wait()方法,同步锁被释放,生产者线程进入等待阻塞队列,不执行后面的操作
                     * 关于wait()方法放在while循环里是为了防止线程被错误地、提早地被唤醒
                     */
                    while(clerk.isEnough){
                        System.out.println( name + " 拿到了同步锁,发现货物还足够,于是继续等待···");
                        clerk.wait();
                        System.out.println( name + " 再次拿到了同步锁,从 wait() 中被唤醒!");
                    }

                    //需要操作的代码块放在 wait()下面是因为,当线程被 notify 之后是从 wait() 下面的代码块继续执行的
                    System.out.println(name + " 正在生产产品···");
                    while(clerk.totalProduct < clerk.MAX_PRODUCT){
                        //生产者每隔 3 秒生产 5 个产品
                        Thread.sleep(3000);
                        if(clerk.MAX_PRODUCT - clerk.totalProduct >= 5){
                            clerk.totalProduct += 5;
                            System.out.println("---->已经生产 5 个产品,现在产品库存为: " + clerk.totalProduct + " 个产品!");
                        }
                        else{
                            int num = clerk.MAX_PRODUCT-clerk.totalProduct;
                            clerk.totalProduct += num;
                            System.out.println("---->已经生产 " + num + " 个产品,现在产品库存为: " + clerk.totalProduct + " 个产品!");
                            Thread.sleep(1000);
                        }
                    }
                    System.out.println("生产完毕,产品数量已充足,请消费者继续购物~~~");
                    clerk.isEnough =true;
                    Thread.sleep(3000);
                    
                    //因为此时消费者线程在等待阻塞状态,所以使用 notify() 时,在等待状态的线程收到通知后退出等待队列
                    //多个消费者线程时使用 clerk.notifyAll();
                    clerk.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
       }
    }
}


//消费者
class Customer implements Runnable{
    Clerk clerk;
    String name;
    int num;

    public Customer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        //不断有消费者来购买产品
        while(true){
            synchronized(clerk){
                try {
                    name = Thread.currentThread().getName();

                    //当产品不足时等待,且释放同步锁,让其它线程有机会获取同步锁
                    while(clerk.isEnough == false){
                        System.out.println( name + " 拿到了同步锁,发现货架上产品不够了,于是继续等待···");
                        clerk.wait();
                        System.out.println( name + " 再次拿到了同步锁,从 wait() 中被唤醒!");
                    }
                    
                    //消费者每次执行一次购买产品的操作
                    System.out.println( name + " 进来了,正在消费···");
                    //消费者随机购买[1,8]个产品数(即最多买 8 个产品,最少买 1 个)
                    num = (int)(Math.random() * 8) + 1;
                    if(clerk.totalProduct >= num){
                        //消费者每隔 2 秒消费 num 个产品
                        Thread.sleep(2000);
                        clerk.totalProduct -= num;
                        System.out.println("----> " + name + " 购买了 " + num + " 个产品,现货柜剩余: " + clerk.totalProduct + " 个产品!");
                        Thread.sleep(1000);
                        System.out.println(name + " 离开了!");
                        Thread.sleep(1000);
                        System.out.println("请下一位消费者上前购物~~~");
                    }else{
                        Thread.sleep(2000);
                        System.out.println(name + " 需要购买 " + num + " 个产品,看到货柜产品数量不太够自己的需求而离开了!店员正在提醒生产者生产产品~~~");
                        clerk.isEnough = false;
                    }
                    Thread.sleep(3000);
                    //因为此时生产者线程在排队等待的状态,所以使用 notify() 时,在等待状态的线程 收到通知后退出等待队列
                    //如果有多个线程则 clerk.notifyAll();
                    clerk.notify();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

结果如下:

一个生产者一个店员一个消费者

一个生产者线程与多个消费者线程

public class TestThread {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();  //店员

        Thread producThread = new Thread(new Producer(clerk), "生产者");
        Thread customerThread = new Thread(new Customer(clerk), "阿大");
        Thread customerThread2 = new Thread(new Customer(clerk), "小二");
        Thread customerThread3 = new Thread(new Customer(clerk), "张三");
        Thread customerThread4 = new Thread(new Customer(clerk), "李四");
        Thread customerThread5 = new Thread(new Customer(clerk), "王五");

        producThread.start();
        customerThread.start();
        customerThread2.start();
        customerThread3.start();
        customerThread4.start();
        customerThread5.start();
    }
}

同时把 notify() 替换为 notifyAll();

结果如下:

一个生产者多个消费者

需要注意的问题如下:

一、wait() 方法在 while 里面而不在 if 里面的原因:可能会出现某一线程被 提前唤醒 通知去获取同步锁(多线程的情况下)
    若 wait() 在 if 语句if(condition){ obj.wait(); }
    那么线程执行 run 方法时可能会出现以下这种情况:

A线程按顺序执行 if 语句→ 若 condition == trueA线程执行 wait()方法 放弃同步锁,进入等待阻塞队列,此时A线程的 run() 方法尚未执行完 → 另一个在排队的B线程抢到同步锁,获取同步锁的B线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程,B线程run()方法 执行完毕C线程被唤醒,并且抢到同步锁的C线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程 → ······ → 直到A线程从 wait() 中被其它线程的 notify() 唤醒,抢到同步锁的 A线程 不再从最开始的 if语句 开始判断,而是直接执行 wait() 后面的语句——执行完 if 语句 范围内的 wait() 后面的语句,再执行 if 语句 范围外的 wait() 下方的语句(在这一步中,如果某些条件已经被改变了而A线程又不做判断的话,很容易导致出错!),执行完一系列操作后接着执行 notifyAll() 方法 唤醒其它线程,此时A线程的 run() 方法执行完毕(其它线程被唤醒并执行一系列操作······)A线程在排队时再次抢到同步锁,然后按顺序执行 if 语句······

    若 wait() 在 while 语句while(condition){ obj.wait(); }
    那么线程执行 run 方法时会出现以下这种情况:

A线程按顺序执行 while 语句→ 若 condition == trueA线程执行 wait()方法 放弃同步锁,进入等待阻塞队列,此时A线程的 run() 方法尚未执行完 → 另一个在排队的B线程抢到同步锁,获取同步锁的B线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程,B线程run()方法 执行完毕C线程被唤醒,并且抢到同步锁的C线程执行完一系列操作,然后执行 notifyAll()方法 唤醒其它线程 → ······ → 直到A线程从 wait() 中被其它线程的 notify() 唤醒,抢到同步锁的 A线程 不再从最开始的 while 语句 开始判断,而是直接执行 wait() 后面的语句——执行完 while 语句 范围内的 wait() 后面的语句,再因为 while 循环 而回到之前再次判断 condition是否为 true ,若 condition == true 则A线程放弃同步锁,继续等待;若 condition == false ,则A线程跳出 while 循环,执行 while 语句 范围之外的 wait() 下方的语句,执行完一系列操作后接着执行 notifyAll() 方法 唤醒其它线程,此时A线程的 run() 方法执行完毕(其它线程被唤醒并执行一系列操作······)A线程在排队时再次抢到同步锁,然后按顺序执行 where 语句······

二、synchronized() 锁外面被 while(true) 包含:为了将生产者这个线程不断生产和消费者这个线程不断消费,所以要在锁外面用 while(true) 设置死循环(根据情况,也可以将 true 改为其他限制条件)

  6、同步锁 synchronized:一个线程访问一个对象中的synchronized同步代码块时,其他试图访问该对象的线程将被阻塞
模拟用户的取票操作(用户先来的先取票,后来的后取票)
        6-①:在方法声明中加上锁,表示这个方法为同步方法

public class Test {
    public static void main(String[] args) {
        TicketMachine tm = new TicketMachine();
        
        // 新建线程且自定义线程名称
        Runnable user1_run = new UserRunnable(tm, 47);
        Thread user1 = new Thread(user1_run, "第一个用户");

        Runnable user2_run = new UserRunnable(tm, 5);
        Thread user2 = new Thread(user2_run, "第二个用户");
        
        user1.start();
        user2.start();
        
    }
}

// 取票机器
class TicketMachine{
    int total = 50;
    String threadName;

    public synchronized void getTicket(int n){
       threadName = Thread.currentThread().getName();
        if(total >= n){
            System.out.println(threadName +" 操作中,原有" + total + "张票!");
            total -= n;
            System.out.println(threadName +" 现在已经取了" + n + "张票了!,还剩" + total + "张票!!");
        }else{
            System.out.println("票已经不足,请前往另一台机器取。");
        }
    }

}

class UserRunnable implements Runnable{
    TicketMachine tm;
    int n;
    
    // 带参数的构造方法
    public UserRunnable(TicketMachine tm, int n) {
        this.tm = tm;
        this.n = n;
    }

    @Override
    public void run() {
        // 用户从机器中取了n张票
        tm.getTicket(n);
    }
}

不加同步锁

加了同步锁


             或

        6-②:在方法中加上对象锁(锁住同一个“取票机器”这一对象)

public void getTicket2(int n, TicketMachine tm){
        synchronized(tm){
            name = Thread.currentThread().getName();
            if(total >= n){
                System.out.println(name+"操作中,原有" + total + "张票!");
                total -= n;
                System.out.println(name+"现在已经取了" + n + "张票了!,还剩" + total + "张票!!");
            }else{
                System.out.println("票已经不足,请"+name+"前往另一台机器取。");
            }
        }
    }
    // 相应main方法里的传参变化
    public static void main(String[] args) {
        TicketMachine tm = new TicketMachine();
        
        // 新建线程且自定义线程名称
        Runnable user1_run = new UserRunnable(47, tm);
        Thread user1 = new Thread(user1_run, "第一个用户");

        Runnable user2_run = new UserRunnable(5, tm);
        Thread user2 = new Thread(user2_run, "第二个用户");
        
        user1.start();
        user2.start();
        
    }

八、模式

  1、单例设计模式
        若实例化一个对象要占用大量的时间和资源,则只实例化一个对象。

  2、模板方法设计模式
        抽象类(父类)作为多个子类的通用模板,子类在抽象类的基础上扩展、改造。

  3、工厂方法
        通过工厂把new对象隔离,通过接口来接受不同产品的实现类。
例:面包的生产
Bread接口以及Bread类

// 面包接口
public interface Bread {
    // 面包的原材料
    void showMaterialsInfo();
    // 面包的口味介绍
    void showTasteInfo();
}

// 奶油面包
class CreamBread implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("奶油面包的原材料:牛奶、黄油、干酵粉、砂糖,盐,和鸡蛋。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("奶油香甜顺滑,面包松软。");
    }
    
}

// 芝士面包
class CheeseBread implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("芝士面包的原材料:高粉、低粉、鸡蛋液、水、黄油、白糖、盐、酵母。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("刚出炉的芝士面包可拉丝,口感丰富、营养美味。");
    }
    
}

// 法棍
class Baguette implements Bread{
    @Override
    public void showMaterialsInfo() {
        System.out.println("法棍的原材料:面粉,酵母,盐,水。");
    }

    @Override
    public void showTasteInfo() {
        System.out.println("表皮松脆,内心柔软而稍具韧性,越嚼越香,充满麦香味。");
    }
    
}

BreadFactory接口以及BreadFactory的类

// 面包工厂的接口
public interface BreadFactory {
    // 生产之后的返回类型为面包类
    Bread productBread();
}

// 奶油面包工厂接上面包工厂接口类生产奶油面包
class CreamBreadFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 奶油面包生产过程
        System.out.println("生产奶油面包中···");
        // 生产完毕返回一个奶油面包类
        return new CreamBread();
    }

}

class CheeseBreadFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 芝士面包生产过程
        System.out.println("生产芝士面包中···");
        // 生产完毕返回一个奶油面包类
        return new CheeseBread();
    }

}

class BaguetteFactory implements BreadFactory{

    @Override
    public Bread productBread() {
        // 法棍生产过程
        System.out.println("生产法棍中···");
        // 生产完毕返回一个法棍类
        return new Baguette();
    }
}

main

public class Test{
    public static void main(String[] args) {
        // 面包通过奶油面包加工厂来生产奶油面包
        Bread creamBread = new CreamBreadFactory().productBread();
        creamBread.showMaterialsInfo();
        creamBread.showTasteInfo();

        // 面包通过芝士面包加工厂来生产芝士面包
        Bread cheeseBread = new CheeseBreadFactory().productBread();
        cheeseBread.showMaterialsInfo();
        cheeseBread.showTasteInfo();
    }
}
工厂模式图

你可能感兴趣的:(Java:基础知识——数据类型、关键字、集合、IO流、类、线程(生产者与消费者)、模式)