day23-线程篇(一)

目录

一.多线程基础

概述:

1.进程与线程

1.1什么是程序?

[1.2 什么是进程?](#1.2 什么是进程?)

1.3什么是线程?

[1.4 进程与线程的区别](#1.4 进程与线程的区别)

[2. 线程基本概念](#2. 线程基本概念)

[3. 线程的创建与启动](#3. 线程的创建与启动)

[4. 线程的创建方式](#4. 线程的创建方式)

[5. 线程的命名](#5. 线程的命名)

[6. 线程的休眠(暂停)](#6. 线程的休眠(暂停))

7.线程的优先级

8.小结

[二. 线程的状态及常用方法](#二. 线程的状态及常用方法)

1.线程的状态

[2. 线程的插队:join( )方法](#2. 线程的插队:join( )方法)

[3. join( )方法和sleep( )方法的区别](#3. join( )方法和sleep( )方法的区别)

[4. 线程的中断:interrupt( )方法](#4. 线程的中断:interrupt( )方法)

[5. 线程的让出:yield( )方法](#5. 线程的让出:yield( )方法)

[5.1 yield( )方法的作用](#5.1 yield( )方法的作用)

[6. 守护线程(Daemon Thread)](#6. 守护线程(Daemon Thread))

[6.1 用户线程与守护线程的区别](#6.1 用户线程与守护线程的区别)

[6.2 设置守护线程](#6.2 设置守护线程)

三.Synchronized同步锁

1.什么是Synchronized同步锁?

[2.synchronized 关键字的用法](#2.synchronized 关键字的用法)

3.synchronized修饰实例方法

4.synchronized修饰静态方法

5.synchronized修饰代码块

[6.synchronized 关键字的补充](#6.synchronized 关键字的补充)


一.多线程基础

概述:

现代操作系统(WindowsmacOSLinux)都可以执行多任务。多任务就是同时运行多个任务。例如:播放音乐的同时,浏览器可以进行文件下载,同时可以进行QQ消息的收发。

1.进程与线程

1.1什么是程序?

程序是含有指令和数据的文件,被存储在磁盘或其他数据存储设备中,可以理解为程序是包含静态代码的文件,例如:浏览器软件,音乐器播放器。

1.2 什么是进程?

进程是程序的一次执行过程,是系统运行程序的基本单位。在windows系统中,每一个正在执行的exe文件或后台服务,都是一个进程,由操作系统统一管理并分配资源,因此进程是动态的。

例如:正在运行的浏览器就是一个进程。

1.3什么是线程?

某些进程内部还需要同时执行多个子任务。例如:我们在使用WPS时,WPS可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行自动保存和上传云文档,我们把子任务称为线程

进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个主线程。

复制代码
                        ┌──────────┐
                        │Process   │
                        │┌────────┐│
            ┌──────────┐││ Thread ││┌──────────┐
            │Process   ││└────────┘││Process   │
            │┌────────┐││┌────────┐││┌────────┐│
┌──────────┐││ Thread ││││ Thread ││││ Thread ││
│Process   ││└────────┘││└────────┘││└────────┘│
│┌────────┐││┌────────┐││┌────────┐││┌────────┐│
││ Thread ││││ Thread ││││ Thread ││││ Thread ││
│└────────┘││└────────┘││└────────┘││└────────┘│
└──────────┘└──────────┘└──────────┘└──────────┘
┌──────────────────────────────────────────────┐
│               Operating System               │
└──────────────────────────────────────────────┘

线程是一个比进程更小的执行单位(CPU的最小执行单位)。一个进程在其执行的过程中可以产生多个线程。与进程不同的是,同类的多个线程共享同一块内存空间和一组系统资源, 所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多。

1.4 进程与线程的区别
  • 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
  • 资源开销:每个进程都有独立的代码副本和数据空间,进程之间的切换,资源开销较大;线程可以看作是轻量级的进程,每个线程都有自己独立的运行栈和程序计数器,线程之间的切换,资源开销小。
  • 包含关系:一个进程内包含有多个线程,在执行过程中,线程的执行不是线性串行的,而是多条线程并行共同完成;
  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响;一个线程崩溃,会导致整个进程退出。所以多进程要比多线程健壮;
  • 执行过程:每个独立的进程有程序运行的入口和程序出口。但是线程不能独立执行,必须依存在应用程序(进程)中,由应用程序提供多个线程执行控制;
复制代码
// 程序:程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,可以理解为程序是包含静态代码的文件
// 多进程:进程是程序的一次执行过程,是系统运行程序的基本单位。
// 多线程:线程是操作系统运行时,能够调度的最小单位,它被包含在进程中,时进程在运行过程中的一个单位
// 多线程的应用场景:
// 软件中的耗时操作,拷贝和迁移文件,加载大量的资源的时候
// 所有的后台服务器
// 所有的聊天软件

2. 线程基本概念

单线程:单线程就是进程中只有一个线程。

复制代码
public class SingleThread {
  	public static void main(String[] args) {
          for (int i = 0; i < 10000; i++) {
              System.out.print(i + " ");
          }
  	}
}

多线程:由一个以上的线程组成的程序称为多线程程序。Java中,一定是从主线程开始执行(main方法),然后在主线程的某个位置创建并启动新的线程。

复制代码
public class MultiThread {
	public static void main(String[] args) {
        // 创建2个线程
		Thread t1 = new Thread(new Runnable() {

			@Override
			public void run() {
				for (int i = 0; i < 10000; i++) {
					System.out.println("线程1:" + i + " ");
				}
			}
		});
		Thread t2 = new Thread(new Runnable() {

			@Override
			public void run() {
				for (int i = 0; i < 10000; i++) {
					System.out.println("线程2:" + i + " ");
				}
			}
		});

        // 启动2个线程
		t1.start();
		t2.start();
	}
}

3. 线程的创建与启动

  • 通过创建Thread实例,完成线程的创建。

    • 线程的内部实现可以通过继承Thread类、实现Runnable接口等方式进行封装。
  • 通过调用Thread实例的start()方法启动新线程。

  • 查看Thread类的源代码,会看到start()方法内部调用了一个private native void start0()方法,native修饰符表示这个方法是由JVM虚拟机内部的C代码实现的本地方法,由JVM根据当前操作系统进行本地实现。

    package thread;

    public class Demo01 {
    public static void main(String[] args) {

    复制代码
          for (int i = 0; i < 100; i++){
              System.out.println("main线程:" + i);
          }
          ThreadOne t1 = new ThreadOne();
          t1.start();

    // for (int i = 0; i < 100; i++){
    // System.out.println("main线程:" + i);
    // }

    复制代码
      }

    }

注意:直接调用Thread实例的run()方法是无效的,因为直接调用run()方法,相当于调用了一个普通的Java方法,当前线程并没有任何改变,也不会启动新线程。

4. 线程的创建方式

方式1:

通过继承Thread,重写Thread类中run()方法。main主线程中new一个Thread的子类,然后调用start()方法。

复制代码
package thread;

public class ThreadOne extends  Thread{
    public ThreadOne(String name){
        super(name);
    }
    public ThreadOne(){
        super();
    }
    public void run(){
        for(int i = 0; i < 100; i++){
            System.out.println(getName() + ":" + i);
        }
    }
}


main
        ThreadOne t1 = new ThreadOne();
        t1.start();

方式2:java.lang.Runnable 接口实现多线程,创建一个实现Runnable接口的实现类,重写run方法

创建Runable的实现类的对象r1,创建Thread类对象,构造方法中传递Runnable接口的实现类对象

调用start方法的启动线程。

复制代码
 public static void main(String[] args) {
        ThreadTwo t2 = new ThreadTwo();
        Thread t = new Thread(t2);
        t.start();
        for (int i=0; i < 100; i++){
            System.out.println("main线程:" + i);
        }
    }
}
class ThreadTwo implements Runnable{
    public void run(){
        for(int i = 0; i < 100; i++){
            System.out.println("线程1:" + i);
        }
    }
}

方式3:实现 java.util.concurrent.Callable 接口,允许子线程返回结果、抛出异常。

复制代码
// 实现子线程
public class SubThread implements Callable<Integer>{
    private int begin,end;
    public SubThread(int begin,int end){
        this.begin = begin;
        this.end = end;
    }
    @Override
    public Integer call() throws Exception {
        int result = 0;
        for(int i=begin;i<=end;i++){
            result+=i;
        }
        return result;
      }
}

5. 线程的命名

  • 调用父类的setName()方法或在构造方法中给线程名字赋值。

  • 如果没有为线程命名,系统会默认指定线程名,命名规则是Thread-N的形式。

    package thread.method;

    import thread.ThreadOne;

    public class Demo01 {
    // 给线程设置名字
    // setName()方式设置线程名
    // 调用有参构造设置线程名
    public static void main(String[] args) {
    ThreadOne t1 = new ThreadOne();
    t1.setName("线程1");
    t1.start();
    // 使用构造方法设置线程名
    ThreadOne t2 = new ThreadOne("线程2");
    t2.start();
    // Runable作为参数
    Runnable r = new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+"线程正在运行");
    }
    };
    Thread t3 = new Thread(r,"西红柿");
    t3.start();
    }
    }

6. 线程的休眠(暂停)

可以调用Thread.sleep(long millis),强迫当前相乘按照毫秒值休眠。

复制代码
package thread.method;

import java.sql.SQLOutput;

public class Demo08 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("main线程进入");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void  run() {
                // 获取当前的系统时间
                long start = System.currentTimeMillis();
                System.out.println("进入t1线程");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    System.out.println("中断t1线程,消耗时间为:"+(System.currentTimeMillis()-start)+"毫秒");
                    e.printStackTrace();
                    return;
                }
                System.out.println("结束t1线程,消耗时间为:"+(System.currentTimeMillis()-start)+"毫秒");
            }
        },"t1线程");
        t1.start();
        // 让主线程休眠
        Thread.sleep(1000*3);
        System.out.println("main线程结束");
        // main主线程修改t1线程中断状态=true
        // t1线程检测中断状态=true,则抛出InterruptedException异常, 子线程执行结束
//        t1.interrupt();
    }
}

7.线程的优先级

  • 在线程中通过setPriorty(int n)设置线程优先级,范围为1-10,默认为5.
  • 优先级高的线程被操作系统调度的优先级较高。

提示:并不能代表,通过设置优先级来确保高优先级的线程一定会先执行。

8.小结

  • Java用Thread对象表示一个线程,通过调用start()启动一个新线程;
  • 一个线程对象只能调用一次start()方法;
  • 线程的执行代码写在run()方法中;
  • 线程调度由操作系统决定,程序本身无法决定调度顺序;

二. 线程的状态及常用方法

1.线程的状态

Java程序中,一个线程对象通过调用start()方法启动线程,并且在线程获取CPU时,自动执行run()方法。run()方法执行完毕,代表线程的生命周期结束。

  • 线程终止的原因有:
    • 线程正常终止:run()方法执行到return语句返回;
    • 线程意外终止:**run()**方法因为未捕获的异常导致线程终止;
    • 对某个线程的Thread实例调用**stop()**方法强制终止(宇宙超级无敌强烈不推荐);

2. 线程的插队:join( )方法

复制代码
public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程Main:开始执行,即将创建并调用子线程");

        // 创建并启动子线程
        MyThread myThread = new MyThread();
        myThread.start();

        // 主线程调用myThread子线程的join()方法
        myThread.join(); // 子线程插队,插入到当前线程main的执行序列前
        
        System.out.println("主线程Main:当子线程myThread执行完毕后,主线程Main再执行");
    }
}
    • 综上所述:join()方法实际上是通过调用wait()方法, 来实现同步的效果的。
      • 例如:A线程 中调用了B线程join()方法,则相当于A线程 调用了B线程wait()方法,在调用了B线程wait()方法后,A线程 就会进入WAITING或者TIMED_WAITING等待状态,因为它相当于放弃了CPU的使用权。
    • 注意:join(0)的意思不是A线程 等待B线程 0秒,而是A线程 等待B线程 无限时间,直到B线程 执行完毕:即join(0)=join()

      public class Thread implements Runnable
      public final void join() throws InterruptedException {
      join(0);
      }

      复制代码
      public final synchronized void join(long millis) throws InterruptedException {
          long base = System.currentTimeMillis();
          long now = 0;
      
          if (millis < 0) {
              throw new IllegalArgumentException("timeout value is negative");
          }
      
          if (millis == 0) {
              while (isAlive()) {
                  // 无限等待
                  wait(0);
              }
          } else {
              while (isAlive()) {
                  long delay = millis - now;
                  if (delay <= 0) {
                      break;
                  }
                  // 计时等待
                  wait(delay);
                  now = System.currentTimeMillis() - base;
              }
          }
      }

      }

3. join( )方法和sleep( )方法的区别

    • 两个方法都可以实现类似"线程等待"的效果,但是仍然有区别;
    • join()是通过在内部使用synchronized + wait()方法来实现的,所以join()方法调用结束后,会释放锁;
    • sleep()休眠没有结束前,不会释放锁;

4. 线程的中断:interrupt( )方法

如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。

注意事项:

  • 线程被Object.wait(), Thread.join()Thread.sleep()三种方法阻塞或等待,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常,从而提前终结被阻塞状态。
  • 如果线程没有被阻塞或等待,调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()等方法进入阻塞或等待时,才会抛出 InterruptedException异常;

5. 线程的让出:yield( )方法

5.1 yield( )方法的作用
    • 线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用。
    • 至于系统是否采纳,取决于JVM的线程调度模型:分时调度模型抢占式调度模型
      • 分时调度模型 :所有的线程轮流获得 cpu的使用权,并且平均分配每个线程占用的 CPU 时间片;
      • 抢占式调度模型 :优先让可运行池中优先级高的线程占用 CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。(JVM虚拟机采用的是抢占式调度模型 )

6. 守护线程(Daemon Thread)

6.1 用户线程与守护线程的区别
    • 用户线程:我们平常创建的普通线程;
    • 守护线程 :用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。
6.2 设置守护线程
  • 在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程

    Thread myThread = new Thread();
    myThread.setDaemon(true);
    myThread.start();

    public class Main {

    复制代码
      public static void main(String[] args) {
          long startTime = System.currentTimeMillis();
    
          // 创建并启动子线程
          new Thread() {
              @Override
              public void run() {
                  //子线程休眠10秒钟
                  try {
                      Thread.sleep(10*1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println("普通用户线程,运行耗时" + (System.currentTimeMillis() - startTime));
              }
          }.start();
    
          //主线程休眠3秒,确保在子线程之前结束休眠
          try {
              Thread.sleep(3*1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
    
          System.out.println("Main主线程,运行耗时 " + (System.currentTimeMillis() - startTime));
      }

    }

运行结果分析:普通用户线程,在没有完成打印内容的时候,JVM是不会被结束。

三.Synchronized同步锁

1.什么是Synchronized同步锁?

Synchronized同步锁,简单来说,使用Synchronized关键字将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全.

2.synchronized 关键字的用法

  1. 修饰实例方法:synchronized修饰实例方法, 则用到的锁,默认为this当前方法调用对象;
  2. 修饰静态方法:synchronized修饰静态方法, 则其所用的锁,默认为Class对象;
  3. 修饰代码块:synchronized修饰代码块, 则其所用的锁,是某个指定Java对象;

3.synchronized修饰实例方法

  • 使用当前对象this 充当锁**,** 完成对当前方法的锁定,只有获取this锁的线程才能访问当前方法;
  • 并发过程中,同一时刻,可以有N个线程请求执行方法,但只有一个线程可以持有this锁,才能执行;
  • 不同线程,持有的对象,必须相同;

当使用synchronized 修饰实例方法时, 以下两种写法作用和意义相同:

复制代码
public class Foo {
    // 实例方法
    public synchronized void doSth1() {
        // 获取this锁,才能执行该方法
    }
    
    // 实例方法
    public void doSth2() {
        synchronized(this) {
           // 获取this锁,才能执行该代码块
        }
    }
}


public static void main(String[] args) {
    // 实例化一个对象
    Foo fa = new Foo();
	
    // 创建不同的线程1
    Thread thread01 = new Thread() {
        public void run() {
            // 使用相同的对象访问synchronized方法
            fa.doSth1();
        }
    };
	
    // 创建不同的线程2
    Thread thread02 = new Thread() {
        public void run() {
            // 使用相同的对象访问synchronized方法
            fa.doSth1();
        }
    };
    
    // 启动线程
    thread01.start();
    thread02.start();
}

4.synchronized修饰静态方法

  • 使用当前对象的Class对象充当锁,完成对当前方法的锁定,只有获取Class锁的线程才能访问当前方法;

  • 不同线程,持有的对象,可以不同,但必须相同class类型;

    public class Foo {
    // 静态方法
    public synchronized static void doSth1() {
    // 获取当前对象的Class对象锁,才能执行该方法
    }

    复制代码
      // 实例方法
      public static void doSth2() {
          synchronized(this.getClass()) {
          	// 获取当前对象的Class对象锁,才能执行该代码块
          }
      }

    }

    public static void main(String[] args) {
    // 创建不同的对象(相同类型)
    Foo fa = new Foo();
    Foo fb = new Foo();

    复制代码
      // 创建不同线程1
      Thread thread01 = new Thread() {
          public void run() {
              // 使用不同的对象访问synchronized方法
              fa.doSth2();
          }
      };
    
      // 创建不同线程2
      Thread thread02 = new Thread() {
          public void run() {
              // 使用不同的对象访问synchronized方法
              fb.doSth2();
          }
      };
    
      // 启动线程
      thread01.start();
      thread02.start();

    }

5.synchronized修饰代码块

复制代码
synchronized(自定义对象) {
   //临界区
}

6.synchronized 关键字的补充

  • 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

, 所有的线程都可以自由地访问对象中的代码, 而synchronized关键字只是限制了线程对于已经加锁的同步代码块的访问,并不会对其他代码做限制。所以,同步代码块应该越短小越好。

  • 父类中synchronized修饰的方法,如果子类没有重写,则该方法仍然是线程安全性;如果子类重写,并且没有使用synchronized修饰,则该方法不是线程安全的;
  • 在定义接口方法时,不能使用synchronized关键字;
  • 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步;
  • 离开synchronized代码块后,该线程所持有的锁,会自动释放;
相关推荐
LZQqqqqo6 分钟前
C# 事件Event
开发语言·c#
ZeroNews内网穿透9 分钟前
ZeroNews内网穿透安全策略深度解析:构建企业级安全连接体系
java·运维·服务器·网络·python·安全·php
麦兜*12 分钟前
Spring Integration 整合 Web3.0网关:智能合约事件监听与Spring Integration方案
java·spring boot·后端·spring·spring cloud·web3·智能合约
回家路上绕了弯14 分钟前
分布式缓存实战指南:从架构到落地的完整方案
java·后端
天天摸鱼的java工程师17 分钟前
Seata分布式事务实现原理剖析
java·后端·面试
泉城老铁18 分钟前
Spring Boot + Vue + ZLMediaKit 实现 RTSP 拉流播放的完整方案
java·vue.js·音视频开发
样子201842 分钟前
PHP 之使用HTMLPurifier过滤XSS
开发语言·前端·php·xss
蜀山雪松43 分钟前
Windows中Idea或者其他开发工具如何使用Google Sans Code - 码农开源等宽字体
java·ide·intellij-idea
写bug写bug44 分钟前
如何应用服务器端的防御式编程
java·后端·代码规范
_码农121381 小时前
Spring IoC容器与Bean管理
java·后端·spring