多线程基础知识(上)

简介: 多线程基础知识(上)

正文


一、JUC知识


1.进程与线程


进程:进程是后台运行的一个程序,它是操作系统动态执行的基本单元。比如QQ、微信

线程:线程是程序运行中的某个功能,一个进程包含若干个线程,线程是独立运行和独立调度的基本单位。比如一个QQ或微信的视频聊天


2.并发与并行


并发:同一个时间应对多件事情的能力

并行:同一个时间动手做多件事情的能力

单核cpu下,线程实际是 串行执行  操作系统中的一个组件叫做任务调度器,将CPU的时间片(windows 时间片最小约为 15 毫秒)分为不同的程序使用,只是由于cpu在线程间(时间片很短)切换的非常快,直觉上觉得是 同时运行 总结一句话就是:微观串行,宏观并行


一般将 线程轮流使用CPU 的做法称为并发(concurrent)


444.png


333.png


多核CPU下,每个 核(core)都可以调度运行线程,这时候线程可以是并行的


222.png


111.png


举例说明


你的一天,起床,吃饭、学习、吃饭、学习、刷手机、睡觉,一个人轮流交替做多件事,这就是并发

如果有一天,你发现自己拥有查克拉,你使出了传说中的影分身之术,分出一个分身,一起完成你的一天,这时既有并发,也有并行(当两个人同时要做吃饭这件事时,碗只有一个,你们就会产生竞争,你们看着办吧)

当你的影分身之术达到登峰造极之时(ps:有影分身还敲什么编程喽,整个中国我罩啦),那就可以有几个分身,同时处理某几件事(ps:这就叫专业)这就是并行


3.创建线程


注:直接调用run(),只会执行同一个线程中的任务,不会启动新线程,应该调用start()


实现 Runnable

继承 Thread


package io.laokou.test.concurrent;
/**
* @author Kou Shenhai
* @version 1.0
* @date 2022/4/17 0017 下午 6:15
*/
public class ThreadTest {
  public static void main(String[] args) {
    //lambda
    Runnable run = () -> {
      System.out.println("implements Runnable");
    };
    new Thread(run).start();
    new MyThread().start();
  }
  static class MyThread extends Thread {
    @Override
    public void run() {
      System.out.println("extends Thread");
    }
  }
}
/**
* implements Runnable
* extends Thread
 */


4.线程状态


New:新创建状态,当一个线程处于新创建状态时,程序还没有开始运行线程中的代码

Runnable:可运行状态,一旦调用start(),线程处于runnable状态,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行时间(一个正在运行中的线程仍然处于可运行状态),一旦一个线程开始运行,它不必始终保持运行,事实上,运行中的线程被中断,目的是为了让其他线程获取运行机会。线程调度的细节依赖于操作系统提供的服务。抢占式调度系统给每一个可运行线程一个时间片执行任务,当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程运行机会,当选择下一个线程时,操作系统考虑线程的优先级。在多个处理器的机器上,每一个处理器运行一个线程,可以多个线程并发运行,如果线程数目多于处理器的数目,调度器依然采用时间片机制。

Blocked:被阻塞状态,一个线程试图获取一个内部对象锁(不是java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态,当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态

Waiting:等待状态,当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态(调用Object.wait()或Thread.join(),等待java.util.concurrent库中的Lock或Condition)

Timed waiting:计时等待状态,调用待超时参数的方法,就会导致线程进入计时等待状态,这一状态将一直保持到超时期满或接收到适当的通知。带有超时参数的方法有Thread.sleep()、Object.wait()、Thread.join()、Lock.tryLock()以及Condition.await()

Terminated:被终止状态,(1).因为run()正常退出而自然死亡。(2).因为一个没有捕获的异常终止run()而意外死亡

线程所具有状态及从一个状态到另一个状态的转换,当一个线程被阻塞或等待时(或终止时),另外一个线程被调度为运行状态,当一个线程被重新激活(例如,因为超时期满或成功获得一个锁),调度器检查它是否具有比当前运行线程更高的优先级,如果是这样的,调度器从当前运行线程中挑选一个,剥夺其运行权,选择一个新的线程运行


111.png


5.使用interrupt()终止线程


interrupt():向线程发送中断请求,线程的中断状态将设置为true,并不是真正停止线程,如果当前线程被一个sleep调用阻塞,则抛出InterruptedException

interrupted():测试当前线程是否被中断,注意,这是一个静态方法,执行后将当前线程的中断状态重置为false

isInterrupted():测试当前线程是否被终止,不像静态的中断方法,不改变线程的中断状态


package io.laokou.test.concurrent;
/**
* @author Kou Shenhai
* @version 1.0
* @date 2022/4/17 0017 下午 6:41
*/
public class InterruptThreadTest {
  public static void main(String[] args) {
    System.out.println("运行线程...");
    MyInterruptThreadTest thread = new MyInterruptThreadTest();
    thread.start();
    thread.interrupt();
    System.out.println("中断线程...");
  }
  static class MyInterruptThreadTest extends Thread {
    @Override
    public void run() {
      for (int i = 0; i < 10; i++) {
        String threadName = getName();
        System.out.println("i=" + i);
        if (isInterrupted()) {
          System.out.println("通过isInterrupted()检测到中断");
          System.out.println("线程" + threadName + "执行第一个interrupted() > " + interrupted());
          System.out.println("线程" + threadName + "执行第二个interrupted() > " + interrupted());
        }
        try {
          sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      System.out.println("循环结束,线程结束...");
    }
  }
}
/**
* 运行线程...
* 中断线程...
* i=0
* 通过isInterrupted()检测到中断
* 线程Thread-0执行第一个interrupted() > true
* 线程Thread-0执行第二个interrupted() > false
* i=1
* i=2
* i=3
* i=4
* i=5
* i=6
* i=7
* i=8
* i=9
* 循环结束,线程结束...
 */


6.线程优先级


注:yield()导致当前执行线程处于让步状态,如果其他的可运行线程具有与此线程同样高的优先级,那么这些线程接下来会被调度,注意,这是一个静态方法


(1).每一个线程都有一个优先级,默认情况下,一个线程继承它的父线程优先级。使用setPriority()来提高或降低任何一个线程的优先级,可以将优先级设置为MIN_PRIORITY(Thread类定义为1),与MAX_PORIORITY(Thread类定义为10)之间的任何值,NORM_PRIORITY被定义为5 当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程,但是,线程优先级是高度依赖于系统的。


(2).有几个高优先级的线程没有进入非活动状态,低优先级的线程可能永远也不能执行。每当调度器决定运行一个新线程时,首先会在具有高优先级的线程中选择,尽管这样会使低优先级的线程完全饿死。


7.守护线程


注:守护线程应该永远不去访问固有资源,因为它会在任何时候深圳在一个操作的中间发送中断


调用this.setDaemon(true)(这方法必须在线程启动之前调用)将线程转换为守护线程,守护线程唯一用途就是为其他线程提供服务(例如计时线程,它定时发送信号给其他线程或清空过时的高速缓存项的线程),当只剩下守护线程时,虚拟机就退出了(当只剩下守护线程,就没必要继续运行程序)


8.同步


在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一个数据的存取


执行 account[to] += amount指令,因为不是原子性,该指令可能被处理如下:


(1).将account[to]加载到寄存器


(2).增加account


(3).将结果写出account[to]


线程1执行步骤1和步骤2,然后被操作系统剥夺运行权,假设线程2被唤醒并修改account[to]的值,然后线程1被唤醒并完成步骤3


package io.laokou.test.concurrent;
import java.util.Arrays;
/**
* @author Kou Shenhai
* @version 1.0
* @date 2022/4/19 0019 上午 8:30
*/
public class UnSyncBankTest {
  private static final int NACCOUNTS = 100;
  private static final double INITIAL_BALANCE = 1000;
  private static final double MAX_AMOUNT = 1000;
  private static final int DELAY = 10;
  public static void main(String[] args) {
    Bank bank = new Bank(NACCOUNTS,INITIAL_BALANCE);
    for (int i = 0; i < NACCOUNTS; i++) {
      int fromAccount = i;
      Runnable runnable = () -> {
        try {
          while (true) {
            int toAccount = (int)(bank.size() * Math.random());
            double amount = MAX_AMOUNT * Math.random();
            bank.transfer(fromAccount,toAccount, amount);
            Thread.sleep((int)(DELAY * Math.random()));
          }
        } catch (Exception e) {}
      };
      new Thread(runnable).start();
    }
  }
}
class Bank {
  private final double[] accounts;
  public Bank(int n,double initialBalance) {
    this.accounts = new double[n];
    Arrays.fill(accounts, initialBalance);
  }
  public void transfer(int from,int to,double amount) {
    if (accounts[from] < amount) {
      return;
    }
    System.out.print(Thread.currentThread());
    accounts[from] -= amount;
    System.out.printf("%10.2f from %d to %d",amount,from,to);
    accounts[to] += amount;
    System.out.printf(" Total Balance: %10.2f%n",getTotalBalance());
  }
  public double getTotalBalance() {
    double sum = 0;
    for (int i = 0; i < accounts.length; i++) {
      sum += accounts[i];
    }
    return sum;
  }
  public int size() {
    return accounts.length;
  }
}
/**
* Thread[Thread-52,5,main]    407.97 from 52 to 23 Total Balance:   99964.67
* Thread[Thread-38,5,main]     80.38 from 38 to 43 Total Balance:   99964.67
* Thread[Thread-67,5,main]    326.22 from 67 to 23 Total Balance:   99964.67
* Thread[Thread-59,5,main]    209.93 from 59 to 40 Total Balance:   99964.67
* Thread[Thread-60,5,main]     27.81 from 60 to 78 Total Balance:   99964.67
* Thread[Thread-74,5,main]    186.07 from 74 to 56 Total Balance:   99964.67
* Thread[Thread-82,5,main]    428.53 from 82 to 70 Total Balance:   99964.67
* Thread[Thread-34,5,main]    414.20 from 34 to 42 Total Balance:   99964.67
* Thread[Thread-23,5,main]    122.91 from 23 to 85 Total Balance:   99964.67
* Thread[Thread-23,5,main]    116.56 from 23 to 83 Total Balance:   99964.67
* Thread[Thread-69,5,main]    346.14 from 69 to 55 Total Balance:   99964.67
* Thread[Thread-90,5,main]    504.83 from 90 to 10 Total Balance:   99964.67
* Thread[Thread-85,5,main]    157.97 from 85 to 41 Total Balance:  100000.00
 */


333.png

目录
相关文章
|
存储 Linux 调度
Linux系统编程 多线程基础
Linux系统编程 多线程基础
69 1
|
Java API 调度
并发编程系列教程(01) - 多线程基础
并发编程系列教程(01) - 多线程基础
77 0
|
7月前
|
存储 安全 Java
10分钟巩固多线程基础
10分钟巩固多线程基础
|
Java 程序员 调度
多线程(初阶)——多线程基础
多线程(初阶)——多线程基础
94 0
|
Java API 调度
并发编程之多线程基础
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
102 0
并发编程之多线程基础
|
缓存 安全 Java
6. 多线程基础
对一个程序的运行状态, 以及在运行中所占用的资源(内存, CPU)的描述; 一个进程可以理解为一个程序; 但是反之, 一个程序就是一个进程, 这句话是错的。
100 0
6. 多线程基础
|
安全 Java 编译器
多线程基础(上)
多线程基础(上)
75 0
多线程基础(上)
|
Java 编译器 程序员
多线程基础(下)
多线程基础(下)
111 0
多线程基础(下)
|
安全 Java 调度
第8章 多线程基础
建立多线程基础,了解基本知识。
112 0
|
设计模式 缓存 安全
Java并发多线程基础总结
Java并发多线程基础总结
147 0
Java并发多线程基础总结