Java多线程理解与实践

Java多线程理解与实践(一)

前言

Java多线程是Java开发的基本功,而在日常面试中也会经常被问到。常常在这个类题目上回答不上,或者回答得卡卡顿顿的,其实就是自己没有理解到位。或者说实践不够。
本文将从最基本的线程创建,同步方法、及线程池开始介绍学习。同时,附上自己写的一些代码,希望从实践中感受到多线程编程的实质。

多线程相关概念介绍

多线程是一种编程模型

多线程本质上是一种编程模型,现代的计算机搭载多核心cpu,为了更好的体现多核心的优势,最大化cpu多核心的优势,提高cpu利用率,便出现了多线程编程。

进程与线程

进程是操作系统资源分配的基本单位。而线程是比进程颗粒度更细,一个程序中可能创建出多个线程同时执行多个任务。而线程是CPU调度的基本单位,共享着进程的资源。

JVM内存模型(JMM)

Java 内存模型(Java Memory Model, JMM)
Java 内存模型

主内存与工作内存
  • 主内存:所有共享变量的存储区域,线程对变量的读写操作最终需同步到主内存。
  • 工作内存:每个线程私有的内存空间,存储线程操作变量的副本。线程不能直接读写主内存中的变量,需通过工作内存交互。

线程对变量的操作流程:

  1. 从主内存读取变量到工作内存。
  2. 在工作内存中修改变量值。
  3. 将修改后的值写回主内存。
可见性、有序性与原子性
  • 可见性:通过volatile、synchronized或final关键字实现。volatile变量修改后会立即同步到主内存,其他线程读取时强制刷新工作内存。
  • 有序性:禁止指令重排序(通过volatile的禁止重排语义或synchronized的锁规则)。
  • 原子性:基本类型(除long/double外)的读写是原子的,复杂操作需通过锁或Atomic类保证。

实践

创建线程

继承Thread 创建线程类
public class ThreadA extends Thread {
    public void run(){
        System.out.printf("[%s] A: Start\n",Thread.currentThread().getName());
        for(int i = 1; i <= 3; i++){
            try {
                Thread.sleep(120);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.printf( "[%s] A: %s\n", Thread.currentThread().getName(), i);
        }
        System.out.printf("[%s] A: END\n",Thread.currentThread().getName());
    }
}
实现Runnable接口 创建线程类
public class ThreadB implements Runnable {
    @Override
    public void run() {
        System.out.printf("[%s] B: Start\n",Thread.currentThread().getName());
        for (int i = 1; i <= 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.printf( "[%s] B: %s\n", Thread.currentThread().getName(), i);
        }
        System.out.printf("[%s] B: End \n",Thread.currentThread().getName());
    }
}

创建线程

以下是main方法 创建线程类。

  • 线程对象被创建后一定是 调用start方法
  • 使用join方法代表等待改线程运行结束后才回到改线程继续执行。
public class Basic {

    public static void main(String[] args) throws InterruptedException {
        System.out.printf("[%s] Main: start \n",Thread.currentThread().getName());

        //ThreadA 是继承自 Thread 类,直接start
        ThreadA threadA = new ThreadA();
        threadA.start();

       // threadA.join();//如果中间加入这个join方法,下面就不会继续,直到A结束

        //ThreadB 只是实现了 Runnable接口,所以它需要再新建一个Thread类
        //这里构造函数需要的是一个实现了Runnable类型的对象(可以理解为一个任务 task)
        Thread threadB = new Thread(new ThreadB());
        threadB.start();

        //threadA.run();//很明显这里的ThreadName 就是Main 主线程。
        //启动 线程是start,而不是直接调用run。run只是主线程调用了这个对象的run方法,仅此而已。
        System.out.printf("[%s] Main: end \n",Thread.currentThread().getName());
        //虽然打印到这里。 但是 主线程还是在等其他线程结束才会结束。

        //线程状态 : new 、runnable 、waiting、block、timed waiting 、terminated、

    }

}

被interrupt(打断)的实验

当我们想在线程运行阶段调用sleep方法时,一个必检异常InterruptedException需要处理。那到底如何使用这个打断。接下来将有一个小实验帮助大家理解。
在这里插入图片描述

两个线程Class

A:打印1到10,打印之间睡觉一秒钟。

public class IThreadA extends Thread {
    @Override
    public void run() {
        System.out.printf("%s ThreadA", Thread.currentThread().getName());

        int n = 0;
        while (true) {
            n++;
            System.out.printf("%s ThreadA ... %s \n", Thread.currentThread().getName(),n);
            try {
                sleep(1000L);
            } catch (InterruptedException e) {
                System.out.printf("%s ThreadA is interrupted\n", Thread.currentThread().getName());
                this.interrupt();
            }
            if (n == 10) {
                break;
            }
            if (interrupted()) {
                System.out.printf("%s ThreadA jumps into interrupted checking.\n", Thread.currentThread().getName());
                break;
            }
        }
    }
}

B:和A类似,从1开始打印,知道running被改变。

public class IThreadB extends Thread {

    public volatile  boolean running = true;

    @Override
    public void run() {
        System.out.printf("%s IThreadB is running.\n", Thread.currentThread().getName());
        int n = 0;
        while (running) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.printf("INTERRUPTED %s, but keep running.\n", Thread.currentThread().getName());
            }
            n++;
            System.out.printf("%s IThreadB ... %s \n", Thread.currentThread().getName(),n);
        }
        if (!running) {
            System.out.printf("%s IThreadB jumps into interrupted checking.\n", Thread.currentThread().getName());
        }
    }
}
执行Main方法的class
  1. 创建并启动A线程 ==》打断 ==》等待A结束
  2. 创建并启动B线程 ==》修改其 running 至false
    public static void main(String[] args) throws InterruptedException {
        IThreadA iThreadA = new IThreadA();
        iThreadA.start();


        iThreadA.interrupt();
        iThreadA.join();

        IThreadB iThreadB = new IThreadB();
        iThreadB.start();
        iThreadB.running = false;
    }
结果

在这里插入图片描述
现象

  1. A打印了1次,然后就被打断了。它跳进了catch代码块,之后通过interrupted()这个方法获取到当前是否被打断状态。
  2. B启动之后,主线程就修改了被volatile修饰的running属性,该属性原本是true。然后,B就没办法进入答应阶段了。正如Java内存模型提到的可见性问题,running 是一个volatile 修饰的变量,被 volatile 修饰完之后,就会让所有线程都可见。它解决的是一个可见性的问题。

总结

本文主要讲了一些Java线程的基础知识,包括简单的线程类创建方式以及InterruptException的出触发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值