首页 > 其他分享 >多线程基础知识!!!

多线程基础知识!!!

时间:2022-08-20 17:33:18浏览次数:65  
标签:Thread void 基础知识 线程 println new 多线程 public

目录

1.线程创建的三种方式

1.1、继承Thread类(重点)

public class MyThread extends Thread{
    @Override
    public void run() {

    }

    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();
    }
}

注:

  • 通过继承Thread类,然后重写run方法实现多线程
  • 通过在main方法中调用实现了Thread类的对象的start方法
  • 线程不一定立即执行,而是有CPU调度
  • 不建议使用,避免oop单继承的局限性

1.2、实现Runnable接口(重点,推荐)

public class MyThread1 implements Runnable{
    @Override
    public void run() {
        
    }

    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
        new Thread(myThread1).start();
    }
}

注:

  • 通过实现Runnable接口,然后重写接口的run方法
  • 通过在main方法中new一个Thread对象,然后将实现了Runnable接口的类的对象当做参数传入Thread构造方法,再调用Thread对象的start方法
  • 本质上来说这两种方式都是一样的,因为Thread类也实现了Runnable方法,而继承Thread类就相当于实现了Runnable接口
  • 推荐使用,方便同一个对象被多个线程调用

1.3、实现Callable接口(了解)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class MyCallable implements Callable {
    private int flag=1;
    @Override
    public String call() throws Exception {
        Thread.sleep(10);
        return "你好"+flag++;
    }

    public static void main(String[] args) {
        MyCallable myCallable=new MyCallable();
        Future<String> future=null;
        List<Future<String>> list=new ArrayList<>();
        ExecutorService service= Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            future=service.submit(myCallable);
            list.add(future);
        }
        try {
            for (int i = 0; i < 10; i++) {
                String str1=list.get(i).get();
                System.out.println(str1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdownNow();
    }
}

注:

  • 通过实现Callable接口,重写call方,该方法与run方法不同,run方法没有返回值而call有
  • 重写call方法时需要抛出异常
  • 创建实现了Callabl接口的类的对象
  • 通过ExecutorService service=Executors.newFixedThreadPool(10)方法创建执行服务,参数为线程的个数
  • 通过service.submit(myCallable)提交执行,并返回一个Future<>类型的结果,参数为实现了Callabl接口的类的对象
  • 通过Future<>对象的get方法获取返回的结果
  • 最后通过ExecutorService 的对象的shutdownNow()方法关闭服务

2.线程的五大状态

  1. 新生状态:当一个线程被new出来的时候,该线程就处于新生状态
  2. 就绪状态:首先第一种是一个线程在新生状态调用了start方法就到了就绪就绪状态,,而第二种就是线程从阻塞状态结束后,也会进入就绪状态,就绪状态不等于线程在运行了,只是已经准备就绪,随时可以被cpu调度进入运行状态,第三种就是线程礼让,通过yield方法将当前处于运行状态的线程暂停,让其从运行状态变为就绪状态
  3. 运行状态:当一个线程处于就绪状态然后被cpu调度的时候,那么该线程就进入了运行状态,运行状态的线程可以有过一些诸如sleep(),wait()方法进入阻塞状态
  4. 阻塞状态:当处于运行状态的线程执行了sleep(),wait()等方法时就会进入阻塞状态,当阻塞事件结束的时候,又会从阻塞状态进入就绪状态,随时等待cpu的调度
  5. 死亡状态:当线程中断结束,就会进入死亡状态,进入该状态的时候就无法再次启动该线程了

3.Lamda表达式

  1. 使用Lamda的好处

    • 避免匿名内部类定义过多
    • 可以让代码更加简洁
    • 丢掉了一些没有意义的代码,只留下核心的逻辑
  2. 函数式接口:只包含一个方法的接口叫做函数式接口,对于函数式接口就可以通过lamda表达式来创建该接口的对象

  3. lamda表达式演化过程:

    1. 正常定义的类

      public class Lamda {
          public static void main(String[] args) {
              Test1 test1=new Test1();
              test1.talk();
          }
      }
      
      interface Test{
          void talk();
      }
      
      class Test1 implements Test{
          @Override
          public void talk() {
              System.out.println("test1");
          }
      }
      
    2. 静态内部类

      public class Lamda {
          
          static class Test2 implements Test{
              @Override
              public void talk() {
                  System.out.println("test2");
              }
          }
      
          public static void main(String[] args) {
              Test2 test2=new Test2();
              test2.talk();
          }
      }
      
      interface Test{
          void talk();
      }
      
    3. 局部内部类

      public class Lamda {
          public static void main(String[] args) {
              class Test3 implements Test{
                  @Override
                  public void talk() {
                      System.out.println("test3");
                  }
              }
              Test3 test3=new Test3();
              test3.talk();
          }
      }
      
      interface Test{
          void talk();
      }
      
    4. 匿名内部类

      public class Lamda {
          public static void main(String[] args) {
              Test test4=new Test() {
                  @Override
                  public void talk() {
                      System.out.println("test4");
                  }
              };
              test4.talk();
          }
      }
      
      interface Test{
          void talk();
      }
      
    5. Lamda表达式

      public class Lamda {
          public static void main(String[] args) {
              Test test5=()-> System.out.println("test5");
              test5.talk();
          }
      }
      
      interface Test{
          void talk();
      }
      

      注:Lamda再简化

      • 如果参数只有一个,那么可以不写返回值类型以及外面的小括号
      • 如果有多个参数,那么可以不写返回值(都去掉),但是不能去掉小括号
      • 如果只有一行代码,可以不写函数体外面的大括号
      • 使用Lamda表达式的前提是函数式接口!!

4.线程初进阶

  1. Thread.currentThread().getName()--拿到当前线程的名字,Thread.currentThread()表示当前线程

    public void run() {
        System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
    }
    
  2. Thread.sleep(int)--线程休眠指定的时间,单位毫秒

    try {
        Thread.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    

    注:

    • 使用该方法可以模拟网络延时,放大问题以便让我们观察到
    • 使用该方法还可以模拟倒计时
    • 该方法不会释放锁
  3. new Thread(myThread1,"小明")--构造函数,第二个参数为线程的名字

    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
    
        new Thread(myThread1,"小明").start();
        new Thread(myThread1,"小红").start();
        new Thread(myThread1,"小东").start();
    }
    
  4. join()--相当于插队,必须该线程执行完才能张执行其他的线程

    public class Yield implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if(i==0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("插队线程======="+i);
            }
        }
    
        public static void main(String[] args) {
            Yield yield1=new Yield();
            Thread thread1=new Thread(yield1);
            thread1.start();
    
            for (int i = 0; i < 500; i++) {
                if(i==200){
                    try {
                        thread1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("main线程======"+i);
            }
        }
    }
    
  5. yield()--暂停当前线程,并执行其他线程,也叫做礼让线程

    public class Yield implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"线程开始执行");
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"线程结束执行");
        }
    
        public static void main(String[] args) {
            Yield yield1=new Yield();
            new Thread(yield1,"a").start();
            new Thread(yield1,"b").start();
        }
    }
    
    

    注:

    • 礼让不一定成功,相当于重新竞争
  6. interrupt()--中断线程,不建议使用这种方式

  7. isAlive()--测试线程是否处于活动状态

  8. getStates()--得到线程的状态

    Yield yield1=new Yield();
    Thread thread1=new Thread(yield1);
    System.out.println(thread1.getState());
    thread1.start();
    System.out.println(thread1.getState());
    for (int i = 0; i < 10; i++) {
        System.out.println(thread1.getState());
        if(i==200){
            try {
                thread1.join();
                System.out.println(thread1.getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(thread1.getState());
        System.out.println("main线程======"+i);
    }
    }
    
    • [NEW]
      尚未启动的线程处于此状态。
    • [RUNNABLE]
      在Java虚拟机中执行的线程处于此状态。
    • [BLOCKED]
      被阻塞等待监视器锁定的线程处于此状态。
    • [WAITING]
      正在等待另一个线程执行特定动作的线程处于此状态。
    • [TIMED_WAITING]
      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    • [TERMINATED]
      已退出的线程处于此状态。
  9. 线程停止方法:

    • 建议让线程正常终止,利用次数,不建议死循环
    • 建议使用一个标志位,去设置一个flag
    • 不要使用stop(),destory()等过时或者jdk不建议的方法
  10. 线程的优先级

    • getPriority()--得到线程的优先级
    • setPriority(int)--设置线程的优先级

    注:

    • 主线程的优先级不能更改,使用默认的优先级5
    • 先设置优先级再启动
    • 优先级大只是被调用的概率大,不一定优先级高的就先被调用
    • 优先级范围为1~10
  11. 守护线程:线程分为用户线程和守护线程,JVM必须等待用户线程执行完成,而不必等待守护线程进行完毕,通过setDaemon(true)设置线程为守护线程,默认为false

5.线程同步

5.1、了解线程同步

问题:当多个线程访问一个资源的时候,就会出现线程不安全的问题,导致资源的变化出现不一致!!

解决:通过锁和队列的机制解决

具体:

  • 每一个对象都有一个锁,线程必须拿到该对象的锁才可以对该资源进行操作
  • 当有多个线程访问同一个对象时,就会形成一个队列
  • 当线程访问该对象的时候,如果该对象有锁,那么线程拿到对象的锁
  • 后续线程按照一定的顺序去访问这个对象,访问的时候会去检查该对象是否有锁,如果没有则说明上一个线程还没有操作完毕,也就是还没有释放该对象的锁,即该等待的线程还不能操作该对象

弊端:

  • 虽然解决了安全的问题,但是使用锁以及排队会降低性能
  • 如果出现优先级高的线程等待优先级低的线程,那么可能会出现优先级倒置的情况

5.2、代码模拟

  1. 使用synchronized关键字修饰方法,锁的是this,也即是当前对象

    class MyThread1 implements Runnable{
        private int ticket=10;
        boolean flag=true;
        @Override
        public  void run() {
            while(flag)
            {
                buy();
            }
        }
    
        public synchronized void buy(){
            if(ticket==0)
            {
                flag=false;
                return;
            }
            else {
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) {
            MyThread1 myThread1=new MyThread1();
    
            new Thread(myThread1,"小明").start();
            new Thread(myThread1,"小红").start();
            new Thread(myThread1,"小东").start();
        }
    }
    
  2. synchronized(Object){}块:Object称为同步监视器,可以是任何对象,推荐使用共享资源作为同步监视器

    class MyThread1 implements Runnable{
        private int ticket=10;
        boolean flag=true;
        @Override
        public  void run() {
            while(flag)
            {
                buy();
            }
        }
    
        public void buy(){
            synchronized (this){
                if(ticket==0)
                {
                    flag=false;
                    return;
                }
                else {
                    System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
                }
            }
        }
        public static void main(String[] args) {
            MyThread1 myThread1=new MyThread1();
    
            new Thread(myThread1,"小明").start();
            new Thread(myThread1,"小红").start();
            new Thread(myThread1,"小东").start();
        }
    }
    

    注:object应该是变化的量的拥有者,即锁的应该是变化的量所属的对象!!

5.3、Lock锁

  1. Lock和synchronized的区别

    • synchronized是Java内置的关键字,在jvm层面上起作用,Lock是一个Java类
    • synchronized无法判断是否获取了锁,Lock可以判断是否获得锁
    • synchronized会自动释放锁,Lock必须手动释放锁,而且释放锁的代码必须写在finally代码块中
    • synchronized修饰的代码块,由其中一个线程获得锁之后,这个线程会阻塞,等待的其他线程会一直等待下去,Lock不一定会死等
    • synchronized是可重入、不可中断、非公平锁;Lock是可重入锁,自己配置是否可中断,自己配置是否公平
    • Java1.6之前synchronized性能低效,Java在1.6之后对其性能进行一个优化。从此,两者的区别只在于一些功能性区别。其实,更加推荐使用synchronized,因为升级Java版本会获得免费的性能提升
  2. 使用Lock锁:

    import java.util.concurrent.locks.ReentrantLock;
    
    class MyThread1 implements Runnable{
        private final ReentrantLock lock=new ReentrantLock();
        private int ticket=10;
        boolean flag=true;
        @Override
        public  void run() {
                while(flag)
                {
                    buy();
                }
        }
    
        public void buy(){
            try{
                lock.lock();
                if(ticket==0)
                {
                    flag=false;
                    return;
                }
                else {
                    System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
                }
            }finally {
                lock.unlock();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public static void main(String[] args) {
            MyThread1 myThread1=new MyThread1();
    
            new Thread(myThread1,"小明").start();
            new Thread(myThread1,"小红").start();
            new Thread(myThread1,"小东").start();
        }
    }
    

    注:在使用Lock的时候,一般通过创建一个ReentrantLock对象来实现加锁,因为ReentrantLock也实现了Lock接口,在使用时通过调用lock()方法加锁,unlock解锁!!

5.4、死锁

产生情况:当有多个线程相互拿着对方资源,并且需要获得对方的资源才能释放锁的时候,就会产生死锁,因为都需要彼此的资源来释放自己的资源,导致资源一直不能被释放进而程序卡死,出现死锁的情况!!!

死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用;

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;

  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

解决死锁的4种基本方法

  1. 预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
    2. 避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁

    1. 检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉

    2. 解除死锁:该方法与检测死锁配合使用

6.线程通信

  1. 为什么需要线程通信:

    • 线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
    • 线程的通信可以被定义为:
      线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。
  2. 有关线程通信的三个方法:

    • wait() :当前线程释放锁并进入等待(阻塞)状态,当传入参数时等待参数的时间,当没有参数时,表示一直等待
    • notify() :唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁
    • notifyAll() :唤醒所有正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁
  3. 线程通信的几种方式:

    • 共享内存

      public class Cache {
          public static void main(String[] args) {
              CacheArea cacheArea=new CacheArea();
      
              new Provider(cacheArea).start();
              new Consumer(cacheArea).start();
          }
      }
      class Provider extends Thread{
          CacheArea cacheArea;
      
          public Provider(CacheArea cacheArea) {
              this.cacheArea = cacheArea;
          }
      
          @Override
          public void run() {
              for (int i = 0; i < 200; i++) {
                  cacheArea.push();
              }
          }
      }
      class Consumer extends Thread{
          CacheArea cacheArea;
      
          public Consumer(CacheArea cacheArea) {
              this.cacheArea = cacheArea;
          }
      
          @Override
          public void run() {
              for (int i = 0; i < 200; i++) {
                  cacheArea.pop();
              }
          }
      }
      class CacheArea{
          int count=0;
      
          public synchronized void push(){
              if(count==100){
                  try {
                      System.out.println("商品满了,快来消费");
                      this.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              count++;
              System.out.println("第" + count + "个商品产出");
              this.notifyAll();
          }
      
          public synchronized void pop(){
              if(count==0){
                  try {
                      System.out.println("没有商品了,快来生产");
                      this.wait();
      
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              System.out.println("第" + count-- + "个商品消费");
              this.notifyAll();
          }
      }
      
    • 消息传递

    • 管道流

  4. 线程池:

    1. 线程池的好处:
      • 降低系统资源消耗, 通过重用已存在的线程, 降低线程创建和销毁造成的消耗;
      • 提高系统响应速度, 当有任务到达时, 无需等待新线程的创建便能立即执行;
      • 方便线程并发数的管控, 线程若是无限制的创建, 不仅会额外消耗大量系统资源, 更是
        占用过多资源而阻塞系统或内存不足等状况, 从而降低系统的稳定性。 线程池能有效管控线程, 统一分配、 调优, 提供资源使用率;
      • 更强大的功能, 线程池提供了定时、 定期以及可控线程数等功能的线程池, 使用方便简
    2. 参数为Runnable类型:使用ExecutorService对象的execute()方法,没有返回值
    3. 参数为Callable类型使用ExecutorService对象的submit方法,有返回值
    4. 关闭线程池:shutdown()
    5. Executors:线程池工具类,用于创建并返回不同类型的线程池

标签:Thread,void,基础知识,线程,println,new,多线程,public
From: https://www.cnblogs.com/xiaoye-Blog/p/16608220.html

相关文章

  • Docker入门-基础知识
    Docker入门-基础知识Cloud研习社 Cloud研习社 2022-06-1707:26 发表于山东收录于合集#实战经验33个#云计算34个#计算机37个#docker3个#IT23个 Dock......
  • GNN学习(一):基础知识
    1#!usr/bin/envpython2#-*-coding:utf-8_*-3#@Time:2022/8/2010:464#@Author:VVZ5#@File:1.2.py678importnumpyasnp9import......
  • 【Java进阶】五分钟快速掌握JVM优化概念、常用命令、工具、JUC、多线程、GC等知识
    〇、概述1、资料 2、内容概括 一、概念(一)JVM (二)JUC (三)GC二、命令(一)JVM优化命令 (二)JUC命令三、工具(一)jdk工具......
  • 【2022-08-19】mysql基础知识(六)
    mysql基础知识(六)mysql之视图view什么是视图?视图就是通过查询得到的一张虚拟表,然后保存下来,下次直接进行使用即可。即:将SQL语句的查询结果当做虚拟表保存起来,以后可......
  • java实现多线程的四种方式
    实现多线程的三种方式:继承Thread类、实现Runnable接口、使用Callable和Future接口、使用线程池创建线程一、继承Thread类,重写run方法publicclassMyThreadextendsTh......
  • 多线程中的安全问题
    目录synchronizedsynchronized的同步代码块synchronized的非静态同步方法synchronized的静态同步方法多入口和多窗口卖票的不同情况Lock锁synchronizedsynchronized格式......
  • Java实现多线程的四种方式
    java中实现多线程主要有四种方式:继承Thread类一,继承Thread类,重写run方法publicclassThreadTest{//主线程publicstaticvoidmain(String[]args){......
  • 初识多线程
    初始多线程实现多线程的方法继承Thread类(重点)实现Runnavle接口(重点)实现Caliable接口(了解,以后可能会学习到!)多线程分两种进程和线程进程每一个程序都是静态的,当......
  • Java基础知识整理(部分)
    继承的本质是对某一类的抽象,从而实现对现实世界更好的建模1.extends的意思是扩展,子类是父类的扩展2.Java中类只有单继承,没有多继承在Java中,所有类都默认直接或间接继承Obje......
  • JAVA之线程及多线程实现
    java的线程是什么1线程是一个程序的一条执行路径。我们之前启动程序后。main方法其他是一条独立的执行路径。2JAVA的多线程JAVA的多线程是指从软硬件实现多条执行路......