JAVA中多线程

本文深入探讨了Java中的多线程概念,包括进程与线程的区别、线程的并发执行以及线程安全问题。通过实例展示了通过Thread类和Runnable接口创建线程,并介绍了线程的调度、控制(如休眠、礼让、守护线程)以及线程安全的解决方案,如同步代码块和Lock锁。此外,还讨论了死锁问题及其避免方法。
摘要由CSDN通过智能技术生成

多线程

1.进程概述及多进程的意义

  • 线程和进程
    要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
  • 进程概述
    通过任务管理器我们就可以看到进程的存在。
    • 概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
      每一个进程都有它自己的内存空间和系统资源。
  • 多进程的意义
    效率变高,Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
    并发执行
    因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快

2.线程,并行和并发的区别

  • 什么是线程
    一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

  • 多线程有什么意义呢?
    多线程的作用不是提高执行速度,而是为了提高应用程序的使用率

  • 并行和并发。
    前者是逻辑上同时发生,指在某一个时间内同时运行多个程序
    后者是物理上同时发生,指在某一个时间点同时运行多个程序。

  • 并发 : 多个线程交替执行

  • Java.exe 命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。

  • JVM 启动至少启动了垃圾回收线程和主线程,所以是多线程的。

3.多线程中Thread类

  • Thread类的基本获取和设置方法

    • - public final String getName()//获取线程名称
      - public final void setName(String name)//设置线程名称
      
    • 获取main方法所在的线程名称

      • public static Thread currentThread()//获取当前执行的线程
        
  • 线程调度及获取和设置线程优先级

    • 线程的执行

      • 线程有两种调度模型:
        • 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
        • 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,Java使用的是抢占式调度模型。
    • 如何设置和获取线程优先级

      • - public final int getPriority() //获取线程的优先级
        - public final void setPriority(int newPriority)//设置线程的优先级
        
      • 获取和设置线程优先级

        • 线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,所以有的时候一两次的运行说明不了问题
      • 获取线程的优先级:

        • public final int getPriority()返回线程的优先级。 
          
      • 给线程设置优先级:范围是 1—10

        • public final void setPriority(int newPriority)
          
  • 线程控制之休眠线程

public static void sleep(long millis//线程休眠

  • 线程控制之加入线程
public final void join() //让线程串行
	//注意事项: 在线程启动之后,在调用方法
  • 线程控制之礼让线程

    • 礼让线程:

      public static void yield():	暂停当前正在执行的线程对象,并执行其他线程。 
      
    • 礼让线程
      这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

  • 线程控制之守护线程

    • 守护线程:

      public final void setDaemon(boolean on):
      	 把该线程标记为守护线程 
      	 该方法必须在启动线程前调用。 
      
    • 守护线程的作用

      • Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。
    • 调用线程对象的方法setDaemon(true),设置线程为守护线程。

    • thread.setDaemon(true)必须在thread.start()之前设置。

    • 在Daemon线程中产生的新线程也是Daemon的。

    • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。

  • 线程控制之中断线程

public final void stop():		停止线程的运行
public void interrupt():		中断线程,当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞

4.多线程程序实现的方式:实现Runnable接口

  • 实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类

    • 如何获取线程名称————System.out.println(Thread.currentThread().getName() + “:” + i);
    • 如何给线程设置名称———— Thread th = new Thread(myRunnable, “线程A”);
    • 实现接口方式的好处————可以避免由于Java单继承带来的局限性。
  • 案例演示

    public class MyRunnable implements Runnable {
    
        //让线程来执行的方法
        @Override
        public void run() {
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                // System.out.println(Thread.currentThread().getName() + ":" + i);
                sum += i;
            }
        }
    }
    
    public class MyTest {
        public static void main(String[] args) {
           /* 1.创建线程的另一种方法是声明实现 Runnable 接口的类。
            2.该类然后实现 run 方法。然后可以分配该类的实例,
            3.在创建 Thread 时作为一个参数来传递并启动*/
    
            //Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。
            //Runnable任务
            MyRunnable myRunnable = new MyRunnable();
            Thread th = new Thread(myRunnable, "线程A");
            th.start();
            
        }
    }
    
    

5.多线程程序实现的方式:实现Callable 接口

  • 实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。

  • 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(200);
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }

        return sum;
    }
}
public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //如果我们想要在主线程获取到子线程执行完之后的结果,我可以使用这种方式
       /* 实现步骤
        1. 创建一个类实现Callable 接口
        2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
        3. 创建Thread类, 将FutureTask对象作为参数传进去
        4. 开启线程*/
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread thread = new Thread(task);
        thread.start();
        Integer integer = task.get();
        System.out.println(integer);

        //Runnable run() 没有返回值 没有抛出异常
        //Callable call() 有返回值的 可以抛出异常
    }
}

6.卖电影票案例(各种方式)(练习)

  • Thread类
public class MyTest {
    public static void main(String[] args) {
        // 需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
        //a: 三个窗口其实就是3个线程
        //  b: 定义票的数量100张
        //  c: 创建线程对象,启动线程. 每卖一张这个票数应该--
        SellTicketThread th1 = new SellTicketThread();
        SellTicketThread th2 = new SellTicketThread();
        SellTicketThread th3 = new SellTicketThread();
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();


    }
}


public class SellTicketThread extends Thread {
    //共享数据
    static int num = 100;

    @Override
    public void run() {
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
            }
        }
    }
}
  • Runnable接口
public class MyTest {
    public static void main(String[] args) {
        SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();//只new了一个对象,所以只调用一次成员变量
        Thread th1 = new Thread(sellTiketRunnable, "窗口1");
        Thread th2 = new Thread(sellTiketRunnable, "窗口2");
        Thread th3 = new Thread(sellTiketRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}


public class SellTiketRunnable implements Runnable {
    //共享数据
    static int num = 100;

    @Override
    public void run() {
        while (true) {
            if (num > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
            }
        }
    }
}

7.线程安全问题的产生原因分析及解决办法

  • 首先想为什么出现问题?(也是我们判断是否有问题的标准)

    • 是否是多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据
  • 如何解决多线程安全问题呢?
    上锁
    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

  • 需要使用同步代码块:

  •   synchronized(对象){
      //不能在括号了直接new 对象 new 了 就没效果
      要被同步的代码 ;
     }
    
  • 同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端

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

    • 就是把同步关键字加到方法上
    • 同步代码块的锁对象: 任意一个对象(同时出现须保持一致)
    • 同步方法的锁对象: 是this
    • 静态同步方法的锁对象:就是当前类对应的字节码文件对象

8.Lock锁的概述和使用

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

9.死锁问题概述和使用

  • 如果出现了同步嵌套,就容易产生死锁问题

  • 是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

  • 同步代码块的嵌套案例

    • 死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态

      public class MyThread extends Thread {
          private boolean flag;
      
          public MyThread(boolean flag) {
              this.flag = flag;
          }
      
          @Override
          public void run() {
              if (flag) {
                  synchronized (LockUtils.objA) {
                      System.out.println("true线程进来了持有 objA锁");
                      synchronized (LockUtils.objB) {
                          System.out.println("true线程进来了持有 objB锁");
                      } //释放objB
                  }//出了这里释放 objA
              } else {
                  synchronized (LockUtils.objB) {
                      System.out.println("false线程进来了持有 objB锁");
                      synchronized (LockUtils.objA) {
                          System.out.println("false线程进来了持有 objA锁");
                      }//objA
                  }//释放objB
              }
          }
      }
      
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值