Java多线程开发——基础篇

目录

1.基本概念

2.创建线程方式

2.1直接建立线程

2.2实现Runnable接口

3.3实现Callable接口

3.4 了解Future接口

Future模式主要角色及其作用

3.5实例化FutureTask类

3.实现线程安全

3.1定义

3.2不安全原因

3.3解决方案

3.4volatile与synchronized区别

3.5Lock与sychronized区别

4.极端情况——线程死锁

4.1定义

4.2解决措施

5.多线程通信

6.线程池的掌握

6.1核心参数

6.2常见线程池

6.3自定义线程池

6.4操作步骤

6.5Handler拒绝策略


1.基本概念

线程状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、

                  等待(Waiting)、超时等待(Timed Waiting)、终止(Terminated)

锁(同步监视器):分为悲观锁和乐观锁,常见的有偏向锁、轻量级锁、重量级锁

                                CAS(Compare And Swap)是一种乐观锁的实现方式,通过比较并

                                交换来实现并发控制

2.创建线程方式

Runnable接口、Callable接口、Future接口、FutureTask类

2.1直接建立线程

        创建Thread子类,按需求重写run()方法 【run代表此线程执行时会做的事】

        这里的属性若为 多线程共享属性,加static修饰

2.2实现Runnable接口

 这里的 多线程共享属性 可以是非静态的,因为多线程共用此接口

3.3实现Callable接口

(1)实现接口Callable,重写call()回调方法,会返回一个值,值类型由Callable泛型决定

实例化上述类,利用FutureTask(Callable)构造器实例化类

3.4 了解Future接口

异步调用的多线程开发模式之一(异步:当我们需要调用一个函数方法时,并不急着要结果。让被调者立即返回,让它在后台慢慢处理这个请求;此时则可以先处理一些其他任务

Future模式主要角色及其作用

Main->调用Client发送请求

Data->返回数据的接口

Client->返回Data对象,立即返回FutureData,并开启ClientThread线程装配RealData

FutureData->虚拟数据,伪造数据立即返回,最终装配上RealData

RealData->真实数据,构造缓慢

3.5实例化FutureTask类

FutureTask用于异步获取执行结果或取消执行任务

通过传入Runnable或者Callable的任务给FutureTask,

直接调用其run方法或者放入线程池执行,

最后在外部通过FutureTask的get方法异步获取执行结果(适合耗时操作)

3.实现线程安全

3.1定义

多线程环境下,对共享资源的访问不会导致数据出错。

因此和单线程执行相同的操作,结果相同

3.2不安全原因

1. 线程是抢占式的执行,线程间的调度充满了随机性
2. 多个线程对同一个变量进行修改操作
3. 对变量的操作不是原子性的
4. 内存可见性导致的线程安全问题
5. 指令重排序也会影响线程安全

3.3解决方案

1、加锁,使用同步机制(如synchronized、Lock)

2、直接使用线程安全的数据结构(如ConcurrentHashMap)

3、利用原子操作类的“比较和替换”机制

4、单个共享变量的线程安全使用volatile关键字

synchronized:Java关键字,属于隐式锁,可以修饰方法或代码块

Lock:JAVA接口,显式锁

原理上都是通过对共享资源加锁来实现同步。

3.4volatile与synchronized区别

volatile轻量级的锁,保证变量的可见性和有序性,但仅仅保证单个变量的原子性

可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入,解决多核CPU缓存可见性问题

有序性:采用内存屏障来实现的,就是在编译器生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

原子性:对单个volatile变量的读写具有原子性,对“volatile变量++”这种复合操作则不具有原子性,

而synchronized既保证可见性又保证原子性

volatile适用于单个变量的读写操作,而synchronized适用于复合操作或临界区。

3.5Lock与sychronized区别

使用方式: 1、Lock接口是显式锁,即我们需要调用其内部定义的方法显式地加锁和解锁,更加灵活,Lock对象创建Condition对象来实现线程通信

                   2、synchronized关键字是隐式锁,无需显式地获取和释放锁,使用方便

 功能特性:Lock弥补了synchronized的不足,它新增了特性:1、可中断地获取锁,2、 非阻塞地获取锁,3、可超时地获取锁

实现机制:AQS是队列同步器,是用来构建锁的基础框架,Lock实现类都是基于AQS实现的

                  synchronized的底层是采用Java对象头来存储锁信息的,对象头包含三部分,分别是Mark Word、Class Metadata Address、Array length。

4.极端情况——线程死锁

4.1定义

多个线程相互等待对方释放资源,导致无法继续执行

4.2解决措施

  1. 避免嵌套锁、

  2. 按固定的顺序获取锁、

  3. 设置超时时间、

  4. 使用Lock对象代替synchronized关键字。

5.多线程通信

通过共享变量、wait()和notify()、BlockingQueue等机制来实现线程间的数据交换和协作

6.线程池的掌握

6.1核心参数

核心线程数(corePoolSize):依据任务的处理时间和每秒产生的任务数量来确定

最大线程数(maximumPoolSize):参照核心线程数和系统每秒产生的最大任务数决定

线程空闲时间(keepAliveTime):用户自设置合理时间间隔

任务队列(workQueue):一定的顺序或优先级来执行任务

拒绝策略(handler):线程池已经关闭或达到饱和(最大线程和队列都已满)状态时,新提交的

                                任务将会被拒绝。

6.2处理流程

向线程池提交一个任务之后,线程池按照如下步骤处理这个任务

1. 线程数未达corePoolSize,新建线程执行该任务

2. 等待队列未满, 任务放入等待队列

3. 等待队列满,线程数未达到maxinumPoolSize,新建线程执行任务

4. 采用初始化线程池时指定的拒绝策略,拒绝执行该任务。

注意:

1、新建的线程处理完当前任务后,不会立刻关闭,而是继续处理等待队列中的任务。

2、如果线程的空闲时间达到了keepAliveTime,则线程池会销毁一部分线程,将线程数量收缩至corePoolSize。

6.3Handler拒绝策略

拒绝策略分别对应着RejectedExecutionHandler接口的4个实现类,

1、让调用者自己执行任务

2、直接抛出异常

3、丢弃任务不做任何处理

4、删除队列中最老的任务并把当前任务加入队列

6.4常见线程池

FixedThreadPool、CachedThreadPool、ScheduledThreadPool

6.5自定义线程池

1:编写任务类(MyTask),实现Runnable接口;
2:编写线程类(MyWorker),用于执行任务,需要持有所有任务;
3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
4:编写测试类(MyTest),创建线程池对象,提交多个任务测试;

如下模板:

package com.itheima.demo01;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/*
    这是自定义的线程池类;

    成员变量:
        1:任务队列   集合  需要控制线程安全问题
        2:当前线程数量
        3:核心线程数量
        4:最大线程数量
        5:任务队列的长度
    成员方法
        1:提交任务;
            将任务添加到集合中,需要判断是否超出了任务总长度
        2:执行任务;
            判断当前线程的数量,决定创建核心线程还是非核心线程
 */
public class MyThreadPool {
    // 1:任务队列   集合  需要控制线程安全问题
    private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
    //2:当前线程数量
    private int num;
    //3:核心线程数量
    private int corePoolSize;
    //4:最大线程数量
    private int maxSize;
    //5:任务队列的长度
    private int workSize;

    public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
        this.corePoolSize = corePoolSize;
        this.maxSize = maxSize;
        this.workSize = workSize;
    }

    //1:提交任务;
    public void submit(Runnable r){
        //判断当前集合中任务的数量,是否超出了最大任务数量
        if(tasks.size()>=workSize){
            System.out.println("任务:"+r+"被丢弃了...");
        }else {
            tasks.add(r);
            //执行任务
            execTask(r);
        }
    }
    //2:执行任务;
    private void execTask(Runnable r) {
        //判断当前线程池中的线程总数量,是否超出了核心数,
        if(num < corePoolSize){
            new MyWorker("核心线程:"+num,tasks).start();
            num++;
        }else if(num < maxSize){
            new MyWorker("非核心线程:"+num,tasks).start();
            num++;
        }else {
            System.out.println("任务:"+r+" 被缓存了...");
        }
    }

}

6.6操作步骤

1:利用Executors工厂类的静态方法,创建线程池对象;
2:编写Runnable或Callable实现类的实例对象;
3:利用ExecutorService的submit方法或ScheduledExecutorService的schedule方    法提交并执行线程任务
4:如果有执行结果,则处理异步执行结果(Future)
5:调用shutdown()方法,关闭线程池

  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值