【java多线程】java多线程+锁![synchronized、Lock、ReentrantLock、Runnable、Future、FutureTask、Callable、Executors]

一、概念

  • 程序:program,是为完成特定的任务或需求,而用某种语言编写的一组指令的集合。指一段静态代码,静态对象。
  • 进程:process,是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它的产生、存在、消亡等。进程作为资源分配的单位
  • 线程:Thread,进程可进一步细化为线程,是一个程序内部的一条执行路径。
  • 多线程:一个进程同一时间并行执行多个线程。
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc)。
  • 一个进程中的线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间通信更简单、高效。
  • 多个线程操作共享的系统资源可能带来安全的隐患。
1.单核CPU与多核CPU
  • 单核CPU:一种假的多线程。因为在一个时间单元内内,也只能执行一个线程的任务。(比如:收费站有多车道,但只有一个收费员,只有收费才能通过,否则就被挂起等待),因为CPU时间单元特别短,所以感觉不出来。
  • 多核CPU:现在大多是多核(服务器),才能更好的发挥多线程的效率。
  • 一个java程序,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。如果发生异常会影响主线程。
2.并行与并发
  • 并行:多个CPU同时执行多个任务。(多个人同时做不同事情)
  • 并发:一个CPU(采用时间片)同时执行多个任务。(秒杀、多个人做同一件事)

二、优点

背景:以单核CPU为例,使用单个线程先后完成多个任务比用多线程用时更短,为何仍需多线程?

优点

  • 1.提高应用程序的响应,对图形界面有意义,增强用户体验
  • 2.提高计算机系统CPU利用率
  • 3.改善程序结构,将长的分为多个独立运行,便于理解修改

三、使用场景

需求:

  • 程序需要执行两个或多个任务
  • 程序需执行等待的操作,如:用户输入、文件读写、网络操作、搜索等
  • 需要一些后台运行程序时

四、线程的创建和使用

java语言允许程序运行多个线程,通过java.lang.Thread类体现。
Thread类特性

  • 每个线程都是通过Thread对象的run()方法完成操作,run()方法体称为:线程体
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run方法。
1.Thread类

java.lang.Thread

构造器

  • Thread():创建新的对象
  • Thread(String threadName):创建线程并指定线程实例名
  • Thread(Runnable target):指定线程的目标对象,它实现了runnable接口的run方法
  • Thread(Runnable target,String name):创建新的Thread对象
2.Thread类的方法

Thread类有关方法

  • void start():启动线程,并执行对象的run()方法
  • run():线程在被调度时执行的操作
  • String getName():返回线程的名称
  • void setName():设置线程的名称
  • static Thread currentThread():返回当前线程。在Thread子类就时this,通常用于主线程和Runnable实现类

  • static void yield() :线程让步

  》暂停当前正在执行的线程,把机会让给优先级相同或者更高的线程
  》若队列中没有同优先级的线程,忽略此方法

  • join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止

  》低优先级的线程也可以获得执行

  • static void sleep(long millis):(指定时间:毫秒)

  》令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队
  》抛出InterruptedException异常

  • stop():强制线程生命期结束
  • boolean isAlive():返回boolean,判断线程是否还活着
3.创建方式
方式一:继承Thread类
  • 1.子类继承Thread类
  • 2.子类重写thread类的run方法
  • 3.创建子类对象。即创建线程对象
  • 4.调用start方法,启动线程,执行run方法

注意

  • 手动调用run,只是普通方法
  • run方法由JVM调用,什么时候调用,执行过程的控制都有系统的CPU的调度决定
  • 启动多线程,必须用start
  • 一个线程对象只能调用一次start,如果重复调用,抛出lllegalThreadStateException异常
方式二:实现Runnable接口
  • 1.定义子类,实现Runnable接口
  • 2.子类重写Runnable接口的run方法
  • 3.通过Thread类含参构造器创建线程对象
  • 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  • 5.调用Thread类的start方法,启动线程,执行Runnable接口的run方法
两种方式的区别

继承方式与实现方式的区别

区别

  • 继承Thread:线程代码存放在Thread类的run方法中
  • 实现Runnable:线程代码存放在接口的子类的run方法。

实现方式的好处

  • 避免了单继承的局限
  • 多个线程可以共享一个接口实现类的对象,非常适合多个相同的线程来处理同一份资源。
4.线程的调度

两种调度方式:

  • 分时调度模型:所有线程轮流使用CPU使用,平均分配每个线程占用Cpu的时间片

  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同,随机分配,优先级高的获取CPU时间片相对多

java 的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略
5.线程优先级

线程的优先等级

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1
  • NORM_PRIORITY:5

    涉及方法
方法说明
final int getPriority返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级,默认5,1~10
static void sleep(long millis)暂停
join等待死亡
setDaemon守护线程,全为守护线程JVM退出

说明

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并不绝对在高优先级之后执行

五、线程分类

java中线程分为两类:
守护线程+用户线程
说明

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

六、线程的声明周期

JDK中用Thread。state类定义了线程的集中状态

  • 新建:当Thread以及子类被创建对象,新生线程对象处于新建状态。
  • 就绪:新建线程被start后,将进入线程队列等待CPU时间片,具备运行条件,未分配到CPU资源
  • 运行:就绪线程获得CPU资源时,便进入运行状态,run方法定义了线程的操作功能
  • 阻塞:某种情况,被人挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了全部工作,或被提前强制中止或异常结束。
    在这里插入图片描述

七、线程状态转换图

在这里插入图片描述

八、线程的同步

1.同步

java对于多线程安全问题提供专业解决方式:同步机制
同步锁机制

在《Thinking in java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突做法:当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

注意

  • 必须确保使用同一资源的多个线程共用一把锁,非常重要。否则无法保证共享资源的安全
  • 一个线程类中所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

同步的范围

1.明确哪些是多线程执行的代码
2.多个线程是否有共享的数据
3.明确多个线程运行代码中是否有多条语句操作共享数据
所有操作共享数据的这些语句都要放在同步范围中

释放锁的操作

1.当前线程的同步方法、同步代码块执行结束
2.线程在执行中遇到break、return代码
3.出现未处理的Error、Exception,导致异常结束
4.执行了当前线程对象的wait方法,当前线程暂停,释放锁

不会释放锁的操作

1.线程执行同步代码块或同步方法时,调用Thread.sleep()\Thread.yield()方法暂停当前线程执行
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
应尽量避免使用suspend和resume来控制线程

2.synchronized

synchronized的锁是什么?

  • 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)
  • 同步方法的锁:静态方法(类名.class),非静态方法(this)
  • 同步代码块:自己指定,很多时候是this或类名.class

使用方法

同步代码块:
synchronized (对象){
//需要同步的代码;
}

同步方法:放在方法声明
public synchronized void show(){

}

3. 懒汉式
public class Singleton {
    //私有化构造器
    private Singleton(){
    }
    //提供一个当前类的实例
    //此实例必须静态化
    private static Singleton  single;

    //提供公共静态方法,返回当前类对象
    public static Singleton getInstance(){
        if (single==null){
            synchronized (Singleton.class){
                if(single==null){
                    single=new Singleton();
                }
            }
        }
        return single;
    }

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1==instance2);
    }
}
4.死锁
  • 不同线程分别占用对方需要同步的资源,不放弃,都在等待对方让步,形成线程死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步
5.Lock锁

Lock锁

  • JDK5.0开始,加入通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。比较常用ReentrantLock,可以显示加锁,释放锁
public class ReentrantTest {
    private final ReentrantLock lock = new ReentrantLock();
    public void show(){
        //加锁
        lock.lock();
        try{
            //代码
        }finally {
            //释放锁
            lock.unlock();
        }
    }
}
6.synchronized与Lock锁比较
  • Lock是显示锁(手动开启和释放锁),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块和方法锁
  • 使用Lock锁,JVM将花费较少时间来调度来调度线程,性能更好。并具有良好扩展性(提供更多的子类)

优先使用顺序
Lock->同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体外)

7.线程的通信

wait()、notify()、notifyAll()

  • wait() :令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程等待其他线程调用notify和notifyAll方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
  • noyifyAll():唤醒正在排队等候资源的所有线程结束等待

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则报java.lang.lllegalMonitorstateException异常

  • 因为这三个方法必须有锁对象调用,而任意对象都可作为synchronized的同步锁,因此这三个方法只能声明在Object类中

    在这里插入图片描述

九、JDK5.0新增两种线程

1.Callable

与Runnable相比

  • 相比run方法,有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

Future接口

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

    @Override
    public String call() throws Exception {
        return "我是"+Thread.currentThread().getName();
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new CallabaleTest());
        Thread thread = new Thread(futureTask);
        thread.setName("callable");
        thread.start();
        String s = futureTask.get();
        System.out.println(s);
    }
}
2.线程池
  • 背景:经常创建、销毁、使用量特别大的资源
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回。可以避免频繁创建、销毁,实现重复利用。
  • 好处:提高响应速度(减少创建时间)、降低资源消耗、便于线程管理:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:无任务时线程最多>     保持时间会终止
  • 线程池相关API
ExecutorService和Excutors
ExecutorService:真正线程池接口。常见子类:ThreadPoolEcecutor
Excutors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

在这里插入图片描述

知识图谱

在这里插入图片描述
在线查看:java多线程

在这里插入图片描述

欢迎一键三连,私信我领取知识图谱!
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

子非Yu@Itfuture

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

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

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

打赏作者

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

抵扣说明:

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

余额充值