• Java基础03


    Java多线程

    进程与线程

    • 一个进程可以有多个线程
    • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运
      行的含义,是一个静态的概念。
    • 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单
    • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一 个线程, 不然没
      有存在的意义。线程是CPU调度和执行的的单位。

    程序-->进程-->线程

    • 线程就是独立的执行路径;
    • 在程序运行时,即便没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
    • main()称之为主线程,为系统的入口,用于执行整个程序;
    • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度
    • 操作系统紧密相关的,先后顺序是不能认为的干预的。
    • 对同- -份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
    • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
    • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

    线程不一定立即执行,由CPU安排调度

    1. 创建线程的方法一
    //创建线程的方法  1 继承thread类  重写run方法
    public class TestThead extends Thread {
        @Override
        public void run() {
    
            for (int i = 0; i < 200; i++) {
                System.out.println("我是创建的线程--->"+i);
            }
        }
    
    
    
        public static void main(String[] args) {
            TestThead threadtest1 = new TestThead();
            threadtest1.start();
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("我是主线程---->"+i);
            }
    
        }
    }
    
    1. 创建线程的方法二

      public class TestThread3 implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 200; i++) {
                  System.out.println("我是创建的线程--->"+i);
              }
          }
          public static void main(String[] args) {
              TestThread3 threadtest3 = new TestThread3();  //创建一个实现runnable接口的实现类对象,将此对象放入Thread对象中
              new Thread(threadtest3).start();
      
              for (int i = 0; i < 1000; i++) {
                  System.out.println("我是主线程---->"+i);
              }
      
          }
      }
      

    实现runnable接口的好处:

    1. 实现接口Runnable具有多线程能力
    2. 启动线程:传入目标对象+ Thread对象.start()
    3. 推荐使用:避免单继承局限性,灵活方便, 方便同一个对象被多个线程使用

    创建线程的三种方式:1.继承Thread类 2.实现runnable接口 3. 实现callable接口

    Lamda表达式

    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
    public class TestLamda {
    
        //2.创建静态内部类
        static class Like2 implements Ilike{
            @Override
            public void lamda() {
                System.out.println("我喜欢lamDa2.0");
            }
        }
    
        public static void main(String[] args) {
            Ilike l = new Like();
            l.lamda();
            l = new Like2();
            l.lamda();
    
            //3.创建局部内部类
            class Like3 implements Ilike{
    
                @Override
                public void lamda() {
                    System.out.println("我喜欢lamDa3.0");
                }
            }
            l = new Like3();
            l.lamda();
    
            //4.匿名内部类
    
            l = new Ilike(){
    
                @Override
                public void lamda() {
                    System.out.println("我喜欢lamDa4.0");
                }
            };
            l.lamda();
            //5.用lamda简化
            l =()-> {
                System.out.println("我喜欢lamDa5.0");
            };
            l.lamda();
    
        }
    }
    
    
    //1. 函数式接口
    interface Ilike{
        void lamda();
    }
    
    class Like implements Ilike{
    
        @Override
        public void lamda() {
            System.out.println("我喜欢lamDa1.0");
        }
    }
    

    总结:
    lambda表达式只能有一行代码的情况 下才能简化成为-一行,如果有多行,那么就用代码块包裹。
    前提是接口为函数式接口
    多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号.

    public class TestLamda2 {
        public static void main(String[] args) {
             Ilove love = (a)->{ //可以省略int  一个参数的时候可以省略括号
                 System.out.println("I love "+a);
            };
    
             love.love(520);
        }
    }
    
    interface Ilove{
        void love(int a);
    }
    

    静态代理

    • 静态代理模式总结: .

      • 真实对象利代理对象都要实现同一个接口

      • 代理对象要代理真实角色

        好处:

        1. 代理对象可以做很多真实对象做不J的事情
        2. 真实对象专注做自己的事情

    线程休眠

    • sleep (时间)指定当前线程阻塞的毫秒数;
    • sleep存在异常InterruptedException;
    • sleep时间达到后线程进入就绪状态;
    • sleep可以模拟网络延时,倒计时等。
    • 每一个对象都有一个锁,sleep不会释放锁;

    线程礼让

    public class TestYield  {
    
        public static void main(String[] args) {
            MyYield t = new MyYield();
    
            new Thread(t,"a").start();
            new Thread(t,"b").start();
    
        }
    
    }
    //线程的礼让不一定成功,看CPU调度
    class MyYield implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"线程开始执行!");
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"线程结束执行!");
        }
    }
    

    线程的join操作

    • 这个可以理解成插队,不太重要

    线程状态

    1631186333403

    线程优先级

    • Java提供一 个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度
      器按照优先级决定应该调度哪个线程来执行。
    • 优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度
    • 使用以下方式改变或获取优先级getPriority() . setPriority(int xXX)
      优先级的设定建议在start(调度前)

    守护线程

    1. 线程分为用户线程和守护线程
    2. 虚拟机必须确保用户线程执行完毕
    3. 虚拟机不用等待守护线程执行完毕。如,后台记录操作日志监控内存,垃圾回收等待

    线程同步

    • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问
      冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
      synchronized
      ,当-个线程获得对象的排它锁,独占资源,其他线程必须等待,
      使用后释放锁即可.存在以下问题:

      1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;
      2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引
        起性能问题;
      3. 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒
        置,引起性能问题.
      public class TestThread5 {
          public static void main(String[] args) {
              buyTicket buy = new buyTicket();
              new Thread(buy,"高帅").start();
              new Thread(buy,"田永平").start();
              new Thread(buy,"蔡鹏瑞").start();
          }
      }
      
      class buyTicket implements Runnable{
          private int ticketNum = 10;
          boolean flag = true; //外部停止方法
      
          @Override
          public void run() {
              while (flag){
                  try {
                      buy();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public void buy() throws InterruptedException {
      
              if(ticketNum<=0){
                  flag = false;
                  return;
              }
              Thread.sleep(100);
              System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票");
          }
      }
      
      运行结果:
      蔡鹏瑞买了第10张票
      田永平买了第9张票
      高帅买了第10张票
      高帅买了第8张票
      蔡鹏瑞买了第8张票
      田永平买了第7张票
      高帅买了第4张票
      田永平买了第5张票
      蔡鹏瑞买了第6张票
      田永平买了第3张票
      高帅买了第2张票
      蔡鹏瑞买了第3张票
      蔡鹏瑞买了第-1张票
      田永平买了第1张票
      高帅买了第0张票
      
      进程已结束,退出代码 0
          
          public synchronized void buy() throws InterruptedException {
      
              if(ticketNum<=0){
                  flag = false;
                  return;
              }
              Thread.sleep(500);
              System.out.println(Thread.currentThread().getName()+"买了第"+ticketNum--+"张票");
          }
      给buy方法上锁。
      
      

    解决线程不安全的方法

    • 同步块: synchronized (Obj){ }

      • Obj称之为同步监视器

      • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器

      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是
        这个对象本身,或者是class [反射中讲解]

    同步监视器的执行过程

    1. 第一个线程访问,锁定同步监视器,执行其中代码.
    2. 第二个线程访问,发现同步监视器被锁定,无法访问.
    3. 第一个线程访问完毕 ,解锁同步监视器
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

    死锁

    产生死锁的四个必要条件:

    1. 互斥条件:一个资源每次只能被一个进程使用
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
    3. 不剥夺条件 :进程已获得的资源,在末使用完之前,不能强行剥夺。
    4. 循环等待条件 :若干进程之间形成- -种头尾相接的循环等待资源关系。

    Lock锁与synchronized

    class buyTicket implements Runnable {
        private int ticketNum = 10;
        boolean flag = true; //外部停止方法
    
        private final ReentrantLock lock = new ReentrantLock(); //定义lock锁
    
        @Override
        public void run() {
            while (flag) {
                try {
                    lock.lock();
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }
    
        public  void buy() throws InterruptedException {
    
            if (ticketNum <= 0) {
                flag = false;
                return;
            }
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + "买了第" + ticketNum-- + "张票");
        }
     }
    
    

    lock与synchronized区别

    1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁 synchronized是隐式锁, 出了
      作用域自动释放
    2. Lock只有代码块锁,synchronized有代码块锁和方法锁
    3. 使用L ock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
      性(提供更多的子类)
    4. 优先使用顺序:
      Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方
      法体之外)

    生产者消费者模式

    管程法

    public class TestPC {
    
        public static void main(String[] args) {
            Buffer buffer = new Buffer();
            new Productor(buffer).start();
            new Consumer(buffer).start();
        }
    }
    
    //生产者
    class Productor extends Thread{
        Buffer buffer = new Buffer();
    
        public Productor(Buffer buffer) {
            this.buffer = buffer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("生产了第"+i+1+"只鸡");
                buffer.push(new Chicken(i));
            }
        }
    }
    
    class Consumer extends Thread{
        Buffer buffer = new Buffer();
    
        public Consumer(Buffer buffer) {
            this.buffer = buffer;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了第"+buffer.pop().id+"只鸡");
            }
        }
    }
    
    class Chicken{
        int id;
    
        public Chicken(int id) {
            this.id = id;
        }
    }
    
    class Buffer{
        Chicken[] chickens = new Chicken[10];
        int count = 0; //计数器
    
        public synchronized void push(Chicken chicken){
            if(count == chickens.length){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                chickens[count] = chicken;
                count++;
    
                //通知消费者消费
            this.notifyAll();
        }
    
        public synchronized Chicken pop(){
            if(count==0){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            count--;  //个数  不是下标  所以得先减去1
            Chicken chicken = chickens[count];
    
    
            //通知生产者生产
            this.notifyAll();
            return chicken;
        }
    
    }
    

    线程池

    1. JDK 5.0起提供了线程池相关API: ExecutorService和Executors
      ExecutorService:真正的线程池接口。
    2. 常见子类ThreadPoolExecutor
      void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执
      行Runnable
    3. Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
      void shutdown() :关闭连接池
    4. Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
  • 相关阅读:
    深入理解Java中的final关键字
    【事故处理】开车撞了人,一定要这样处理,否则后悔终生!
    spark、storm与Hadoop
    深入理解Java中的final关键字
    java并发之原子性、可见性、有序性
    java中重写equals和hashCode方法
    RESTClient插件POST方法传递参数
    Java内部类的作用
    java常用设计模式
    思辨: 讨论交流和独立思考谁更重要。
  • 原文地址:https://www.cnblogs.com/g414056667/p/15252965.html
Copyright © 2020-2023  润新知