• 16_多线程


    多线程
    什么是进程?
    应用程序的一次运行产生进程。
     
    为什么存在进程的概念?
     
    什么是线程
    参考:https://www.cnblogs.com/geeta/p/9474051.html
     
    线程和进程区别
     
    案例:理解上课的进程
     
    实现多线程
    继承Thread类
     
    package cn.sxt01.thread01;
    public class MyThread extends Thread {
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println("MyThread:" + i);
    }
    }
    }
    package cn.sxt01.thread01;
    public class Test01 {
    public static void main(String[] args) {
     
    // 【1】创建一个线程并执行
    MyThread myThread = new MyThread();
    myThread.start();
     
    // main线程也称主线程
    for (int i = 0; i < 10; i++) {
    System.out.println("MainThread:" + i);
    }
     
    }
    }
     
    Test01中存在两个线程,一个是main线程,也称主线程。另外一个是myThread线程。
    两个线程抢占CPU,所以程序运行轨迹不确定。
     
    实现Runnable接口
    Runnable接口表示实现类是否具有在多线程中执行的能力。
    package cn.sxt01.thread01;
    public class MyRunnable implements Runnable{
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println("MyRunnable:" + i);
    }
    }
    }
     
    案例:卖票
     
    package cn.sxt02.thread02;
    public class TicketThread extends Thread {
    private static int count = 5;
    public TicketThread(String name) {
    super(name);
    }
    @Override
    public void run() {
    // 模拟买票
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(super.getName() + "卖出一张,还剩" + count + "张");
    }
    }
    }
    }
    窗口A卖出一张,还剩4张
    窗口B卖出一张,还剩2张
    窗口C卖出一张,还剩3张
    窗口B卖出一张,还剩0张
    窗口A卖出一张,还剩1张
     
    分析运行轨迹
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(super.getName() + "卖出一张,还剩" + count + "张");
    }
    }
    A抢占到CPU,count(5),条件成立,count--,输出
    窗口A卖出一张,还剩4张
    C抢占到CPU,
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(super.getName() + "卖出一张,还剩" + count + "张");
    }
    }
    C开始执行,执行到super.getName() + "卖出一张,还剩" + count + "张" 准备好,线程C挂起。
    B抢占到CPU
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(super.getName() + "卖出一张,还剩" + count + "张");
    }
    }
    B开始执行,指定到输出位置,输出
    窗口B卖出一张,还剩2张
    CPU时间片到,
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(super.getName() + "卖出一张,还剩" + count + "张");
    }
    }
    C抢占到CPU,直接冲上次挂起位置开始执行,输出
    窗口C卖出一张,还剩3张
     
    结论:
    [1]线程在执行过程中,在任意位置时,都有可能被挂起。下次抢占到cpu后,从挂起位置开始执行。
    [2]当有多个线程访问共享数据时,会出现数据错乱。
    [3]多线程提高了cpu利用率,但同时程序的复杂度增加。
    public class MyRun implements Runnable {
     
    private int count = 5;
    @Override
    public void run() {
    // 模拟买票
    for (int i = 0; i < 5; i++) {
    if (count > 0) {
    count--;
    System.out.println(Thread.currentThread().getName()+"卖出一张,还剩" + count + "张");
    }
    }
    }
    }
    package cn.sxt02.thread02;
    public class Test02 {
    public static void main(String[] args) {
     
    MyRun run = new MyRun();
     
    Thread t1 = new Thread(run,"窗口A");
    Thread t2 = new Thread(run,"窗口B");
    Thread t3 = new Thread(run,"窗口C");
     
    t1.start();
    t2.start();
    t3.start();
    }
    }
     
    比较Thread和Runnable的区别
    [1] 继承Thread的线程类不能再继承其他父类;而实现Runnable接口还可以继承其他类。
    [2] 实现Runnable接口的线程实现类,更便于多个线程共享资源。
     
     
     
     
    线程的生命周期
    生命周期
    新生状态
    用new关键字建立一个线程后,该线程对象就处于新生状态。
    处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态。
    就绪状态
    处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU。
    当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称为“CPU调度”。
    运行状态
    在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。
    如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
    阻塞状态
    处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己运行,进入阻塞状态。
    在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。
    死亡状态
    死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过stop方法来终止一个线程【不推荐使用】;三是线程抛出未捕获的异常。
     
    线程常见方法
    [1]优先级
    package cn.sxt03.thread03;
    public class Test02Priority {
    public static void main(String[] args) {
     
     
    System.out.println(Thread.currentThread().getPriority());
     
    // 线程优先级的最大最小默认值
    System.out.println(Thread.MAX_PRIORITY);
    System.out.println(Thread.MIN_PRIORITY);
    System.out.println(Thread.NORM_PRIORITY);
     
    Thread02 t2 = new Thread02("线程B");
    t2.setPriority(Thread.NORM_PRIORITY);
     
    Thread02 t1 = new Thread02("线程A");
    t1.setPriority(Thread.MAX_PRIORITY);
     
    t1.start();
    t2.start();
    }
    }
    线程优先级越大,表示被CPU调度的可能性增大,并不一定先执行。
     
    [2]isAlive 检测线程是否处于活动状态。
    package cn.sxt03.thread03;
    public class Test03isAlive {
    public static void main(String[] args) {
     
    Thread t1 = new Thread("线程A");
    System.out.println(t1.isAlive());
    t1.start();
    System.out.println(t1.isAlive());
     
    }
    }
     
    [3]join 线程的强行执行
    调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行
    public class Test04Join {
    public static void main(String[] args) {
     
    Thread04 t1 = new Thread04("线程A");
    t1.start();
     
    for (int i = 0; i < 5; i++) {
    if(i == 3) {
    try {
    t1.join();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    System.out.println("mainThread" + " i=" + i);
    }
    }
    }
     
    [4]sleep 线程休眠
    当前线程休眠,线程进入阻塞状态。到休眠时间到,进入就绪状态。在休眠过程中线程可以被中断。
     
     
    [5]yield 线程礼让
    public class Test06Yield {
    public static void main(String[] args) {
     
     
    Thread06 t1 = new Thread06("线程A");
    t1.start();
     
     
    for (int i = 0; i < 5; i++) {
    if(i == 3) {
    Thread.yield();
    }
    System.out.println("mainThread" + " i=" + i);
    }
    }
    }
    可能的结果
    mainThread i=0
    mainThread i=1
    mainThread i=2 => 主线程礼让一次,下次有可能调度主线程,也有可能调度t1
    mainThread i=3
    mainThread i=4
    线程A i=0
    线程A i=1
    线程A i=2
    线程A i=3
    线程A i=4
     
    线程礼让后,线程进入就绪状态。
    线程礼让让当前线程进入就绪状态(礼让一次),但调度者有可能再次调度礼让的线程。
     
    [6]stop 线程终止
    线程终止存在安全隐患,容易导致被锁的资源无法释放,不建议使用。通常使用interrupt代替中断线程,中断线程并通过异常捕获,线程继续执行并正常结束。
     
    线程安全
    多线程中允许多个线程访问同一个资源(共享资源),多个线程可以对共享资源进行破坏性操作容易导致数据错乱的问题。
     
    原子性操作:逻辑上认为多句代码之间应该属于整体的,要么都执行,要么都不执行。不允许存在执行一半的情况。把这样的操作称为原子性操作。
    同步代码块
    如果是少量的代码,可以把原子性操作放入同步代码块中,使用关键字synchronized语法
    synchronized ( 同步监视器/互斥锁 ){
    原子性操作
    }
    同步监视器/互斥锁一定是对象类型。
     
    package cn.sxt04.thread04;
    public class MyRun implements Runnable {
     
    private int count = 5;
    @Override
    public void run() {
    // 模拟买票
    for (int i = 0; i < 5; i++) {
    // mutex 互斥锁
    // 通常将当前对象作为同步对象/互斥锁
    synchronized (this) {
    if (count > 0) {
    count--;
     
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
     
    System.out.println(Thread.currentThread().getName() + "卖出一张,还剩" + count + "张");
    }
    }
    }
    }
    }
     
    同步代码块本质上是给共享资源加锁。锁会导致其他访问共享资源的线程阻塞。
    同步方法
    如果代码量多,可以考虑使用同步方法。
    package cn.sxt04.thread04;
    public class MyRun implements Runnable {
    private int count = 5;
    @Override
    public void run() {
    // 模拟排队买票
    for (int i = 0; i < 5; i++) {
    this.buyTicket();
    }
    }
    // 同步方法默认把当前对象this加锁
    public synchronized void buyTicket() {
    if (count > 0) {
    count--;
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "卖出一张,还剩" + count + "张");
    }
    }
    }
     
    同步监视器
    • synchronized(obj){}中的obj称为同步监视器
    • 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象本身
     
    同步监视器的执行过程
    • 第一个线程访问,锁定同步监视器,执行其中代码
    • 第二个线程访问,发现同步监视器被锁定,无法访问
    • 第一个线程访问完毕,解锁同步监视器
    • 第二个线程访问,发现同步监视器未锁,锁定并访问
     
    死锁(C)
    当线程A拥有一个资源r1,再去申请另外一个资源r2时;此时线程B拥有r2资源,再去申请r1,此时两个线程陷入互相等待,陷入阻塞,此时就绪队列为空,cpu空转。
     
    package cn.sxt05.thread07;
    public class ThreadA extends Thread{
    private Object r1;
    private Object r2;
     
    public ThreadA(Object r1, Object r2) {
    super();
    this.r1 = r1;
    this.r2 = r2;
    }
    @Override
    public void run() {
    synchronized (r1) {
    System.out.println("已拥有r1");
     
    synchronized (r2) {
    System.out.println("想申请拥有r2");
    }
    }
    }
    }
     
     
     
  • 相关阅读:
    IntelliJ IDEA 2018.3 升级功能介绍
    Spring 自动装配及其注解
    在IDEA中实战Git-branch
    IntelliJ IDEA 新版发布:支持CPU火焰图,新增酷炫主题
    java中URL和File的相互转化
    写一个函数,求一个字符串的长度,在main函数中输入字符串,并输出其长度
    输入一行字符,分别统计出其中英文 字母、空格、数字和其它字符的个数
    输入两个正整数m和n,求其最大公约数和最小公倍数。
    一个数如果恰好等于它的因子之和,这个数就称为 "完数 "
    判断101-200之间有多少个素数,并输出所有素数。
  • 原文地址:https://www.cnblogs.com/aknife/p/10988093.html
Copyright © 2020-2023  润新知