JAVA基础-多线程入门(详解)

目录

引言

一,线程概念

二,创建线程

2.1,继承Thread类,重写run方法

[2.2,实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函 数的target](#2.2,实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函 数的target)

2.3,通过Callable和FutureTask创建线程 ( 线程有返回值)

三,线程状态

四,volatile和synchronized

[4.1、volatilevolatile 解决的是内存可见性问题](#4.1、volatilevolatile 解决的是内存可见性问题)

[4.1.1, volatile 原理](#4.1.1, volatile 原理)

[4.1.2, volatile 修饰的变量可见性](#4.1.2, volatile 修饰的变量可见性)

[4.1.3, volatile 禁止指令重排](#4.1.3, volatile 禁止指令重排)

[4.1.4volatile 使用范围](#4.1.4volatile 使用范围)

[4.1.5 volatile 使用场景](#4.1.5 volatile 使用场景)

4.2、synchronized

[4.2.1, synchronized 原理](#4.2.1, synchronized 原理)

[4.2.2, synchronized 修饰的代码块或方法保证内存可见性](#4.2.2, synchronized 修饰的代码块或方法保证内存可见性)

[4.2.3, synchronized 修饰的代码块或方法保证原子性](#4.2.3, synchronized 修饰的代码块或方法保证原子性)

[4.2.4,synchronized 使用范围](#4.2.4,synchronized 使用范围)

[4.2.5, synchronized 使用场景](#4.2.5, synchronized 使用场景)

[4.3、volatile 和 synchronized 异同点](#4.3、volatile 和 synchronized 异同点)

[4.3.1, 相同点](#4.3.1, 相同点)

[4.3.2, 不同点](#4.3.2, 不同点)


引言

什么是程序 ?

一个程序可以有多个进程 。程序是一段静态的代码,它是应用程序执行的蓝本。

什么是进程 ?

一个进程可以有多线程 进程是指一种正在运行的程序,有自己的地址空间。 作为蓝本的程序可以被多次加载到系统的不同内存区域分别执行,形成不同的进程。

基于进程的特点是允许计算机同时运行两个或更多的程序。

什么是线程 ?

线程是进程内部单一的一个顺序控制流。 一个进程在执行过程中,可以产生多个线 程。每个线程也有自己产生、存在和消亡的过程。

一,线程概念

线程(Thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个线程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每个线程并行执行不同的任务。线程在Unix SystemV及SunOS中也被称为轻量进程(Lightweight Processes),但"轻量进程"更多指内核线程(Kernel Thread),而用户线程(User Thread)则被称为"线程"。

线程是独立调度和分派的基本单位,可以分为:

(1)操作系统内核调度的内核线程,如Win32线程;

(2)由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;

(3)由内核与用户进程进行混合调度,如Windows7的线程。

同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间、文件描述符和信号处理等。但同一进程中的多个线程有各自的调用栈(Call Stack)、各自的寄存器环境(Register Context)、各自的线程本地存储(Thread-Local Storage)。

二,创建线程

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函 数的target

3.通过Callable和FutureTask创建线程 ( 线程有返回值)

4.通过线程池创建线程

前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void, 所以没有办法返回结果。

后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是 Object,所以返回的结果可以放在Object对象中。

2.1,继承Thread类,重写run方法

示例代码:

java 复制代码
public class Thread1 extends Thread {
    @Override
    public void run() {
        String t = Thread.currentThread().getName();
        System.out.println("线程名称:" + t);

        while (true) {
            try {
                Thread.sleep(5000);
                //cmd /k shutdown /s /t 0
                Runtime.getRuntime().exec("cmd /k start http://www.baidu.com");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

2.2,实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函 数的target

示例代码:

java 复制代码
public class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

实例化:

java 复制代码
public class T3 {
    public static void main(String[] args) {
        Thread2 tt = new Thread2();

        Thread th1 = new Thread(tt, "A");
        Thread th2 = new Thread(tt, "B");
        Thread th3 = new Thread(tt, "C");

        th1.start();
        th2.start();
        th3.start();
    }
}

2.3,通过Callable和FutureTask创建线程 ( 线程有返回值)

示例代码:

java 复制代码
public class Thread3 implements Callable<Integer> {
    private Integer num;

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public Thread3(Integer num) {
        this.num = num;
    }

    public Thread3() {
    }

    @Override
    public Integer call() throws Exception {
        return this.num * this.num;
    }
}

实例化:

java 复制代码
public class T4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> call = new Thread3(6);
        FutureTask<Integer> ft = new FutureTask<>(call);
        Thread t = new Thread(ft);
        t.start();
        System.out.println(Thread.activeCount());

        Integer n = ft.get();
        System.out.println(n);


    }
}

三,线程状态

  1. 新建(NEW):新创建了一个线程对象。

  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该 状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代 码。

  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice, 暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行 (running)状态。阻塞的情况分三种:

  5. 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列 (waitting queue)中。

  6. 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用, 则JVM会把该线程放入锁池(lock pool)中。

  7. 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了 I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超 时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  8. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命 周期。死亡的线程不可再次复生。

四,volatile和synchronized

4.1、volatile

volatile 解决的是内存可见性问题

4.1.1, volatile 原理

volatile原理是基于CPU内存屏障指令实现的

4.1.2, volatile 修饰的变量可见性

volatile是变量修饰符,其修饰的变量具有内存可见性

一般情况下线程在执行时,Java中为了加快程序的运行效率,会先把主存数据拷贝到线程本地(寄存器或是CPU缓存),操作完成后再把结果从线程本地缓存刷新到主存中,这样就会导致修改后放入变量结果同步到主存中需要一个过程,而此时另外的线程看到的还是修改之前的变量值,这样就会导致不一致

为了解决上述多线程中内存可见的问题,引入了 volatile 关键字,那么它为什么可以解决内存可见性问题呢?

答案: volatile 它会使得所有对 volatile 变量的读写都会直接读写主存,而不是先读写线程本地缓存,这样就保证了变量的内存可见性

4.1.3, volatile 禁止指令重排

volatile可以禁止进行指令重排

指令重排: 处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证各个语句的执行顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。指令重排序不会影响单个线程的执行,但是会影响到线程并发执行时的正确性

线程执行到volatile修饰变量的读写操作时,其他线程对这个变量的操作肯定已经完成了,且结果已经同步到了主存中,即对其他的线程可见,本线程再对该变量操作完全没有问题的

4.1.4volatile 使用范围

volatile关键字仅能实现对原始变量(如boolen、 short 、int 、long等)操作的原子性,不能保证复合操作的原子性,比如 i++

i++,实际上是由三个原子操作组成:read i; inc; write i,假如多个线程同时执行i++,volatile只能保证他们操作的i是同一块内存,但不能保证i结果的正确性,原因如下:

比如有两个线程A和B对volatile修饰的i进行i++操作,i的初始值是0,A线程执行i++时刚读取了i的值0,就切换到B线程了,B线程(从内存中)读取i的值也为0,然后就切换到A线程继续执行i++操作,完成后i就为1了,接着切换到B线程,因为之前已经读取过了,所以继续执行i++操作,最后的结果i就为1了,A和B线程同步到主存中的i的值都是1

4.1.5 volatile 使用场景

  • 对变量的写入操作不依赖变量的当前值,或者只有单个线程更新变量的值
  • 该变量没有包含在具有其他变量的不变式中

4.2、synchronized

  • synchronized 既解决了内存可见性问题,又解决了执行顺序问题
  • synchronized 可以修饰代码块或方法,既可以保证可见性,又能够保证原子性

4.2.1, synchronized 原理

synchronized 是基于 monitor 实现的

4.2.2, synchronized 修饰的代码块或方法保证内存可见性

通过synchronized或者Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存中

4.2.3, synchronized 修饰的代码块或方法保证原子性

线程要么不执行(线程没有获取到对象锁),线程要么执行到底(线程获取到了对象锁),直到执行完释放锁

4.2.4,synchronized 使用范围

synchronized 不仅能修饰代码块,还可以修饰方法

4.2.5, synchronized 使用场景

需要控制多线程访问的方法或者更新的变量

4.3、volatile 和 synchronized 异同点

4.3.1, 相同点

volatile 和 synchronized 都保证了内存可见性

4.3.2, 不同点

  • volatile仅能使用在变量级别,synchronized则可以使用在变量、方法、和类级别的
  • volatile仅能实现变量的修改可见性,不能保证原子性,而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞
  • volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化
  • 由于 4 中的区别,在某些情况下 volatile 的性能优于 synchronized
相关推荐
小小李程序员29 分钟前
LRU缓存
java·spring·缓存
cnsxjean35 分钟前
SpringBoot集成Minio实现上传凭证、分片上传、秒传和断点续传
java·前端·spring boot·分布式·后端·中间件·架构
CRMEB-嘉嘉38 分钟前
如何优化 PHP 性能?
开发语言·php
hadage2331 小时前
--- stream 数据流 java ---
java·开发语言
Want5951 小时前
Python绘制太极八卦
开发语言·python
翀哥~1 小时前
python VS c++
开发语言·c++·python
《源码好优多》1 小时前
基于Java Springboot汽配销售管理系统
java·开发语言·spring boot
小林想被监督学习1 小时前
Java后端如何进行文件上传和下载 —— 本地版
java·开发语言
Erosion20202 小时前
SPI机制
java·java sec
猪猪虾的业余生活2 小时前
matlab实现,数据曲线毛刺光滑
开发语言·matlab