Java基础之多线程

多线程

线程

  • 进程有多个子任务,每一个子任务就是一个线程

进程

  • 简单理解,正在运行的程序或软件

线程与进程之间的关系

  • 线程依赖于进程而存在

  • 一个进程里面至少有1个线程

  • 用迅雷下载多个电影的例子

  • 线程共享进程资源

串行

  • 一个任务接一个任务按顺序执行

并行

  • 多个任务,在同一时刻(时间点)同时执行

并发

  • 多个任务,在同一时间段内同时执行

同步与异步

指的是被调用者

A调用B

同步: A的本次调用可以得到结果 (你走我不走)

异步: A的本次调用不会得到结果,等有了结果之后再通知A (你走你的我走我的)

Jvm是多线程的,至少有两个线程,main方法和GC回收垃圾

java采用的是抢占式的线程调度方式,优先级是1-10

多线程的实现方式一:继承Thread类

步骤

  1. 定义一个类继承Thread类

  2. 重写run方法

  3. 创建子类对象

  4. 通过start方法启动

注意事项

对象才代表一个线程,和类无关

一个线程不能多次启动

如果调用run方法就相当于普通的方法调用,并不是真正的启动线程

获取和设置线程名称

获取线程名称

StringgetName() 返回该线程的名称

获取主线程名称

static ThreadcurrentThread() 返回对当前正在执行的线程对象的引用

设置线程名称

voidsetName(String name) 改变线程名称,使之与参数 name 相同。

线程控制API

线程休眠sleep

| static void | sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 | | ----------- | ------------------------------------------------------------ |

线程加入(合并) join

voidjoin() 等待该线程终止。

哪个线程调用join,其他线程就必须等待该线程执行完才能执行

守护线程daemon

线程分类

  • 用户线程

  • 守护线程(为用户线程服务的) GC垃圾回收线程就是一个守护线程

voidsetDaemon(boolean on) 将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

当所有的线程中只剩下守护线程的时候,程序终止

多线程的实现方式二:实现Runnable接口

步骤

  1. 实现Runnable接口

  2. 重写run方法

  3. 创建子类对象

  4. 创建Thread对象,并且把实现了Runnable接口的子类对象作为参数传递

  5. start方法启动

注意

为什么Runnable中的run方法会运行在子线程当中

因为在Thread类中有一个target来判断我们传过来的Runnable接口的子类有没有重写run方法,如果有的话就执行

建议使用Runnable来实现多线程

解决多线程数据安全问题

产生的原因

  • 多线程的运行环境(需求不能改)

  • 多线程共享数据(需求不能改)

  • 存在非原子操作

    • 什么是原子操作? 一个操作要么1下完成,要么不完成

只能通过原子操作来解决多线程的安全问题

synchronized

同步代码块

锁对象 可以是任意的java对象(但是一定要保证是同一个)

同步方法

锁对象是this

静态方法

锁对象是类的字节码文件对象

细节

  • 同步代码块中的锁对象可以是任意java对象,任意java对象都可以充当锁对象的这个角色,仅限于同步代码块当中

    • 任意java对象内部,都存在这一个标志位,标志位用来表示加锁和释放锁

我们的代码是运行在某条执行路径下(某个线程),当某个线程要执行同步代码块

  • 访问之前会尝试对锁对象加锁,如果没有加锁,可以访问执行

  • 如果别的线程想要访问这个代码块,不能执行, 会处于阻塞状态

  • 当这个线程访问完了同步代码块,退出之前,会释放锁,修改标志位

lock

不建议使用

使用方法:

在要上锁的代码前使用:lock.lock()

执行完毕后使用解锁:lock.unlock()

但一般要配合finally使用,因为不能自动关闭,所以通常是使用synchronized来实现同步

死锁

2个或以上线程争抢资源而造成的互相等待的现象就被称之为死锁

死锁产生的场景

一般出现在嵌套同步代码块中,嵌套的顺序不一致就会存在死锁

// 同步代码块嵌套
synchronized(objA){
    synchronized(objB){
        // 代码
    }
}

死锁的解决方法

1.更改加锁顺序

2.在外面加一把大锁,变成原子操作

线程间通信

wait

  1. 阻塞功能: 当在某线程中,对象上.wait(), 在哪个线程中调用wait(), 导致哪个线程处于阻塞状态

  2. 唤醒条件 在其他线程中,在同一个对象(即对象A)上调用其notify()或notifyAll()

  3. 运行条件 当前线程必须拥有此对象监视器。 监视器:指synchronized代码块中的锁对象 即我们只能在当前线程所持有的synchronized代码块中的锁对象上调用wait方法,才能正常执行 如果我不在同步代码块中调用就会有这样一个异常 IllegalMonitorStateException

  4. 执行特征 执行wait的时候会释放监视器,即释放锁 注意:Thread的sleep方法,执行的时候: 该线程不丢失任何监视器的所属权

notify

  • 唤醒在此对象监视器上等待的单个线程。

  • 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。

  • 选择是任意性的

notifyAll

唤醒所有等待的线程

完整的线程的状态转换

多线程工具

线程池

使用线程池的原因:run方法执行完了之后线程就死了,需要重新创建,因此需要引入线程池

//JDK5提供了一Executors来产生线程池,有如下方法:
ExecutorService newCachedThreadPool()
// 特点:
// 1.会根据需要创建新线程,也可以自动删除,60s处于空闲状态的线程
// 2.线程数量可变,立马执行提交的异步任务(异步任务:在子线程中执行的任务)
// 适用场景: 执行很多短期异步的小程序或负载较轻的服务器
ExecutorService newFixedThreadPool(int nThreads)
// 特点:
// 1.线程数量固定
// 2.维护一个无界队列(暂存已提交的来不及执行的任务)
// 3.按照任务的提交顺序,将任务执行完毕  
// 适用场景: 执行长期的任务
ExecutorService newSingleThreadExecutor()
// 特点:
// 1.单个线程
// 2.维护了一个无界队列(暂存已提交的来不及执行的任务)
// 3.按照任务的提交顺序,将任务执行完毕
// 适用场景: 一个任务接一个任务执行

线程池的使用:ExecutorService(接口) Future<T> submit(Callable<T> task) Future<?> submit(Runnable task)

停止线程池:

shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

shutdownNow() 停止目前活动的所有任务

线程池的使用步骤

1.创建线程池

2.submit任务

多线程的实现方式三:实现Callable接口

会得到一个返回值,基本上用于在需要返回值的场景

Future 表示异步计算的结果

不使用线程池,可以用Future来执行Callable

使用future.get()可以获得异步计算的结果

Timer

schedule(TimerTask task, Date time)
schedule(TimerTask task, long delay, long period)//常用
schedule(TimerTask task, Date firstTime, long period)
scheduleAtFixedRate(TimerTask task, long delay, long period)
​
2跟4的区别 : 追赶特性

TimerTask是一个抽象类,需要子类继承并重写方法才能使用

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值