Java多线程总结

一. 进程 线程 概念

程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一
段静态的代码,静态对象。

1.1 进程

进程(process) 是程序的一次执行过程,或是正在运行的一个程序。是一个动态
的过程:有它自身的产生、存在和消亡的过程。——生命周期

在这里插入图片描述

进程的声明周期:
在这里插入图片描述

1.2 线程

线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

在这里插入图片描述

1.3 多核多线程

在这里插入图片描述

“同一时间点只有一个任务在执行”。其实“同一时间点只有一个任务在执行” 这句话是不准确的,至少它是不全面的。我们分析一下多核的情况。

看一下CPU , 有6核
在这里插入图片描述
多核CPU ,多个并行计算的核心,每个处理核心对应一个内核线程(Kernel Thread,KLT),一般一核心对应一线程
现在常见的双核四线程就是采用超线程技术将一个物理处理核心模拟成两个逻辑处理核心 博主的CPU就是6核12线程

超线程技术
利用特殊的硬件指令,把一个物理芯片模拟成两个逻辑处理核心
程序一般不会使用到内核线程 而是使用内核线程的高级API (轻量级进程Light Weight Process LWP)
轻量级进程也就是所说的 线程

由于每个轻量级进程都有一个内核线程支持, 因此只有先支持内核线程,才能有轻量级进程。
用户线程&内核线程之间对应关系有 一对一 一对多 多对一

  1. 一对一模型
    在这里插入图片描述

一个用户线程就唯一对应一个内核线程 。一对一模型使用户线程具有与内核线程一样的优点,一个线程因某种原因阻塞时其他线程的执行不受影响;此处,一对一模型也可以让多线程程序在多处理器的系统上有更好的表现。但一对一模型也有两个缺点:1. 许多操作系统限制了内核线程的数量,因此一对一模型会使用户线程的数量受到限制;2. 许多操作系统内核线程调度时,上下文切换的开销较大,导致用户线程的执行效率下降

  1. 多对一模型

在这里插入图片描述

将多个用户进程映射到一个内核线程上,线程之间切换由用户态的代码来执行,相对一对一 多对一线程切换要快很多 , 用户可以无限线程 。 缺点 1. 如果其中一个用户线程阻塞,那么其它所有线程都将无法执行,因为此时内核线程也随之阻塞了;2. 在多处理器系统上,处理器数量的增加对多对一模型的线程性能不会有明显的增加,因为所有的用户线程都映射到一个处理器上了。

  1. 多对多模型

在这里插入图片描述

多个用户线程映射到多个内核线程上

  1. 一个用户线程的阻塞不会导致所有线程的阻塞,因为此时还有别的内核线程被调度来执行;2. 多对多模型对用户线程的数量没有限制;3. 在多处理器的操作系统中,多对多模型的线程也能得到一定的性能提升,但提升的幅度不如一对一模型的高。

1.4 线程&进程区别

线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;

进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间 (包括代码段、数据集、堆等) 及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;调度和切换:线程上下文切换比进程上下文切换要快得多。

在这里插入图片描述

二.线程的创建和使用

2.1 Thread类

JVM允许程序运行多个线程,通过java.lang.Thread类来实现

多线程的创建 方式一: 继承于Thread类
  • 创建一个继承于Thread类的子类
  • 重写Thread类的run() — > 将此线程执行的操作声明在run()中
  • 创建Thread类的子类的对象
  • 通过此对象调用start()
/ TODO 1 创建一个继承于Thread类的子类
class MyThread extends Thread{
    // todo 2 重写Thread类的run()

    @Override
    public void run() {
        for(int i = 0 ; i < 100 ; i ++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}



public class ThreadTest {

    public static void main(String[] args) {
        //todo 3 创建Thread类的子类的对象
        MyThread myThread = new MyThread();

        //todo 4  通过此对象调用start()
        myThread.start();

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i + "*************");
            }
        }
                //TODO 5 第二个子线程
            MyThread myThread2 = new MyThread();
            myThread2.start();
    }
}

在这里插入图片描述
mt子线程的创建和启动过程
在这里插入图片描述
在这里插入图片描述
注意:

  • 若直接调用run() , 那么就是普通方法(还是在main中执行) 没有启动多线程
  • run() 方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度执行
  • 想要启动多线程 , 必须调用start方法
  • 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上
    的异常“IllegalThreadStateException”。
        thread1.start();
//        thread2.start();
        //TODO 创建Thread类的匿名子类的方式
        new Thread(){
            @Override
            public void run() {
                for(int i = 0 ; i < 100 ; i ++){
                    System.out.println(Thread.currentThread().getName() + " : " + i);
                }
            }
        }.start();
Thread类中常用方法
    1. start() : 启动当前线程,调用当前线程的run()
    1. run() : 通常需要重写,操作声明在此
    1. currentThread() : 静态方法,返回当前代码的线程
    1. getName() : 获取当前线程的名字
    1. setName() : 设置当前线程的名字
    1. yield() : 释放当前CPU的执行权
            if(i % 20 == 0){
                //释放CPU执行权,有可能再次抢到
                yield();
            }
    1. join() :调用线程将被阻塞,直到 join() 方法加入的线程执行完为止
            if(i % 20 == 0){
                //释放CPU执行权,有可能再次抢到
                join();
            }
    1. sleep(long millistime) : 当前线程睡眠 毫秒
    1. isAlive() : 判断当前线程是否还存活
线程的优先级的设置

在这里插入图片描述
同优先级的线程组成先进先出的队列(先到先服务) , 使用时间片策列
高优先级,使用优先调度的抢占式策略

  1. 线程的优先级等级
    MAX_PRIORITY:10
    MIN _PRIORITY:1
    NORM_PRIORITY:5 默认

  2. 优先级的方法

    getPriority() :返回线程优先值
    setPriority(int newPriority) :改变线程的优先级

2.2 Runnable接口

  • 1 . 创建一个实现了Runnable接口的类
  • 2 . 实现类区实现Runnable中的抽象方法:run()
  • 3 . 创建实现类的对象
  • 4 . 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 5 . 通过Thread类的对象调用start()
    启动线程—> 调用当前线程的run() —> 调用了Runnable类型的target
//TODO 1 . 创建一个实现了Runnable接口的类
class MThread implements Runnable{

    //TODO 2 . 实现类区实现Runnable中的抽象方法:run()
    public void run() {
        for(int i = 0 ; i < 100 ; i ++){
            System.out.println(i);
        }
    }
}


public class ThreadTest1 {
    public static void main(String[] args) {
        //TODO 3 . 创建实现类的对象
        MThread mThread = new MThread();
        //TODO 4 . 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //TODO 5 . 通过Thread类的对象调用start()
        t1.start();

    }

}

Java中的线程分为两类:一种是守护线程,一种是用户线程。
 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
 Java垃圾回收就是一个典型的守护线程。
 若JVM中都是守护线程,当前JVM将退出。
 形象理解:兔死狗烹,鸟尽弓藏

三.线程的声明周期

在这里插入图片描述

在这里插入图片描述

四.线程的同步

在这里插入图片描述
解决: 当一个线程A操作共享数据的时候,其他线程不能进来,A操作完其他线程才能操作

3.2.1) 同步代码块
synchronized(同步监视器){
	//需要被同步的code
}
说明:操作共享数据的代码,即为需要被同步的代码
	共享数据:多个线程共同操作的变量

同步监视器(锁)
任何一个对象都可以充当synchronized
多个线程必须要用同一把锁

class Ticket implements Runnable{


    private int ticket = 100;
    Object obj = new Object();

    public void run() {
        while (true){
            //TODO 同步代码块
            synchronized(obj){
            if(ticket > 0 ){
                System.out.println(Thread.currentThread().getName() + "售出车票,ticket号码为" + ticket--);
            }else {break;}
            }
        }
    }
}
3.2.2 同步方法

操作共享数据的操作在一个方法中,把这个方法设定为同步的

class Ticket2 implements Runnable{


    private int ticket = 100;
    Object obj = new Object();
    
    //TODO 提取出来一个同步方法
    private synchronized void show(){
        if(ticket > 0 ){
            System.out.println(Thread.currentThread().getName() + "售出车票,ticket号码为" + ticket--);
        }
    }

    public void run() {
        while (true){
            show();
        }
    }
}

extend方式實現

class Window4 extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    private static synchronized void show(){//同步监视器:Window4.class
        //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}
懒汉模式线程安全
class Bank {

    private Bank() {
    }

    private static Bank instance = null;


    public static Bank getInstance() {

        //TODO 方式一 效率低一些
//        if(instance == null){
//            instance = new Bank();
//        }
//        return instance;}
        //TODO 方式二 效率高一些
        if (instance == null) {
            synchronized (Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }

        }
        return instance;

    }
}

Lock 锁

从JDK5开始,Java提供了更强大的线程同步机制,通过显示定义 锁对象 来实现同步。同步锁用lock对象充当

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
每次只能由一个线程对Lock对象加锁

ReentrantLock类实现了Lock,它拥有于synchronized相同的并发性和内存予以
常用ReentrantLock 显示加锁 释放锁

class Window2 implements Runnable{

    private int ticket = 100;

    //TODO 1 实例化ReentrantLock 默认参数为不公平
    private ReentrantLock lock = new ReentrantLock(true);

    public void run() {
        while (true){

            //TODO 2 调用lock锁定
            lock.lock();
            try {
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() +"出售:" + ticket--);
                }else break;
            }finally {
                //TODO 4 调用unlock解锁
                lock.unlock();
            }


        }
    }
}
注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized & Lock

相同:都是来解决线程安全问题
不同:

  • synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器 Lock需要手动的执行同步,结束同步也需要手动实现

五.线程的通信

  • wait() :当前线程进入阻塞状态,并释放同步监视器
  • notify() : 唤醒优先级最高的阻塞状态的线程
  • notifyall() : 唤醒全部阻塞状态的线程

三个方法必须使用在同步代码块 同步方法 中的同步监视器
否则会出现java.lang.IllegalMonitorStateException

sleep wait 对比

相同:线程进入阻塞
不同点:

  • Thread.sleep() 是静态的 Object.wait() 非静态的
  • sleep 可以任意调用 wait 必须在同步代码块 同步方法中
  • sleep

六.JDK5.0新增的线程同步方式

3 实现Callable接口

与使用Runnable相比, Callable功能更强大些
 相比run()方法,可以有返回值
 方法可以抛出异常
 支持泛型的返回值
 需要借助FutureTask类,比如获取返回结果

Future接口
 可以对具体Runnable、Callable任务的执行结果进行取消、查询是
否完成、获取结果等。
 FutrueTask是Futrue接口的唯一的实现类
 FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值

//TODO 1 创建一个实现了Callable接口的实现类
class NumThread implements Callable{

    //TODO 2 实现call方法 , 将此线程实现的操作声明在call中
    public Object call() throws Exception {
        int sum = 0 ;
        for ( int i = 0 ; i <= 100 ; i ++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {

    public static void main(String[] args) {

        //TODO 3 创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //TODO 4 将Callable接口实现类对象作为参数传递到FutureTask中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //TODO 5 将futureTask对象作为参数传递给Thread类的构造器中,创建THread对象,start()
        Thread t1 = new Thread(futureTask);
        t1.start();

        try {
            //TODO 6  get() 返回值就是FutureTask构造器参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

4 线程池

提前创建好多个线程,放入到线程池当中,使用时直接获取
可以比米娜频繁创建销毁、 使用重复利用
减少创建新线程的时间 降低资源消耗

Executors 工具类

线程池的工厂类,用户创建并返回不同类型的线程池

  • newCachedThreadPool():创建一个可根据需要传概念新线程的线程池
  • newFixedThreadPool(n):创建固定个数的线程池
  • newSingleThreadExecutor():创建一个只有一个线程的线程池
  • newSechduledThreadPool(n):定期延后运行命令或者定期地执行
public class ThreadPool {

    public static void main(String[] args) {
        //TODO 1 提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //TODO 2 执行指定的线程的操作,需要提供实现Runnable接口或者Callable接口
        executorService.execute(new NumberThread());//适合使用于Runnable
        executorService.execute(new NumberThread1());//适合使用于Runnable
//        executorService.submit();//适合使用于Callable

        //TODO 3 关闭连接池
        executorService.shutdown();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oifengo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值