Java进阶13(多线程、线程安全)

Part01:多线程

1、进程和线程的概念

  • 进程:正在运行的程序叫做进程;
  • 线程:是进程中的一个执行单元,来完成进程中的某个功能;一个进程中至少有一个线程;

2、多线程:多线程是指一个进程中有多个线程在同时执行;

3、程序运行原理

  • 分时调度:所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
  • 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)
  • Java使用的是抢占式调度;
  • 大部分操作系统都支持多进程并发运行,感觉好像多个进程在同一时刻运行着,实际上,CPU(中央处理器)使用抢占式调度模式在多个线程之间进行着高速的切换。对应CPU的一个核而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换的速度相对我们的感觉要快,看上去就是在同一时刻运行;

注意⚠️:多线程程序并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU的使用率更高;

4、主线程:

  • main方法所在的线程称为主线程
  • Java是多线程程序,但是JVM默认只创建一个线程(主线程)
  • 在同一个线程中的方法是至上而下运行的

5、Thread类:

  • Thread类就是我们说的线程类,提供了一堆和线程有关的方法;

  • 构造:public Thread( );//创建一个默认名字的线程对象
    public Thread(String name);//创建一个指定名字的线程对象

  • 创建线程的一种方式:继承Thread类,重写run方法;具体步骤如下

    //1.创建一个类MyThread继承线程类Thread
    public class MyThread extends Thread {
      //2.重写父类的run方法
      @Override
      public void run() {
          //3.把任务代码放入run方法中
          for (int i = 0;i<20;i++){
              System.out.println("线程"+getName()+i+"正在执行");
          }
      }
    }
    public class ThreadDemo {
     public static void main(String[] args){
          //4.创建子类线程对象
          MyThread thread = new MyThread();
          //5.开启线程
          thread.start();
      for (int i =0;i<20;i++){
          System.out.println("主线程"+Thread.currentThread().getName()+i+"正在执行");
      }
     }
    }
    

6、Runnable接口:

  • 创建线程的另一种方式:实现Runnable接口,实现run方法;具体步骤如下:

    //1.实现Runnable接口
    public class MyRunnable implements Runnable {
      //2.重写run方法
      @Override
      public void run() {
          //写任务代码
          for (int i = 0;i<20;i++){
              System.out.println("线程"+getName()+i+"正在执行");
          }
      }
    }
    public class RunnableDemo {
      public static void main(String[] args){
          //3.创建实现类对象
          MyRunnable mr = new MyRunnable();
          //4.创建Thread对象,并把实现类对象作为参数传递
          Thread td = new Thread(mr);
          //5.启动这个线程
          td.start();
      for (int i =0;i<20;i++){
          System.out.println("主线程"+Thread.currentThread().getName()+i+"正在执行");
      }
     }
    }
    

7、两种创建线程方式比较:

  • 从耦合性分析:

    • 继承Thread类:线程和任务是紧紧联系在一起的,耦合性高
    • 实现Runnable接口:线程是线程,任务是任务,他们之间没有必然的联系,给一个线程传递哪个任务那么该线程就执行哪个任务,耦合性低;
  • 从代码的扩展性:

    • 继承Thread类:由于是继承Thread类,Java只支持类的单继承,所以子类线程不能再继承别的类
    • 实现Runnable接口:由于是实现接口,实现类同时可以继承别的类

因此:在开发中,我们一般用实现Runnable接口的方式创建线程

8、使用匿名内部类创建线程对象

  • 继承方式:

    new Thread(){
      @Override
      public void run() {
          for (int i =0;i<20;i++){
              System.out.println("线程"+getName()+i+"正在执行");
          }
      }
    }.start();
    
  • 实现方式:

    new Thread(new Runnable(){
      @Override
      public void run() {
          for (int i =0;i<20;i++){
              System.out.println("线程"+Thread.currentThread().getName()+i+"正在执行");
          }
      }
    }).start();
    

Part02:线程安全

1、线程安全
当有多个线程在同时运行,这些线程同时运行一段代码(即同一个任务,同一个run方法),操作同一个共享数据时,这时候可能就会出现线程的安全问题,即线程不安全;
注意⚠️:如果是单线程,或者是多线程操作的是不同数据,那么一般是没有问题的
2、解决线程安全的方式:

  • 同步代码块、给代码加上同步锁

    //格式:
    synchronized(锁对象){
       代码
    }
    //锁对象可以是任意对象
    
    //以卖票为例
    public class SaleTicketDemo {
      public static void main(String[] args) {
          //3个线程必须是同一个任务
          TicketsRunable t = new TicketsRunable();
          new Thread(t, "窗口1").start();
          new Thread(t, "窗口2").start();
          new Thread(t, "窗口3").start();
      }
    }
    
    class TicketsRunable implements Runnable {
      private int tickets = 100;
      private Object obj = new Object();
    
    @Override
    public void run() {
        while (true) {
          //同步锁
          synchronized (obj) {
              if(tickets > 0) {
                  System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票");
                  tickets--;
              }
          }
         
      }
     }
    }
    
  • 同步方法

    //格式:
    public synchronized void 方法名(){
      代码
    }
    
    //以卖票为例
    public class SaleTicketDemo {
      public static void main(String[] args) {
          //3个线程必须是同一个任务
          TicketsRunable t = new TicketsRunable();
          new Thread(t, "窗口1").start();
          new Thread(t, "窗口2").start();
          new Thread(t, "窗口3").start();
      }
    }
    
    class TicketsRunable implements Runnable {
      private int tickets = 100;
      private Object obj = new Object();
    @Override
    public void run() {
      while (true) {
          sale();
      }
    }
    
    public synchronized void sale() {
          System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票");
          tickets--;
      }
     }
    }
    

注意⚠️:
同步代码块和同步方法的原理一样,都是具有锁对象,哪个线程来执行,那么该线程就持有这个锁对象;同步方法使用的锁对象是当前对象 this,如果同步方法是静态方法,那么他的锁对象是当前类.class

  • Lock接口方式:
    • 我们实际用的是他的一个实现类:ReentrantLock

    • Lock接口中的两个方法:lock( );//上锁、unlock( );//释放锁

      //以卖票为例
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class SaleTicketDemo {
      public static void main(String[] args) {
        //3个线程必须是同一个任务
        TicketsRunable t = new TicketsRunable();
        new Thread(t, "窗口1").start();
        new Thread(t, "窗口2").start();
        new Thread(t, "窗口3").start();
        }
      }
      
      class TicketsRunable implements Runnable {
      private int tickets = 1000;
      Lock l = new ReentrantLock();
      @Override
      public void run() {
       while (true) {
            l.lock();
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票");
                tickets--;
            }
            l.unlock();
        }
       }
      }
      

3、线程的状态图解
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值