JavaSE --- 多线程

ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现) 线程的创建方式,Callable接口和Future接口的配合,Runnable任务、Callable任务提交到线程池,他们之间的区别 阅读详情

1.实现多线程

1.1简单了解多线程【理解】

  • 是指从软件或者硬件上实现多个线程并发执行的技术。

  • 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
    在这里插入图片描述

1.2并发和并行【理解】

  • 并行:在同一时刻,有多个指令在多个CPU上同时执行。
    在这里插入图片描述

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行。

在这里插入图片描述

1.3进程和线程【理解】

  • 进程:是正在运行的程序
    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
    动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
    并发性:任何进程都可以同其他进程一起并发执行
  • 线程:是进程中的单个顺序控制流,是一条执行路径
    单线程:一个进程如果只有一条执行路径,则称为单线程程序
    多线程:一个进程如果有多条执行路径,则称为多线程程序

在这里插入图片描述

1.4实现多线程方式一:继承Thread类【应用】

  • 方法介绍
    方法名 说明
    void run() 在线程开启后,此方法将被调用执行
    void start() 使此线程开始执行,Java虚拟机会调用run方法()
  • 实现步骤
    • 定义一个类MyThread继承Thread类
    • 在MyThread类中重写run()方法
    • 创建MyThread类的对象
    • 启动线程
  • 代码演示
 public class MyThread extends Thread {
      @Override
      public void run() {
          for(int i=0; i<100; i++) {
              System.out.println(i);
          }
      }
  }
  public class MyThreadDemo {
      public static void main(String[] args) {
          MyThread my1 = new MyThread();
          MyThread my2 = new MyThread();
  
  //        my1.run();
  //        my2.run();
  
          //void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
          my1.start();
          my2.start();
      }
  }
  • 两个小问题
    • 为什么要重写run()方法?
      因为run()是用来封装被线程执行的代码
    • run()方法和start()方法的区别?
      run():封装线程执行的代码,直接调用,相当于普通方法的调用
      start():启动线程;然后由JVM调用此线程的run()方法

1.5实现多线程方式二:实现Runnable接口【应用】

  • Thread构造方法
    方法名 说明
    Thread(Runnable target) 分配一个新的Thread对象
    Thread(Runnable target, String name) 分配一个新的Thread对象
  • 实现步骤
    • 定义一个类MyRunnable实现Runnable接口
    • 在MyRunnable类中重写run()方法
    • 创建MyRunnable类的对象
    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    • 启动线程
  • 代码演示
public class MyRunnable implements Runnable {
      @Override
      public void run() {
          for(int i=0; i<100; i++) {
              System.out.println(Thread.currentThread().getName()+":"+i);
          }
      }
  }
  public class MyRunnableDemo {
      public static void main(String[] args) {
          //创建MyRunnable类的对象
          MyRunnable my = new MyRunnable();
  
          //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
          //Thread(Runnable target)
  //        Thread t1 = new Thread(my);
  //        Thread t2 = new Thread(my);
          //Thread(Runnable target, String name)
          Thread t1 = new Thread(my,"坦克");
          Thread t2 = new Thread(my,"飞机");
  
          //启动线程
          t1.start();
          t2.start();
      }
  }

1.6实现多线程方式三: 实现Callable接口【应用】

方法介绍

方法名                             	说明                                 
V call()                        	计算结果,如果无法计算结果,则抛出一个异常              
FutureTask(Callable<V> callable)	创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()                         	如有必要,等待计算完成,然后获取其结果  
  • 实现步骤
    • 定义一个类MyCallable实现Callable接口
    • 在MyCallable类中重写call()方法
    • 创建MyCallable类的对象
    • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
    • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
    • 启动线程
    • 再调用get方法,就可以获取线程结束之后的结果。
  • 代码演示
 public class MyCallable implements Callable<String> {
      @Override
      public String call() throws Exception {
          for (int i = 0; i < 100; i++) {
              System.out.println("跟女孩表白" + i);
          }
          //返回值就表示线程运行完毕之后的结果
          return "答应";
      }
  }
  public class Demo {
      public static void main(String[] args) throws ExecutionException, InterruptedException {
          //线程开启之后需要执行里面的call方法
          MyCallable mc = new MyCallable();
  
          //Thread t1 = new Thread(mc);
  
          //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
          FutureTask<String> ft = new FutureTask<>(mc);
  
          //创建线程对象
          Thread t1 = new Thread(ft);
  
          String s = ft.get();
          //开启线程
          t1.start();
  
          //String s = ft.get();
          System.out.println(s);
      }
  }
  • 三种实现方式的对比
    • 实现Runnable、Callable接口
      • 好处: 扩展性强,实现该接口的同时还可以继承其他的类
      • 缺点: 编程相对复杂,不能直接使用Thread类中的方法
    • 继承Thread类
      • 好处: 编程比较简单,可以直接使用Thread类中的方法
      • 缺点: 可以扩展性较差,不能再继承其他的类

1.7设置和获取线程名称【应用】

  • 方法介绍
	方法名                       	说明                
    void  setName(String name)	将此线程的名称更改为等于参数name
    String  getName()         	返回此线程的名称          
    Thread  currentThread()   	返回对当前正在执行的线程对象的引用
  • 代码演示
public class MyThread extends Thread {
      public MyThread() {}
      public MyThread(String name) {
          super(name);
      }
  
      @Override
      public void run() {
          for (int i = 0; i < 100; i++) {
              System.out.println(getName()+":"+i);
          }
      }
  }
  public class MyThreadDemo {
      public static void main(String[] args) {
          MyThread my1 = new MyThread();
          MyThread my2 = new MyThread();
  
          //void setName(String name):将此线程的名称更改为等于参数 name
          my1.setName("高铁");
          my2.setName("飞机");
  
          //Thread(String name)
          MyThread my1 = new MyThread("高铁");
          MyThread my2 = new MyThread("飞机");
  
          my1.start();
          my2.start();
  
          //static Thread currentThread() 返回对当前正在执行的线程对象的引用
          System.out.println(Thread.currentThread().getName());
      }
  }

1.8线程休眠【应用】

  • 相关方法
方法名                           	说明                      
static void sleep(long millis)	使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • 代码演示
 public class MyRunnable implements Runnable {
      @Override
      public void run() {
          for (int i = 0; i < 100; i++) {
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
  
              System.out.println(Thread.currentThread().getName() + "---" + i);
          }
      }
  }
  public class Demo {
      public static void main(String[] args) throws InterruptedException {
          /*System.out.println("睡觉前");
          Thread.sleep(3000);
          System.out.println("睡醒了");*/
  
          MyRunnable mr = new MyRunnable();
  
          Thread t1 = new Thread(mr);
          Thread t2 = new Thread(mr);
  
          t1.start();
          t2.start();
      }
  }

1.9线程优先级【应用】

  • 线程调度

    • 两种调度方式
      • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
      • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
    • Java使用的是抢占式调度模型
    • 随机性
      假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
      在这里插入图片描述
  • 优先级相关方法
    方法名 说明
    final int getPriority() 返回此线程的优先级
    final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

  • 代码演示

public class MyCallable implements Callable<String> {
      @Override
      public String call() throws Exception {
          for (int i = 0; i < 100; i++) {
              System.out.println(Thread.currentThread().getName() + "---" + i);
          }
          return "线程执行完毕了";
      }
  }
  public class Demo {
      public static void main(String[] args) {
          //优先级: 1 - 10 默认值:5
          MyCallable mc = new MyCallable();
  
          FutureTask<String> ft = new FutureTask<>(mc);
  
          Thread t1 = new Thread(ft);
          t1.setName("飞机");
          t1.setPriority(10);
          //System.out.println(t1.getPriority());//5
          t1.start();
  
          MyCallable mc2 = new MyCallable();
  
          FutureTask<String> ft2 = new FutureTask<>(mc2);
  
          Thread t2 = new Thread(ft2);
          t2.setName("坦克");
          t2.setPriority(1);
          //System.out.println(t2.getPriority());//5
          t2.start();
      }
  }

1.10守护线程【应用】

  • 相关方法
    方法名 说明
    void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
  • 代码演示
public class MyThread1 extends Thread {
      @Override
      public void run() {
          for (int i = 0; i < 10; i++) {
              System.out.println(getName() + "---" + i);
          }
      }
  }
  public class MyThread2 extends Thread {
      @Override
      public void run() {
          for (int i = 0; i < 100; i++) {
              System.out.println(getName() + "---" + i);
          }
      }
  }
  public class Demo {
      public static void main(String[] args) {
          MyThread1 t1 = new MyThread1();
          MyThread2 t2 = new MyThread2();
  
          t1.setName("女神");
          t2.setName("备胎");
  
          //把第二个线程设置为守护线程
          //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
          t2.setDaemon(true);
  
          t1.start();
          t2.start();
      }
  }

2.线程同步

2.1卖票【应用】

  • 案例需求
    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
  • 实现步骤
    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • 判断票数大于0,就卖票,并告知是哪个窗口卖的
    • 卖了票之后,总票数要减1
    • 票卖没了,线程停止
    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • 启动线程
  • 代码实现
 public class SellTicket implements Runnable {
      private int tickets = 100;
      //在SellTicket类中重写run()方法实现卖票,代码步骤如下
      @Override
      public void run() {
          while (true) {
              if(ticket <= 0){
                      //卖完了
                      break;
                  }else{
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      ticket--;
                      System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
          }
      }
  }
  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();
      }
  }

2.2卖票案例的问题【理解】

  • 卖票出现了问题
    • 相同的票出现了多次
    • 出现了负数的票
  • 问题产生原因
    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

2.3同步代码块解决数据安全问题【应用】

  • 安全问题出现的条件
    • 是多线程环境
    • 有共享数据
    • 有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    • 基本思想:让程序没有安全问题的环境
  • 怎么实现呢?
    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
    • Java提供了同步代码块的方式来解决
  • 同步代码块格式:
    synchronized(任意对象) { 
      	多条语句操作共享数据的代码 
      }
  synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
  • 同步的好处和弊端
    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
  • 代码演示
 public class SellTicket implements Runnable {
      private int tickets = 100;
      private Object obj = new Object();
  
      @Override
      public void run() {
          while (true) {
              synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
                  //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出来了,这段代码的锁就被释放了
          }
      }
  }
 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();
      }
  }

2.4同步方法解决数据安全问题【应用】

  • 同步方法的格式
同步方法:就是把synchronized关键字加到方法上
      修饰符 synchronized 返回值类型 方法名(方法参数) { 
      	方法体;
      }
  同步方法的锁对象是什么呢?
  	this
  • 静态同步方法
同步静态方法:就是把synchronized关键字加到静态方法上
      修饰符 static synchronized 返回值类型 方法名(方法参数) { 
      	方法体;
      }
  同步静态方法的锁对象是什么呢?
  	类名.class
  • 代码演示
public class MyRunnable implements Runnable {
      private static int ticketCount = 100;
  
      @Override
      public void run() {
          while(true){
              if("窗口一".equals(Thread.currentThread().getName())){
                  //同步方法
                  boolean result = synchronizedMthod();
                  if(result){
                      break;
                  }
              }
  
              if("窗口二".equals(Thread.currentThread().getName())){
                  //同步代码块
                  synchronized (MyRunnable.class){
                      if(ticketCount == 0){
                         break;
                      }else{
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          ticketCount--;
                          System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
                      }
                  }
              }
  
          }
      }
  
      private static synchronized boolean synchronizedMthod() {
          if(ticketCount == 0){
              return true;
          }else{
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              ticketCount--;
              System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
              return false;
          }
      }
  }
public class Demo {

      public static void main(String[] args) {

          MyRunnable mr = new MyRunnable();

          Thread t1 = new Thread(mr);
          Thread t2 = new Thread(mr);
    
          t1.setName("窗口一");
          t2.setName("窗口二");
    
          t1.start();
          t2.start();
      }

  }

2.5Lock锁【应用】

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

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

    方法名说明
    ReentrantLock()创建一个ReentrantLock的实例
  • 代码演示

  public class Ticket implements Runnable {
      //票的数量
      private int ticket = 100;
      private Object obj = new Object();
      private ReentrantLock lock = new ReentrantLock();

      @Override
      public void run() {
          while (true) {
              //synchronized (obj){//多个线程必须使用同一把锁.
              try {
                  lock.lock();
                  if (ticket <= 0) {
                      //卖完了
                      break;
                  } else {
                      Thread.sleep(100);
                      ticket--;
                      System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } finally {
                  lock.unlock();
              }
              // }
          }
      }
  }
public class Demo {
      public static void main(String[] args) {
          Ticket ticket = new Ticket();

          Thread t1 = new Thread(ticket);
          Thread t2 = new Thread(ticket);
          Thread t3 = new Thread(ticket);

          t1.setName("窗口一");
          t2.setName("窗口二");
          t3.setName("窗口三");

          t1.start();
          t2.start();
          t3.start();
      }
  }

2.6死锁【理解】

  • 概述
    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
  • 什么情况下会产生死锁
    1. 资源有限
    2. 同步嵌套
  • 代码演示
 public class Demo {
      public static void main(String[] args) {
          Object objA = new Object();
          Object objB = new Object();
  
          new Thread(()->{
              while(true){
                  synchronized (objA){
                      //线程一
                      synchronized (objB){
                          System.out.println("小康同学正在走路");
                      }
                  }
              }
          }).start();
  
          new Thread(()->{
              while(true){
                  synchronized (objB){
                      //线程二
                      synchronized (objA){
                          System.out.println("小薇同学正在走路");
                      }
                  }
              }
          }).start();
      }
  }

3.生产者消费者

3.1生产者和消费者模式概述【应用】

  • 概述
 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
  所谓生产者消费者问题,实际上主要是包含了两类线程:
  	一类是生产者线程用于生产数据
  	一类是消费者线程用于消费数据
  为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
  生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
  消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
  • Object类的等待和唤醒方法
 	方法名             	说明                                      
    void wait()     	导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify()   	唤醒正在等待对象监视器的单个线程                        
    void notifyAll()	唤醒正在等待对象监视器的所有线程 

3.2生产者和消费者案例【应用】

  • 案例需求
    • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量
    • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
      1.判断是否有包子,决定当前线程是否执行
      2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子
      3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子
    • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
      1.判断是否有包子,决定当前线程是否执行
      2.如果没有包子,就进入等待状态,如果有包子,就消费包子
      3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子
    • 测试类(Demo):里面有main方法,main方法中的代码步骤如下
      创建生产者线程和消费者线程对象
      分别开启两个线程
  • 代码实现
public class Desk {
      //定义一个标记
      //true 就表示桌子上有汉堡包的,此时允许吃货执行
      //false 就表示桌子上没有汉堡包的,此时允许厨师执行
      public static boolean flag = false;
  
      //汉堡包的总数量
      public static int count = 10;
  
      //锁对象
      public static final Object lock = new Object();
  }
      public class Cooker extends Thread {
      //    生产者步骤:
      //            1,判断桌子上是否有汉堡包
      //    如果有就等待,如果没有才生产。
      //            2,把汉堡包放在桌子上。
      //            3,叫醒等待的消费者开吃。
          @Override
          public void run() {
              while(true){
                  synchronized (Desk.lock){
                      if(Desk.count == 0){
                          break;
                      }else{
                          if(!Desk.flag){
                              //生产
                              System.out.println("厨师正在生产汉堡包");
                              Desk.flag = true;
                              Desk.lock.notifyAll();
                          }else{
                              try {
                                  Desk.lock.wait();
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  }
              }
          }
      }
      public class Foodie extends Thread {
          @Override
          public void run() {
      //        1,判断桌子上是否有汉堡包。
      //        2,如果没有就等待。
      //        3,如果有就开吃
      //        4,吃完之后,桌子上的汉堡包就没有了
      //                叫醒等待的生产者继续生产
      //        汉堡包的总数量减一
      
              //套路:
                  //1. while(true)死循环
                  //2. synchronized 锁,锁对象要唯一
                  //3. 判断,共享数据是否结束. 结束
                  //4. 判断,共享数据是否结束. 没有结束
              while(true){
                  synchronized (Desk.lock){
                      if(Desk.count == 0){
                          break;
                      }else{
                          if(Desk.flag){
                              //有
                              System.out.println("吃货在吃汉堡包");
                              Desk.flag = false;
                              Desk.lock.notifyAll();
                              Desk.count--;
                          }else{
                              //没有就等待
                              //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
                              try {
                                  Desk.lock.wait();
                              } catch (InterruptedException e) {
                                  e.printStackTrace();
                              }
                          }
                      }
                  }
              }
      
          }
      }
      public class Demo {
          public static void main(String[] args) {
              /*消费者步骤:
              1,判断桌子上是否有汉堡包。
              2,如果没有就等待。
              3,如果有就开吃
              4,吃完之后,桌子上的汉堡包就没有了
                      叫醒等待的生产者继续生产
              汉堡包的总数量减一*/
      
              /*生产者步骤:
              1,判断桌子上是否有汉堡包
              如果有就等待,如果没有才生产。
              2,把汉堡包放在桌子上。
              3,叫醒等待的消费者开吃。*/
      
              Foodie f = new Foodie();
              Cooker c = new Cooker();
      
              f.start();
              c.start();
      
          }
      }

3.3生产者和消费者案例优化【应用】

  • 需求
    • 将Desk类中的变量,采用面向对象的方式封装起来
    • 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用
    • 创建生产者和消费者线程对象,构造方法中传入Desk类对象
    • 开启两个线程
  • 代码实现
public class Desk {
      //定义一个标记
      //true 就表示桌子上有汉堡包的,此时允许吃货执行
      //false 就表示桌子上没有汉堡包的,此时允许厨师执行
      //public static boolean flag = false;
      private boolean flag;
  
      //汉堡包的总数量
      //public static int count = 10;
      //以后我们在使用这种必须有默认值的变量
     // private int count = 10;
      private int count;
  
      //锁对象
      //public static final Object lock = new Object();
      private final Object lock = new Object();
  
      public Desk() {
          this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了
      }
   public Desk(boolean flag, int count) {
          this.flag = flag;
          this.count = count;
      }
  
      public boolean isFlag() {
          return flag;
      }
  
      public void setFlag(boolean flag) {
          this.flag = flag;
      }
  
      public int getCount() {
          return count;
      }
  
      public void setCount(int count) {
          this.count = count;
      }
  
      public Object getLock() {
          return lock;
      }
  
      @Override
      public String toString() {
          return "Desk{" +
                  "flag=" + flag +
                  ", count=" + count +
                  ", lock=" + lock +
                  '}';
      }
  }
  public class Cooker extends Thread {
  
      private Desk desk;
  
      public Cooker(Desk desk) {
          this.desk = desk;
      }
  //    生产者步骤:
  //            1,判断桌子上是否有汉堡包
  //    如果有就等待,如果没有才生产。
  //            2,把汉堡包放在桌子上。
  //            3,叫醒等待的消费者开吃。
  
      @Override
      public void run() {
          while(true){
              synchronized (desk.getLock()){
                  if(desk.getCount() == 0){
                      break;
                  }else{
                      //System.out.println("验证一下是否执行了");
                      if(!desk.isFlag()){
                          //生产
                          System.out.println("厨师正在生产汉堡包");
                          desk.setFlag(true);
                          desk.getLock().notifyAll();
                      }else{
                          try {
                              desk.getLock().wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
      }
  }
public class Foodie extends Thread {
      private Desk desk;
  
      public Foodie(Desk desk) {
          this.desk = desk;
      }
  
      @Override
      public void run() {
  //        1,判断桌子上是否有汉堡包。
  //        2,如果没有就等待。
  //        3,如果有就开吃
  //        4,吃完之后,桌子上的汉堡包就没有了
  //                叫醒等待的生产者继续生产
  //        汉堡包的总数量减一
  
          //套路:
              //1. while(true)死循环
              //2. synchronized 锁,锁对象要唯一
              //3. 判断,共享数据是否结束. 结束
              //4. 判断,共享数据是否结束. 没有结束
          while(true){
              synchronized (desk.getLock()){
                  if(desk.getCount() == 0){
                      break;
                  }else{
                      //System.out.println("验证一下是否执行了");
                      if(desk.isFlag()){
                          //有
                          System.out.println("吃货在吃汉堡包");
                          desk.setFlag(false);
                          desk.getLock().notifyAll();
                          desk.setCount(desk.getCount() - 1);
                      }else{
                          //没有就等待
                          //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
                          try {
                              desk.getLock().wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }
          }
  
      }
  }
public class Demo {
      public static void main(String[] args) {
          /*消费者步骤:
          1,判断桌子上是否有汉堡包。
          2,如果没有就等待。
          3,如果有就开吃
          4,吃完之后,桌子上的汉堡包就没有了
                  叫醒等待的生产者继续生产
          汉堡包的总数量减一*/
  
          /*生产者步骤:
          1,判断桌子上是否有汉堡包
          如果有就等待,如果没有才生产。
          2,把汉堡包放在桌子上。
          3,叫醒等待的消费者开吃。*/
  
          Desk desk = new Desk();
  
          Foodie f = new Foodie(desk);
          Cooker c = new Cooker(desk);
  
          f.start();
          c.start();
  
      }
  }

3.4阻塞队列基本使用【理解】

  • 阻塞队列继承结构

在这里插入图片描述

  • 常见BlockingQueue:
    ArrayBlockingQueue: 底层是数组,有界
    LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int的最大值
  • BlockingQueue的核心方法:
    put(anObject): 将参数放入队列,如果放不进去会阻塞
    take(): 取出第一个数据,取不到会阻塞
  • 代码示例
 public class Demo02 {
      public static void main(String[] args) throws Exception {
          // 创建阻塞队列的对象,容量为 1
          ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
  
          // 存储元素
          arrayBlockingQueue.put("汉堡包");
  
          // 取元素
          System.out.println(arrayBlockingQueue.take());
          System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞
  
          System.out.println("程序结束了");
      }
  }

3.5阻塞队列实现等待唤醒机制【理解】

  • 案例需求
    • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务
      1.构造方法中接收一个阻塞队列对象
      2.在run方法中循环向阻塞队列中添加包子
      3.打印添加结果
    • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务
      1.构造方法中接收一个阻塞队列对象
      2.在run方法中循环获取阻塞队列中的包子
      3.打印获取结果
    • 测试类(Demo):里面有main方法,main方法中的代码步骤如下
      创建阻塞队列对象
      创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象
      分别开启两个线程
  • 代码实现
public class Cooker extends Thread {
  
      private ArrayBlockingQueue<String> bd;
  
      public Cooker(ArrayBlockingQueue<String> bd) {
          this.bd = bd;
      }
  //    生产者步骤:
  //            1,判断桌子上是否有汉堡包
  //    如果有就等待,如果没有才生产。
  //            2,把汉堡包放在桌子上。
  //            3,叫醒等待的消费者开吃。
  
      @Override
      public void run() {
          while (true) {
              try {
                  bd.put("汉堡包");
                  System.out.println("厨师放入一个汉堡包");
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
  }
  
  public class Foodie extends Thread {
      private ArrayBlockingQueue<String> bd;
  
      public Foodie(ArrayBlockingQueue<String> bd) {
          this.bd = bd;
      }
  
      @Override
      public void run() {
  //        1,判断桌子上是否有汉堡包。
  //        2,如果没有就等待。
  //        3,如果有就开吃
  //        4,吃完之后,桌子上的汉堡包就没有了
  //                叫醒等待的生产者继续生产
  //        汉堡包的总数量减一
  
          //套路:
          //1. while(true)死循环
          //2. synchronized 锁,锁对象要唯一
          //3. 判断,共享数据是否结束. 结束
          //4. 判断,共享数据是否结束. 没有结束
          while (true) {
              try {
                  String take = bd.take();
                  System.out.println("吃货将" + take + "拿出来吃了");
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
  
      }
  }
  
  public class Demo {
      public static void main(String[] args) {
          ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
  
          Foodie f = new Foodie(bd);
          Cooker c = new Cooker(bd);
  
          f.start();
          c.start();
      }
  }

4.线程池

4.1 线程状态介绍

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?
Java中的线程状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下:

public class Thread {
        
        public enum State {
        
            /* 新建 */
            NEW , 
    
            /* 可运行状态 */
            RUNNABLE , 
    
            /* 阻塞状态 */
            BLOCKED , 
    
            /* 无限等待状态 */
            WAITING , 
    
            /* 计时等待 */
            TIMED_WAITING , 
    
            /* 终止 */
            TERMINATED;
        
    	}
        
        // 获取当前线程的状态
        public State getState() {
            return jdk.internal.misc.VM.toThreadState(threadStatus);
        }
        
    }

通过源码我们可以看到Java中的线程存在6种状态,每种线程状态的含义如下

线程状态         	具体含义                                    
  NEW          	一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程象,没有线程特征。
  RUNNABLE     	当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的度。
  BLOCKED      	当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
  WAITING      	一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
  TIMED_WAITING	一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。
  TERMINATED   	一个完全运行完成的线程的状态。也称之为终止状态、结束状态       

各个状态的转换,如下图所示:

在这里插入图片描述

4.2 线程池-基本原理

概述 :

提到池,大家应该能想到的就是水池。水池就是一个容器,在该容器中存储了很多的水。那么什么是线程池呢?线程池也是可以看做成一个池子,在该池子中存储很多个线程。

线程池存在的意义:

系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系

统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,我们就可以采用线程池。线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就

会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。

线程池的设计思路 :

  1. 准备一个任务容器
  2. 一次性启动多个(2个)消费者线程
  3. 刚开始任务容器是空的,所以线程都在wait
  4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒
  5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来

4.3 线程池-Executors默认线程池

概述 : JDK对线程池也进行了相关的实现,在真实企业开发中我们也很少去自定义线程池,而是使用JDK中自带的线程池。

我们可以使用Executors中所提供的静态方法来创建线程池

static ExecutorService newCachedThreadPool()   创建一个默认的线程池

static newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池

代码实现 :

package com.xxx.mythreadpool;


//static ExecutorService newCachedThreadPool()   创建一个默认的线程池
//static newFixedThreadPool(int nThreads)	    创建一个指定最多线程数量的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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

        //1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
        ExecutorService executorService = Executors.newCachedThreadPool();
        //Executors --- 可以帮助我们创建线程池对象
        //ExecutorService --- 可以帮助我们控制线程池

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        //Thread.sleep(2000);

        executorService.submit(()->{
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        executorService.shutdown();
    }
}

4.4 线程池-Executors创建指定上限的线程池

使用Executors中所提供的静态方法来创建线程池

static ExecutorService newFixedThreadPool(int nThreads) : 创建一个指定最多线程数量的线程池

代码实现 :

 package com.xxx.mythreadpool;
    
    //static ExecutorService newFixedThreadPool(int nThreads)
    //创建一个指定最多线程数量的线程池
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    
    public class MyThreadPoolDemo2 {
        public static void main(String[] args) {
            //参数不是初始值而是最大值
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
            System.out.println(pool.getPoolSize());//0
    
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName() + "在执行了");
            });
    
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName() + "在执行了");
            });
    
            System.out.println(pool.getPoolSize());//2
    //        executorService.shutdown();
        }
    }

4.5 线程池-ThreadPoolExecutor

创建线程池对象 :

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

代码实现 :

 package com.xxx.mythreadpool;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class MyThreadPoolDemo3 {
    //    参数一:核心线程数量
    //    参数二:最大线程数
    //    参数三:空闲线程最大存活时间
    //    参数四:时间单位
    //    参数五:任务队列
    //    参数六:创建线程工厂
    //    参数七:任务的拒绝策略
        public static void main(String[] args) {
            ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            pool.submit(new MyRunnable());
            pool.submit(new MyRunnable());
    
            pool.shutdown();
        }
    }

4.6 线程池-参数详解

在这里插入图片描述

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    
corePoolSize:   核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime:  空闲线程最大存活时间,不能小于0
unit:           时间单位
workQueue:      任务队列,不能为null
threadFactory:  创建线程工厂,不能为null      
handler:        任务的拒绝策略,不能为null  

4.7 线程池-非默认任务拒绝策略

RejectedExecutionHandler是jdk提供的一个任务拒绝策略接口,它下面存在4个子类。

ThreadPoolExecutor.AbortPolicy: 		    丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 		   丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:    抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy:        调用任务的run()方法绕过线程池直接执行。

注:明确线程池对多可执行的任务数 = 队列容量 + 最大线程数

案例演示1:演示ThreadPoolExecutor.AbortPolicy任务处理策略

 public class ThreadPoolExecutorDemo01 {
    
        public static void main(String[] args) {
    
            /**
             * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
             */
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                    new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.AbortPolicy()) ;
    
            // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用AbortPolicy这个任务处理策略的时候,就会抛出异常
            for(int x = 0 ; x < 5 ; x++) {
                threadPoolExecutor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
                });
            }
        }
    }

控制台输出结果

pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
pool-1-thread-3---->> 执行了任务

控制台报错,仅仅执行了4个任务,有一个任务被丢弃了

案例演示2:演示ThreadPoolExecutor.DiscardPolicy任务处理策略

  public class ThreadPoolExecutorDemo02 {
        public static void main(String[] args) {
            /**
             * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
             */
            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                    new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardPolicy()) ;
    
            // 提交5个任务,而该线程池最多可以处理4个任务,当我们使用DiscardPolicy这个任务处理策略的时候,控制台不会报错
            for(int x = 0 ; x < 5 ; x++) {
                threadPoolExecutor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
                });
            }
        }
    }

控制台输出结果

pool-1-thread-1---->> 执行了任务
pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务

控制台没有报错,仅仅执行了4个任务,有一个任务被丢弃了

案例演示3:演示ThreadPoolExecutor.DiscardOldestPolicy任务处理策略

 public class ThreadPoolExecutorDemo02 {
        public static void main(String[] args) {
            /**
             * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
             */
            ThreadPoolExecutor threadPoolExecutor;
            threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                    new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.DiscardOldestPolicy());
            // 提交5个任务
            for(int x = 0 ; x < 5 ; x++) {
                // 定义一个变量,来指定指定当前执行的任务;这个变量需要被final修饰
                final int y = x ;
                threadPoolExecutor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "---->> 执行了任务" + y);
                });     
            }
        }
    }

控制台输出结果

pool-1-thread-2---->> 执行了任务2
pool-1-thread-1---->> 执行了任务0
pool-1-thread-3---->> 执行了任务3
pool-1-thread-1---->> 执行了任务4

由于任务1在线程池中等待时间最长,因此任务1被丢弃。

案例演示4:演示ThreadPoolExecutor.CallerRunsPolicy任务处理策略

  public class ThreadPoolExecutorDemo04 {
        public static void main(String[] args) {
    
            /**
             * 核心线程数量为1 , 最大线程池数量为3, 任务容器的容量为1 ,空闲线程的最大存在时间为20s
             */
            ThreadPoolExecutor threadPoolExecutor;
            threadPoolExecutor = new ThreadPoolExecutor(1 , 3 , 20 , TimeUnit.SECONDS ,
                    new ArrayBlockingQueue<>(1) , Executors.defaultThreadFactory() , new ThreadPoolExecutor.CallerRunsPolicy());
    
            // 提交5个任务
            for(int x = 0 ; x < 5 ; x++) {
                threadPoolExecutor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + "---->> 执行了任务");
                });
            }
        }
    }

控制台输出结果

pool-1-thread-1---->> 执行了任务
pool-1-thread-3---->> 执行了任务
pool-1-thread-2---->> 执行了任务
pool-1-thread-1---->> 执行了任务
main---->> 执行了任务

通过控制台的输出,我们可以看到次策略没有通过线程池中的线程执行任务,而是直接调用任务的run()方法绕过线程池直接执行。

5. 原子性

5.1 volatile-问题

代码分析 :

 package com.xxx.myvolatile;
    
    public class Demo {
        public static void main(String[] args) {
            MyThread1 t1 = new MyThread1();
            t1.setName("小路同学");
            t1.start();
    
            MyThread2 t2 = new MyThread2();
            t2.setName("小皮同学");
            t2.start();
        }
    }

    package com.xxx.myvolatile;
    
    public class Money {
        public static int money = 100000;
    }

    package com.xxx.myvolatile;
    
    public class MyThread1 extends  Thread {
        @Override
        public void run() {
            while(Money.money == 100000){
    
            }
    
            System.out.println("结婚基金已经不是十万了");
        }
    }
    

    package com.xxx.myvolatile;
    
    public class MyThread2 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            Money.money = 90000;
        }
    }

程序问题 : 女孩虽然知道结婚基金是十万,但是当基金的余额发生变化的时候,女孩无法知道最新的余额。

5.2 volatile解决

以上案例出现的问题 :

当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题 

1,堆内存是唯一的,每一个线程都有自己的线程栈。

2 ,每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。

3 ,在线程中,每一次使用是从变量的副本中获取的。

Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值

代码实现 : 使用volatile关键字解决

package com.xxx.myvolatile;

public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("小路同学");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("小皮同学");
        t2.start();
    }
}

package com.xxx.myvolatile;

public class Money {
    public static volatile int money = 100000;
}

package com.xxx.myvolatile;

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(Money.money == 100000){

        }

        System.out.println("结婚基金已经不是十万了");
    }
}


package com.xxx.myvolatile;

public class MyThread2 extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Money.money = 90000;
    }
}

5.3 synchronized解决

synchronized解决 :

1 ,线程获得锁

2 ,清空变量副本

3 ,拷贝共享变量最新的值到变量副本中

4 ,执行代码

5 ,将修改后变量副本中的值赋值给共享数据

6 ,释放锁

代码实现 :

package com.xxx.myvolatile2;

public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.setName("小路同学");
        t1.start();

        MyThread2 t2 = new MyThread2();
        t2.setName("小皮同学");
        t2.start();
    }
}

package com.xxx.myvolatile2;

public class Money {
    public static Object lock = new Object();
    public static volatile int money = 100000;
}

package com.xxx.myvolatile2;

public class MyThread1 extends  Thread {
    @Override
    public void run() {
        while(true){
            synchronized (Money.lock){
                if(Money.money != 100000){
                    System.out.println("结婚基金已经不是十万了");
                    break;
                }
            }
        }
    }
}

package com.xxx.myvolatile2;

public class MyThread2 extends Thread {
    @Override
    public void run() {
        synchronized (Money.lock) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Money.money = 90000;
        }
    }
}

5.4 原子性

概述 : 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。

代码实现 :

package com.xxx.threadatom;

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
class MyAtomThread implements Runnable {
    private volatile int count = 0; //送冰淇淋的数量

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //1,从共享数据中读取数据到本线程栈中.
            //2,修改本线程栈中变量副本的值
            //3,会把本线程栈中变量副本的值赋值给共享数据.
            count++;
            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}

代码总结 : count++ 不是一个原子性操作, 他在执行的过程中,有可能被其他线程打断

5.5 volatile关键字不能保证原子性

解决方案 : 我们可以给count++操作添加锁,那么count++操作就是临界区中的代码,临界区中的代码一次只能被一个线程去执行,所以count++就变成了原子操作。

 package com.xxx.threadatom2;
    
    public class AtomDemo {
        public static void main(String[] args) {
            MyAtomThread atom = new MyAtomThread();
    
            for (int i = 0; i < 100; i++) {
                new Thread(atom).start();
            }
        }
    }
    class MyAtomThread implements Runnable {
        private volatile int count = 0; //送冰淇淋的数量
        private Object lock = new Object();
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                //1,从共享数据中读取数据到本线程栈中.
                //2,修改本线程栈中变量副本的值
                //3,会把本线程栈中变量副本的值赋值给共享数据.
                synchronized (lock) {
                    count++;
                    System.out.println("已经送了" + count + "个冰淇淋");
                }
            }
        }
    }

5.6 原子性_AtomicInteger

概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变

量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解

使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:

AtomicBoolean: 原子更新布尔类型

AtomicInteger: 原子更新整型

AtomicLong: 原子更新长整型

以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:

public AtomicInteger():	   			    初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue):  初始化一个指定值的原子型Integer

int get():   			 				获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():     				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。

代码实现 :

 package com.xxx.threadatom3;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyAtomIntergerDemo1 {
    //    public AtomicInteger():	               初始化一个默认值为0的原子型Integer
    //    public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
        public static void main(String[] args) {
            AtomicInteger ac = new AtomicInteger();
            System.out.println(ac);
    
            AtomicInteger ac2 = new AtomicInteger(10);
            System.out.println(ac2);
        }
    
    }

    package com.xxx.threadatom3;
    
    import java.lang.reflect.Field;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyAtomIntergerDemo2 {
    //    int get():   		 		获取值
    //    int getAndIncrement():     以原子方式将当前值加1,注意,这里返回的是自增前的值。
    //    int incrementAndGet():     以原子方式将当前值加1,注意,这里返回的是自增后的值。
    //    int addAndGet(int data):	 以原子方式将参数与对象中的值相加,并返回结果。
    //    int getAndSet(int value):  以原子方式设置为newValue的值,并返回旧值。
        public static void main(String[] args) {
    //        AtomicInteger ac1 = new AtomicInteger(10);
    //        System.out.println(ac1.get());
    
    //        AtomicInteger ac2 = new AtomicInteger(10);
    //        int andIncrement = ac2.getAndIncrement();
    //        System.out.println(andIncrement);
    //        System.out.println(ac2.get());
    
    //        AtomicInteger ac3 = new AtomicInteger(10);
    //        int i = ac3.incrementAndGet();
    //        System.out.println(i);//自增后的值
    //        System.out.println(ac3.get());
    
    //        AtomicInteger ac4 = new AtomicInteger(10);
    //        int i = ac4.addAndGet(20);
    //        System.out.println(i);
    //        System.out.println(ac4.get());
    
            AtomicInteger ac5 = new AtomicInteger(100);
            int andSet = ac5.getAndSet(20);
            System.out.println(andSet);
            System.out.println(ac5.get());
        }
    }

5.7 AtomicInteger-内存解析

AtomicInteger原理 : 自旋锁 + CAS 算法

CAS算法:

有3个操作数(内存值V, 旧的预期值A,要修改的值B)

当旧的预期值A == 内存值   此时修改成功,将V改为B                 

当旧的预期值A!=内存值   此时修改失败,不做任何操作                 

并重新获取现在的最新值(这个重新获取的动作就是自旋)

5.8 AtomicInteger-源码解析

代码实现 :

 package com.xxx.threadatom4;
    
    public class AtomDemo {
        public static void main(String[] args) {
            MyAtomThread atom = new MyAtomThread();
    
            for (int i = 0; i < 100; i++) {
                new Thread(atom).start();
            }
        }
    }

    package com.xxx.threadatom4;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyAtomThread implements Runnable {
        //private volatile int count = 0; //送冰淇淋的数量
        //private Object lock = new Object();
        AtomicInteger ac = new AtomicInteger(0);
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                //1,从共享数据中读取数据到本线程栈中.
                //2,修改本线程栈中变量副本的值
                //3,会把本线程栈中变量副本的值赋值给共享数据.
                //synchronized (lock) {
    //                count++;
    //                ac++;
                int count = ac.incrementAndGet();
                System.out.println("已经送了" + count + "个冰淇淋");
               // }
            }
        }
    }
    

源码解析 :

  //先自增,然后获取自增后的结果
    public final int incrementAndGet() {
            //+ 1 自增后的结果
            //this 就表示当前的atomicInteger(值)
            //1    自增一次
            return U.getAndAddInt(this, VALUE, 1) + 1;
    }
    
    public final int getAndAddInt(Object o, long offset, int delta) {
            //v 旧值
            int v;
            //自旋的过程
            do {
                //不断的获取旧值
                v = getIntVolatile(o, offset);
                //如果这个方法的返回值为false,那么继续自旋
                //如果这个方法的返回值为true,那么自旋结束
                //o 表示的就是内存值
                //v 旧值
                //v + delta 修改后的值
            } while (!weakCompareAndSetInt(o, offset, v, v + delta));
                //作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
                //                                 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
                //如果修改失败,那么继续自旋。
            return v;
    }

5.9 悲观锁和乐观锁

synchronized和CAS的区别 :

相同点:在多线程情况下,都可以保证共享数据的安全性。

不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)

cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。

如果别人修改过,那么我再次获取现在最新的值。            

 如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)

6. 并发工具类

6.1 并发工具类-Hashtable

Hashtable出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

代码实现 :

  package com.xxx.mymap;
    
    import java.util.HashMap;
    import java.util.Hashtable;
    
    public class MyHashtableDemo {
        public static void main(String[] args) throws InterruptedException {
            Hashtable<String, String> hm = new Hashtable<>();
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 25; i++) {
                    hm.put(i + "", i + "");
                }
            });
    
    
            Thread t2 = new Thread(() -> {
                for (int i = 25; i < 51; i++) {
                    hm.put(i + "", i + "");
                }
            });
    
            t1.start();
            t2.start();
    
            System.out.println("----------------------------");
            //为了t1和t2能把数据全部添加完毕
            Thread.sleep(1000);
    
            //0-0 1-1 ..... 50- 50
    
            for (int i = 0; i < 51; i++) {
                System.out.println(hm.get(i + ""));
            }//0 1 2 3 .... 50
    
    
        }
    }

6.2 并发工具类-ConcurrentHashMap基本使用

ConcurrentHashMap出现的原因 : 在集合类中HashMap是比较常用的集合对象,但是HashMap是线程不安全的(多线程环境下可能会存在问题)。为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下。

基于以上两个原因我们可以使用JDK1.5以后所提供的ConcurrentHashMap。

体系结构 :

在这里插入图片描述

总结 :

1 ,HashMap是线程不安全的。多线程环境下会有数据安全问题

2 ,Hashtable是线程安全的,但是会将整张表锁起来,效率低下

3,ConcurrentHashMap也是线程安全的,效率较高。     在JDK7和JDK8中,底层原理不一样。

代码实现 :

 package com.xxx.mymap;
    
    import java.util.Hashtable;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class MyConcurrentHashMapDemo {
        public static void main(String[] args) throws InterruptedException {
            ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);
    
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 25; i++) {
                    hm.put(i + "", i + "");
                }
            });
    
    
            Thread t2 = new Thread(() -> {
                for (int i = 25; i < 51; i++) {
                    hm.put(i + "", i + "");
                }
            });
    
            t1.start();
            t2.start();
    
            System.out.println("----------------------------");
            //为了t1和t2能把数据全部添加完毕
            Thread.sleep(1000);
    
            //0-0 1-1 ..... 50- 50
    
            for (int i = 0; i < 51; i++) {
                System.out.println(hm.get(i + ""));
            }//0 1 2 3 .... 50
        }
    }

6.3 并发工具类-ConcurrentHashMap1.7原理

在这里插入图片描述

6.4 并发工具类-ConcurrentHashMap1.8原理

在这里插入图片描述

总结 :

1,如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做。     在第一次添加元素的时候创建哈希表

2,计算当前元素应存入的索引。

3,如果该索引位置为null,则利用cas算法,将本结点添加到数组中。

4,如果该索引位置不为null,则利用volatile关键字获得当前位置最新的结点地址,挂在他下面,变成链表。		

5,当链表的长度大于等于8时,自动转换成红黑树6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性

6.5 并发工具类-CountDownLatch

CountDownLatch类 :

方法 解释
public CountDownLatch(int count) 参数传递线程数,表示等待线程数量
public void await() 让线程等待
public void countDown() 当前线程执行完毕

使用场景: 让某一条线程等待其他线程执行完毕之后再执行

代码实现 :

package com.xxx.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread1 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}


package com.xxx.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread2 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}


package com.xxx.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class ChileThread3 extends Thread {

    private CountDownLatch countDownLatch;
    public ChileThread3(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }
    @Override
    public void run() {
        //1.吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName() + "在吃第" + i + "个饺子");
        }
        //2.吃完说一声
        //每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}


package com.xxx.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class MotherThread extends Thread {
    private CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        //1.等待
        try {
            //当计数器变成0的时候,会自动唤醒这里等待的线程。
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //2.收拾碗筷
        System.out.println("妈妈在收拾碗筷");
    }
}


package com.xxx.mycountdownlatch;

import java.util.concurrent.CountDownLatch;

public class MyCountDownLatchDemo {
    public static void main(String[] args) {
        //1.创建CountDownLatch的对象,需要传递给四个线程。
        //在底层就定义了一个计数器,此时计数器的值就是3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        //2.创建四个线程对象并开启他们。
        MotherThread motherThread = new MotherThread(countDownLatch);
        motherThread.start();

        ChileThread1 t1 = new ChileThread1(countDownLatch);
        t1.setName("小明");

        ChileThread2 t2 = new ChileThread2(countDownLatch);
        t2.setName("小红");

        ChileThread3 t3 = new ChileThread3(countDownLatch);
        t3.setName("小刚");

        t1.start();
        t2.start();
        t3.start();
    }
}

总结 :

1. CountDownLatch(int count):参数写等待线程的数量。并定义了一个计数器。

2. await():让线程等待,当计数器为0时,会唤醒等待的线程

3. countDown(): 线程执行完毕时调用,会将计数器-1。

6.6 并发工具类-Semaphore

使用场景 :

可以控制访问特定资源的线程数量。

实现步骤 :

1,需要有人管理这个通道

2,当有车进来了,发通行许可证

3,当车出去了,收回通行许可证

4,如果通行许可证发完了,那么其他车辆只能等着

代码实现 :

  package com.xxx.mysemaphore;
    
    import java.util.concurrent.Semaphore;
    
    public class MyRunnable implements Runnable {
        //1.获得管理员对象,
        private Semaphore semaphore = new Semaphore(2);
        @Override
        public void run() {
            //2.获得通行证
            try {
                semaphore.acquire();
                //3.开始行驶
                System.out.println("获得了通行证开始行驶");
                Thread.sleep(2000);
                System.out.println("归还通行证");
                //4.归还通行证
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    package com.xxx.mysemaphore;
    
    public class MySemaphoreDemo {
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable();
    
            for (int i = 0; i < 100; i++) {
                new Thread(mr).start();
            }
        }
    }
JavaSE多线程 编写一个类,直接继承java. lang. Thread,重写run方法。 阅读详情

相关推荐

JavaSE-多线程知识

JavaSE-多线程知识

lancehao的博客 194

JAVA线程池submit详解 ,execute和submit提交任务的区别

JAVA线程池submit详解 ,execute和submit提交任务的区别

胖虎儿的博客 1万+

Innovus ECO修复时序违例:ecoAddRepeater插Buffer原理与实战

芯片物理设计越到后期,时序收敛越依赖局部修正而非全局重跑。ECO(Engineering Change Order)作为数字后端高频操作,能在不破坏既有布局布线结果的前提下,精准修复DRV违例、hold时序问题或实现功能补丁。其中,插入Buffer或Inverter是拆解长线RC负载、改善max_transition/max_capacitance最直接的手段。Innovus提供的ecoAddRepeater命令封装了单元选择、坐标摆放和网表重连等底层细节,配合ecoRoute可快速完成绕线。合理选型Buf

weixin_31858171的博客 300

多线程(二) | 彻底搞懂线程池-Executors

[原文约7000字] 上篇文章,我们讲解了通过Thread和 Runnable 使用线程的方法,并且演示了如何创建一个线程并启动,今天我们来聊一聊多线程中的线程池。 一、为什么要使用线程池 我们使用多线程的一个一般步骤是: 先创建一个线程,然后线程执行线程任务,线程任务执行完毕后,线程会被销毁。 但是线程的创建和销毁是比较耗费资源的。在高并发场景之下,如果需要开启大量的线程,而每个线程它执行任务所需的时间很短的情况下,那么线程的频繁的创建和销毁就会成为性能的瓶颈。所以线程池应运而生。线程池的主要功能

lsqingfeng的博客 4167

win2016 php mysql_Windows Server 2016 IIS10.0+PHP(FastCGI)+MySQL环境搭建教程 | 系统运维...

准备篇一、环境说明:操作系统:Windows Server 2016PHP版本:php 7.1.0MySQL版本:MySQL 5.7.17.0二、相关软件下载:1、PHP下载地址:2、MySQL下载地址:3、Microsoft Visual C++ 2010 可再发行组件包(安装MySQL需要此插件)4、Visual C++ Redistributable for Visual Studio 20...

weixin_34794420的博客 1377

ORACLE 12C RAC 启动报错,错误代码 ORA-27300,ORA-27301,ORA-27302

非一体机,12.2 无任何补丁,2套X86服务器,1节点集群异常(数据库正常,还有会话),2节点正常工作,负载不高,硬件、空间均未检查到异常,多路径存在掉线的情况,但通过Mulitpath -ll 看又是正常的,2根私有网络,其中一根不同,sysctl中配置的稀疏模式。5、再次加成crs alert ohas等集群日志,以及根据现象和错误再次到后台sr中查找可用信息,发现有一个类似问题,12.2 RAC 无任何补丁,报同样错误,解决方案为重启两套服务器。查询MOS文档以及后台文档,排除上面指出的问题。

weixin_42344665的博客 1464

线程池ThreadPoolExecutor的submit方法

Future submit(Callable task);     Future submit(Runnable task, T result);     Future submit(Runnable task); 三个重载方法,第一个很好理解,传入callable,本身call函数就有一个返回值,可以正常取得。 第二个第三个就不清楚怎么可以取到返回值了,因为Runnabl

ccityzh的博客 1万+

全志T113 Tina5.0开发:(一)添加自己的板级支持包

3.验证 先执行 source build/envsetup.sh ,然后lunch,下图可见9就是我们自己添加的板子。2. 1)修改 t113-evb1.mk重命名为t113-ws_devboard。我使用的spinand,只需要修改openwrt里面的文件即可。以下内容仅作为自己笔记,如果错误欢迎留言指出。修改t113-ws_devboard。

u014428915的博客 1164

executor.execute()与 executor.submit()区别

方法则适用于需要获取任务结果、进行精细的异常处理、监控任务状态或可能需要取消任务的情况。提供了诸如检查任务是否已完成、阻塞等待任务完成、获取任务结果或取消任务等操作。方法更适合用于提交不需要返回结果、异常处理相对简单且不需要取消功能的任务。接口的任务,这类任务没有返回值,通常用于执行纯粹的操作或计算。方法,允许尝试取消尚未开始或正在进行的任务。:当提交的任务在执行过程中抛出异常时,该异常将被传递给线程的。提交一个任务时,无法直接获取任务执行的结果或状态。,所以无法直接取消已提交的任务或查询其执行状态。

qq_31532979的贺公子之数据科学与艺术博客,致力于科技向善,拥抱开源,要用技术的影响力来领导团队,而不是威权和职位! 3483

RK3568驱动 SPI主/从 配置

RK3568 SPI 配置指南 内核配置:启用用户空间驱动(主)和从模式协议支持。 主模式:设备树设置status="okay"、max-freq及引脚复用,子节点compatible="rockchip,spidev"。 从模式:需添加spi-slave属性,子节点命名以slave@X开头。 编译烧写:修改设备树后编译内核或全量镜像,验证/dev/spidevX.Y节点。 测试:使用spidev_test工具,主模式可环回测试,从模式需与主设备通信。 (关键:从模式强制spi-slave和命名规则,频率匹

文石_2009的博客 1144

RuntimeError: The current installed version of g++ (4.8.5) is less than the mininum version CUDA解决方案

本文主要介绍了RuntimeError: The current installed version of g++ (4.8.5) is less than the mininum version CUDA解决方案,希望能对学习python的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 8万+

JavaSE-多线程

这里写自定义目录标题学习主题:JavaSE-多线程学习前言:线程实现:线程的状态: 多线程 学习主题:JavaSE-多线程 多线程的本质就是多条路径同时执行 学习前言: 1.一条路径 程序按顺序执行,线程安全,但是CPU利用率低 2.两条路径 即为多线程,可以并行执行,但是存在线程不安全问题 3.进程 线程是操作系统的运行调度最小单位,是进程的运行单位 a:一个进程可以有多个线程 b:一个进程中的线程共享相同的内存单元 4.线程的优缺点 a:资源利用率高,程序响应快,程序设计存在便利 b:设计复杂(复

weixin_44158590的博客 464

嵌入式编程模型 | 原型模式

本文介绍了原型模式在嵌入式传感器网络中的应用。原型模式通过复制现有对象创建新实例,避免了重复初始化开销,适用于设备配置克隆、状态复制等场景。文章以传感器网络为例,展示了原型模式的结构(抽象原型类、具体原型类、客户端)和实现方式,通过C语言代码演示了温度/湿度传感器的克隆过程。该模式能有效提升嵌入式系统资源复用效率,实现快速部署相似配置的传感器节点,同时保持内存优化和配置灵活性。

嵌入式大杂烩 146

JavaSE多线程笔记

javase多线程笔记

cc_1113的博客 916

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

JavaSE——多线程

首先要弄明白一点,不等于操作系统级别的线程,Java创建的线程是Java虚拟机管理的,是。提供了统一的API,屏蔽了不同操作系统的差异性。操作系统级别的线程是由操作系统内核直接管理和调度的实体。它们是 CPU 调度的基本单位。现代 JVM 实现中,,即所谓的。ThreadC语言创建的线程,才是真正意义上的操作系统上的线程。但是C语言在类Unix使用pThread来创建线程,但是在Windows使用的是Win32 API。所以。

weixin_69032308的博客 1072

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

JavaSE多线程《二》

JavaSE多线程《一》回顾 三·多线程的常用操作方法 3.1 线程的命名于取得命名 多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。在Thread类中提供有如下的线程名称方法: 要想取得线程的对象,在Thread类中提供有一个方法取得当前线程对象: /** * Returns a reference to the currently...

LeBronJJJ的博客 235

JavaSE多线程《三》

往期回顾: JavaSE多线程《一》 JavaSE多线程《二》 3.5 线程停止 多线程中有三种方式可以停止线程。 设置标记位,可以使线程正常退出。 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。 使用Thread类中的一个 interrupt() 可以中断线程。 首先示例,利用设置标记为flag 使线程停止: 代码如下: package com.revisi...

LeBronJJJ的博客 237

JavaSE后阶(多线程)

Java多线程

2401_85776998的博客 1078

JavaSE基础:多线程

JavaSE基础:多线程 文章目录JavaSE基础:多线程前言一、基本概念1、什么是进程?什么是线程?2、java程序中的线程3、进程和线程的关系4、对于单核的CPU来说,真的可以做到真正的多线程并发吗?5、实现线程的两种方式6、线程对象的生命周期二、线程的调度和数据安全1、常用的线程调度模型2、有关线程调度的方法3、数据安全(重点)4、存在安全问题的三个条件5、解决线程安全问题6、异步和同步编程模型7、三大变量的线程安全问题8、局部变量的使用9、synchronized三种写法三、守护线程1、概念2、例子

weixin_44675404的博客 493

JavaSE总结 多线程 网络编程

JavaSE多线程 多线程

天上飞的云传奇 363

JavaSE_10多线程

JavaSE_10多线程

圆静居士的专栏 347

JavaSE多线程之线程种类

JavaSE多线程之线程种类 1.线程控制之休眠线程 A:线程休眠: public static void sleep(long millis) 线程休眠 B:案例演示: 线程休眠 2.线程控制之加入线程 A:加入线程: public final void join() 意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行 注意事项: 在线程启动之后,在调用方法 B:案例演示: 加入线程 ![在这里插入图片描述](https://img-blog.csdnimg.cn/202006050746

ITzhazhassc的博客 192

JavaSE超详细笔记-多线程篇-基于黑马

假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。他的作用就是去调用main方法,并执行里面的代码,之前在main方法里的代码,其实都是运行在main线程当中。接下来,以线程基础,线程优先级,守护线程,礼让线程,插入线程的几个角度去拆分。

超级苦力怕的博客 1249

JavaSE(多线程和线程池)知识点总结

​ 线程就是操作系统能够进行运算调度的最小单位,它被包含在进程(进程是程序的基本执行实体,任务管理器种选择的进程就可以看到每个软件的进程)中,是进程中实际运作的单位。​ 比如:360杀毒软件,包括木马查杀、电脑清理、系统修复功能。其中360杀毒软件就是进程,其中的功能就是线程。​ 简单理解:应用软件种相互独立,可以同时运行的功能就是线程。

weixin_46119796的博客 1745

Javase学习复习D16[多线程]

主线程 : CPU和内存之间开辟的转门为main方法服务的线程指的是两个或者两个以上的线程在执行的过程中由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况称之为死锁根据上图所示:线程1正在持有锁1,但是线程1必须再拿到锁2,才能继续执行而线程2正在持有锁2,但是线程2需要再拿到锁1,才能继续执行此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。

ysy1648067239的博客 1049
上一篇: Java中的IO流
下一篇: 网络编程入门-UDP通信程序-TCP通信程序
MemoryHer
博客等级 码龄5年 4粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值