资讯详情

Java复习07

多线程
实现多线程
  1. 进程

    进程是正在运行的程序

    • 是系统资源分配和调用的独立单位
    • 每个过程都有自己的内存空间和系统资源
  2. 线程

    线程是过程中的单个顺序控制流,是执行路径

    • 单线程:如果一个过程只有一个执行路径,则称为但线程序
    • 多线程:如果一个过程有多个执行路径,则称为多线程序
  3. 实现多线程的方法

    方式1:继承Thread类

    • 定义一个类MyThread继承Thread类

    • 在MyThread类中重写run()方法

    • 创建MyThread类的对象

    • 启动线程

    两个小问题:

    • 为什么要重写?run()方法?

      因为run()用于包装被线程执行的代码

    • run()方法和start()方法的差异

      run():直接调用封装线程执行的代码,相当于普通方法的调用

      start():启动线程,然后通过JVM调用此线程run()方法

  4. 设置和获取线程名称

    Thread设置和获取线程名称的方法

    • void setName(String name):将该线程的名称更改为参数name
    • String getName():返回此线程的名称
    • 线程名也可以通过方法设置
package itheima02;  public class MyThread extends Thread {     @Override     public void run() {         for (int i = 0; i < 100; i  ) {             System.out.println(getName()   ":"   i);         }     } }  /*      private String name;      public Thread() {         init(null, null, "Thread-"   nextThreadNum(), 0);     }      public Thread(ThreadGroup group, Runnable target, String name, long stackSize) {         this(goup, target, name, stackSize, null ,true);     }      private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritYhreadLocals){         this.name=name;           public final String getName() {         return new String(name, true);     }       private static int threadInitNumber;    //从0开始     private static synchronized int nextThreadNum() {         return threadInitNumber  ;     }  */ 
package itheima02;  public class MyThreadDemo {     public static void main(String[] args) {         MyThread my1 = new MyThread();         MyThread my2 = new MyThread();          my1.setName("gaotie");         my2.setName("feiji");          my1.start();         my2.start();     } } 
package itheima02; /*     Thread 获取和设置线程名称的方法         1.         void setName(String name):将该线程的名称更改为等一参数name         String getName():返回此线程的名称  */  public class MyThreadDemo {     public static void main(String[] args) {        /*         MyThread my1 = new MyThread();         MyThread my2 = new MyThread();          //2. 线程名称采用带参结构法设置,也是通过getName()获取值         my1.setName("gaotie");//如果要用这种方法给线程命名,必须在定义类中提供带参法,并通过super访问父类代餐结构的方法         my2.setName("feiji");*/   //        MyThread my1 = new MyThread(); //        MyThread my2 = new MyThread(); // //        my1.start(); //        my2.start();          //static Thread currentThread() 返回引用正在执行的线程对象 //        System.out.println(getName();//现在main()方法没有继承Thread不能调用getName()获取线程名         //3. static Thread currentThread() 返回引用当前正在执行的线程对象 --- 静态方法 获得线程对象,调用线程对象getName()获取线程名称         System.out.println(Thread.currentThread().getName()main,当前的方法是在一个名称中main执行的线程      } } 

如何获取main)()方法所在的线程名称?

  • public static Thread currentThread():返回引用正在执行的当前线程对象
  1. 线程调度

    线程有两种调度模型

    • 分时调度模型:轮流使用所有线程CPU使用权平均分配每个线程占用CPU的时间
    • 抢占调度模型:优先使用优先级高的线程CPU,如果线程的优先级相同,则随机选择一个。优先级高的线程获取的时间片相对较多

    Java抢占抢占调度模型

    假设只有一台电脑CPU,那么CPU一个指令只能在某个时刻执行,线程只能获得CPU时间片,即试用权,可以执行指令。因此,多线程序的执行是随机的,因为谁抓住了它CPU试用期不一定。

    Thread设置和获取线程优先级的方法

    • public final int getPriority():返回此线程的优先级
    • public final void setPriority(int newPriority):更改此线程的优先级
package itheima03;  public class ThreadPriorityDemo {     public static void main(String[] args) {         ThreadPriority tp1 = new ThreadPriority();         ThreadPriority tp2 = new ThreadPriority();         ThreadPriority tp3 = new ThreadPriority();          tp1.setName("gaotie");         tp2.setName("feiji");         tp3.setName("qiche");          //public final int getPriority():返回此线程的优先级         System.out.println(tp1.getPriority();//5         System.out.println(tp2.getPriority();//5         System.out.println(tp3.getPriority();//5  默认情况下,这三个线程的优先级为5          // public final void setPriority(int newPriority):更改此线程的优先级 //        tp1.setPriority(10000);// IlegalArgumentException - 优先级不在MIN_PRIOORITY到MAX_PRIOTRITY范围内

        System.out.println(Thread.MAX_PRIORITY);// 10
        System.out.println(Thread.MIN_PRIORITY);// 1
        System.out.println(Thread.NORM_PRIORITY);// 5

        //设置正确的优先级
        tp1.setPriority(5);
        tp2.setPriority(10);
        tp3.setPriority(1);
        //线程优先级高仅仅代表该线程获取CPU的机率高

        tp1.start();
        tp2.start();
        tp3.start();

    }
}

​ 线程默认优先级是5,线程优先级的范围是:1-10

​ 线程优先级高仅仅表示线程获取的CPU时间片的机率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果

  1. 线程控制

    方法名 说明
    static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
    void join() 等待这个线程死亡
    void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
    package itheima03;
    
    public class ThreadJoinDemo {
        public static void main(String[] args) {
            ThreadJoin tj1 = new ThreadJoin();
            ThreadJoin tj2 = new ThreadJoin();
            ThreadJoin tj3 = new ThreadJoin();
    
            tj1.setName("康熙");
            tj2.setName("四阿哥");
            tj3.setName("八阿哥");
    
            tj1.start();
            try {
                tj1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //第一个线程执行完后后两个线程才开始执行
            tj2.start();
            tj3.start();
    
        }
    }
    
    package itheima03;
    /*
        void setDaemon(boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
     */
    
    public class ThreadDeamonDemo {
        public static void main(String[] args) {
            ThreadDeamon td1 = new ThreadDeamon();
            ThreadDeamon td2 = new ThreadDeamon();
    
            td1.setName("关羽");
            td2.setName("张飞");
    
            //设置主线程为刘备
            Thread.currentThread().setName("刘备");
    
            //设置守护线程,当主线程结束时,两个守护线程也应当结束
            td1.setDaemon(true);
            td2.setDaemon(true);
    
            td1.start();
            td2.start();
    
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    
  2. 线程生命周期

    在这里插入图片描述

  3. 多线程的实现方式

    方式2:实现Runnable接口

    • 定义一个类MyRunnable实现Runnable接口
    • 在MyRunnable类中重写run()方法
    • 创建MyRunnable类的对象
    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    • 启动线程

    多线程的实现方案有两种

    • 继承Thread类
    • 实现Runnable接口

    相比继承Thread类,实现Runnable接口的好处

    • 避免了Java单继承的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码,把线程和程序的代码、数据有效分离,较好地体现了面向对象的设计思想
线程同步

案例:卖票

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

思路:

  1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private tickets=100;

  2. 在SellTicket类中重写run()方法实现卖票,代码步骤如下:

    A:判断票数大于0,就卖票,并告知是哪个窗口卖的

    B:卖了票之后,总票数要减1

    C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行

  3. 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

    A:创建SellTicket类的对象

    B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

    C:启动线程

    package itheima05;
    
    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            /*
            A:判断票数大于0,就卖票,并告知是哪个窗口卖的
    
            B:卖了票之后,总票数要减1
    
            C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
             */
            while (true) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
    
    package itheima05;
    
    public class SellTicketDemo {
        public static void main(String[] args) {
            //创建SellTicket类的对象
            SellTicket st = new SellTicket();
    
            //创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
            Thread t1 = new Thread(st, "窗口1");
            Thread t2 = new Thread(st, "窗口2");
            Thread t3 = new Thread(st, "窗口3");
    
            //启动线程
            t1.start();
            t2.start();
            t3.start();
        }//可以跑,但是一开始的时候三个窗口都在售卖同一张票
    }
    

    思考在实际生活中,售票时出票也是需要时间的,所以在出售一张票的时候,需要一点时间的延迟,接下来我妈去修改卖票程序中卖票的动作:

    每次出票时间100毫秒,用sleep()方法实现

    卖票出现了问题

    • 相同的票出现了多次

    • 出现了负数的票

      package itheima05;
      
      public class SellTicket implements Runnable {
          private int tickets = 100;
      
          @Override
          public void run() {
      
              while (true) {
                  if (tickets > 0) {
                      //通过sleep()方法来模拟出票时间
                      try {
                          Thread.sleep(100);
                          //t1线程休息100毫秒
                          //t2线程抢到CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
                          //t3线程抢到CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      //假设线程按照顺序醒过来
                      //t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第100张票
                      System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
                      //t2抢到CPU的执行权,在控制台输出,窗口2正在出售第100张票
                      //t3抢到CPU的执行权,在控制太输出:窗口3正在出售第100张票
                      tickets--;
                      //如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终就变成了97
                  }
              }
          }
      }
      

    问题原因:

    • 线程执行的随机性导致的

    卖票案例数据安全问题的解决

    为什么出现问题?(这也是我妈判断多线程程序是否会有数据安全问题的标识)

    • 是否是多线程环境
    • 是否共享数据
    • 是否有多条语句操作共享数据

    如何解决多线程安全问题?

    • 基本思想:让线程没有安全问题的环境

    怎么实现

    • 把多条语句操作共享数据的代码起来,让任意时刻只能有一个线程执行即可
    • Java提供了同步代码块的方式解决
    1. 同步代码块

    锁多条语句操作共享数据,可以使用同步代码块实现

    • 格式:

      synchronized(任意对象){

      ​ 多条语句操作共享数据的代码

      }

      • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

        package itheima05;
        
        public class SellTicket implements Runnable {
            private int tickets = 100;
            private Object obj = new Object();
        
            @Override
            public void run() {
        
                while (true) {
                    //tickets = 100;
                    //t1,t2,t3
                    //假设t1抢到了CPU的执行权
        //            synchronized (new Object()) {//针对三个不同的对象new了三把锁,锁不住,故要用同一个对象
                    synchronized (obj) {
                        //t1进来后,就会把这段代码给锁起来
                        //此时假设t2抢到了CPU的执行权,但是synchronized锁上了,只能等,t1休息好后往下执行
                        if (tickets > 0) {
                            try {
                                Thread.sleep(100);
        						//t1休息100毫秒
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            //窗口1正在出售第100张票
                            System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
                            tickets--;//tickets = 99
                        }
                    }
                    //t1出来了,这段代码的锁就被释放了
                }
            }
        }
        

        同步的好处和弊端

        • 好处:解决了多线程的数据安全问题
        • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这时很耗费资源,无形中会降低程序的运行效率

      同步方法

      同步方法:就是把synchronized关键字加到方法上

      • 格式:

        修饰符 synchronized 返回值类型 方法名(方法参数){ }

      同步方法的锁对象是什么?

      • this

      同步静态方法:就是把synchronized关键字加到静态方法上

      • 格式:

        修饰符 static synchronized 返回值类型 方法名(方法参数) {}

      同步静态方法的锁对象是什么呢?

      • 类名.class

        package itheima05;
        
        public class SellTicket implements Runnable {
            //    private int tickets = 100;
            private static int tickets = 100;
            private Object obj = new Object();
            private int x = 0;
        
            @Override
            public void run() {
        
                while (true) {
                    if (x % 2 == 0) {
        //                synchronized (obj) {
        //                    synchronized (this) {//这里不能使用this
                        synchronized (SellTicket.class) {//得到该类的字节码文件对象,它就是我们静态同步方法的锁
                            if (tickets > 0) {
                                try {
                                    Thread.sleep(100);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
                                tickets--;
                            }
                        }
                    } else {
        //                synchronized (obj) {
        //                    if (tickets > 0) {
        //                        try {
        //                            Thread.sleep(100);
        //                        } catch (InterruptedException e) {
        //                            e.printStackTrace();
        //                        }
        //                        System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
        //                        tickets--;
        //                    }
        //                }
                        sellTicket();
                    }
                    x++;
                }
            }
        
        //    private void sellTicket() {
        //        synchronized (obj) {
        //            if (tickets > 0) {
        //                try {
        //                    Thread.sleep(100);
        //                } catch (InterruptedException e) {
        //                    e.printStackTrace();
        //                }
        //                System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
        //                tickets--;
        //            }
        //        }
        //    }
        
            private static synchronized void sellTicket() {//静态的内容和类相关所以上面不能使用this
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售" + tickets + "张票");
                    tickets--;
                }
            }
        }
        
    1. 线程安全的类

    StringBuffer

    • 线程安全,可变的字符序列
    • 从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

    Vector

    • 从Java2平台v1.2开始,该类改进了List接口,使其称为Java Collection Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector

    Hashtable

    • 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值

    • 从Java2平台v1.2开始,该类进行了改进,实现了Map接口,使其称为Java Collection Framework的成员。与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable

      package itheima06;
      
      import java.util.*;
      
      public class ThreadDemo {
          public static void main(String[] args) {
              StringBuffer sb = new StringBuffer();//线程安全
              StringBuilder sb2 = new StringBuilder();//不安全
      
              Vector<String> v = new Vector<String>();//线程安全
              ArrayList<String> array = new ArrayList<String>();//不安全
      
              Hashtable<String, String> ht = new Hashtable<String, String>();//
              HashMap<String, String> hm = new HashMap<String, String>();//
      
              //static <T> List<T> sychronizedList(List<T> list) 返回由指定列表支持的同步(线程安全)列表
              List<String> list = Collections.synchronizedList(new ArrayList<String>());//ArrayList是线程不安全的集合类,但是调用了sychronized方法后,变成了线程安全的集合类
              
          }
      }
      
    1. Lock锁

      虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何枷锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

      Lock实现提供比使用synchronized方法和语句可以获得的更管饭的锁定操作

      Lock提供了获得锁和释放的方法

      • void lock():获得锁
      • void unlock():释放锁

      Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

      ReentrantLock的构造方法:

      • ReentrantLock():创建一个ReentrantLock()的实例
      package itheima07;
      
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class SellTicket implements Runnable {
          private int tickets = 100;
          private Lock lock = new ReentrantLock();
          //接口不能实例化,用无参构造方法创建一个实例对象
      
          @Override
          public void run() {
              while (true) {
                  try {
                      lock.lock();
                      if (tickets > 0) {
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                          tickets--;
                      }//如果中间这段代码出了问题,就不会进行下一步释放锁,所有加一个try...finally.
                  } finally {
                      lock.unlock();
                  }
                  
              }
          }
      }
      
      package itheima07;
      
      /*
          卖票案例
       */
      public class SellTicketDemo {
          public static void main(String[] args) {
              SellTicket st = new SellTicket();
      
              Thread t1 = new Thread(st, "新窗口1");
              Thread t2 = new Thread(st, "新窗口2");
              Thread t3 = new Thread(st, "新窗口3");
      
              t1.start();
              t2.start();
              t3.start();
          }
      }
      
生产者消费者
  1. ​ 生产者消费者模式概述

    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程变成的理解更加深刻

    所谓生产者消费者问题,实际上主要是包含了两类线程:

    • 一类是生产者线程用于生成数据
    • 一类是消费者线程用于消费数据

    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    • 生成者生产数据之后直接放置在共享数据区中,并不关心消费者的行为
    • 消费者只需要从共享数据区中区获取数据,并不需要关心生产者的行为

    为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中

    Object类的等待和唤醒方法:

    方法名 说明
    void wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
    void notify() 唤醒正在等待对监视器的单个线程
    void notifyAll() 唤醒正在等待对象监视器的所有线程
    1. 生产者消费者案例

      生产者消费者案例中包含的类:

      • 奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
      • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
      • 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
      • 测试类(BoxDemo):里面有main方法,main()方法中的代码步骤如下
        1. 创建奶箱对象,这是共享数据区域
        2. 创建生产者对象,把奶箱对象作为构造方法参数传递,因为这个类中要调用存储牛奶的操作
        3. 创建消费者对象,把奶箱对象作为构造方法传递参数,因为这个类中要调用获取牛奶的操作
        4. 创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
        5. 启动线程
      package itheima08;
      
      public class BoxDemo { 
                  
          public static void main(String[] args) { 
                  
              //创建奶箱对象,这是共享数据区域
              Box b = new Box();
      
              //创建生产者对象,把奶箱对象作为构造方法参数传递,因为这个类中要调用存储牛奶的操作
              Producer p = new Producer(b);
      
              //创建消费者对象,把奶箱对象作为构造方法传递参数,因为这个类中要调用获取牛奶的操作
              Customer c = new Customer(b);
      
              //创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
              Thread t1 = new Thread(p);
              Thread t2 = new Thread(c);
      
              //启动线程
              t1.start();
              t2.start();
              //运行结果用户一直在拿第5瓶奶
      
          }
      }
      
    package itheima08;
    
    public class Box { 
              
        //定义一个成员变量,表示第x瓶奶
        private int milk;
        //定义一个成员变量,表示奶箱的状态
        private boolean state = false;
    
    
        //提供存储牛奶和获取牛奶的操作
        public synchronized void put(int milk) { 
              
            //如果有牛奶,等待消费
            if (state) { 
              
                try { 
              
                    wait();
                } catch (InterruptedException e) { 
              
                    e.printStackTrace();
                }
            }
            //如果没有牛奶,就生产牛奶
            this.milk = milk;
            System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
    
            //生产完毕后,修改奶箱状态
            state = true;
    
            //唤醒其他等待的线程
            notifyAll();
        }
    
        public synchronized void get() { 
              
            //如果没有牛奶,等待生产
            if (!state) { 
              
    
                try { 
              
                    wait();
                } catch (InterruptedException e) { 
              
                    e.printStackTrace();
                }
            }
            //如果有牛奶,就消费牛奶
            System.out.println("用户拿到第" + this.milk + "瓶奶");
    
            //消费完毕后,此修改奶箱状态
            state = false;
    
            //唤醒其他等待的线程
            notifyAll();
        }
    
    
    }
    
    package itheima08;
    
    public class Producer implements Runnable { 
              
        private Box b;
    
        public Producer(Box b) { 
              
            this.b = b;
        }
    
        @Override
        public void run() { 
              
            for (int i = 1; i <= 5; i++) { 
              
                b.put(i);
            }
        }
    }
    
    package itheima08;
    
    public class Customer implements Runnable { 
              
        private Box b;
    
        public Customer(Box b) { 
              
            this.b = b;
        }
    
        @Override
        public void run() { 
              
            while (true) { 
              
                b.get();
            }
        }
    }
    

(“用户拿到第” + this.milk + “瓶奶”);

       //消费完毕后,此修改奶箱状态
       state = false;

       //唤醒其他等待的线程
       notifyAll();
   }

}


```java 
package itheima08;

public class Producer implements Runnable {
    private Box b;

    public Producer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            b.put(i);
        }
    }
}
package itheima08;

public class Customer implements Runnable { 
        
    private Box b;

    public Customer(Box b) { 
        
        this.b = b;
    }

    @Override
    public void run() { 
        
        while (true) { 
        
            b.get();
        }
    }
}

标签: ht2088变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台