线程入门知识

新启线程的两种方式

继承Thread

class UserThread : Thread(){
    override fun run() {
        super.run()
        println("I extend thread")
    }
}

fun main(){
    val userThread = UserThread()
    userThread.start()
}

实现Runnable接口

class UserRunnable : Runnable{
    override fun run() {
        println("I implement runnable")
    }
}

fun main(){
    val userRunnable = UserRunnable()
    Thread(userRunnable).start()
}

Thread和Runnable的区别

Thread是Java里对线程的唯一抽象,Runnable只是对任务(业务逻辑)的抽象。Thread可以接受任意一个Runnable的实例并执行。

通过继承Thread启动线程的缺点:

  • java只能单继承,继承了Thread就不能继承其他类,限制了扩展性

  • 这种方式会使任务和线程对象紧密耦合

  • 每次执行新任务都要new一个Thread对象,创建和销毁的开销大

  • 代码结构较差,不符合”组合优于继承“的原则

通过实现Runnable启动线程的优点

  • 可以继承其他类,更灵活

  • 让任务和线程对象解耦。一个任务可以被多个线程执行

  • 可以更好地配合线程池,线程池可以复用已创建的线程,效率更高

  • 代码结构更好,更面向对象

如何中止线程

  1. 不推荐使用的方法:stop() 原因:stop方法不会给线程正常释放资源的时间

  2. 推荐使用的方法: interrupt()

  • 用法: 在调用interrupt()方法后,线程通过isInterrupted()或Thread.interrupted()方法判断是否被中断

  • 原理: 调用线程的interrupt()方法就是将该线程的中断标志位改为true,然后通过检查方法来判断该线程是否 被中断。

  • 注意:如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则当线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

示例代码

输出结果

所以如果你自己自定义一个取消标志位来中断线程,是无法在阻塞状态下感知到你改变了取消标志位,也就无法及时中断线程。

线程的常用方法

start()

在这个方法中,我们创建的Thread实例才真正地和操作系统里面的线程挂钩,等分配到cpu才会调用实现的run()方法。

run()

实现业务逻辑的地方,可以多次调用。

yield()

让出该线程占用的cpu,但让出的时间不可设定,也不会释放锁。

join()

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A 的 join()方法,直到线程 A 执行完毕后,才会继续执行线程 B。

wait()

调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断 才会返回.需要注意,调用 wait()方法后,会释放对象的锁。可以设置等待时间,时间一过,退出等待状态,继续执行下面代码。

notify()/notifyAll()

通知一个/所有等待在该对象上的线程,没竞争到锁的线程继续等待。

线程间的共享

synchronized内置锁

  • 作用:保证同一时间只有一个线程访问,适合多个线程同时进行读写的场景。

  • 错误加锁:在同步块里面改变了加锁对象的地址

  • 用法: 通过加锁,保证打印顺序的正确

volatile

  • 作用:保证了不同线程对这个变量的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。适合一写多读的场景。

  • 用法: 如果不添加Volatile注释,会导致子线程不知道ready的变化,从而一直陷入循环。

ThreadLocal

  • 作用:为每一个线程都提供了变量的副本,适用于不想要多线程共享的场景。

  • 用法:如果不用ThreadLocal,就会导致用户名称,请求Id与线程名称无法对应,对应关系将会变得混乱。

  • 内存泄漏的原因:使用完ThreadLocal后不调用remove()方法

  • 错误用法:修改ThreadLocalMap存放的对象的属性值,这会导致所有使用此对象的线程都会修改。

线程安全

死锁

  • 触发条件:1.两个及以上的线程争夺两个及以上的资源;2.争夺资源的顺序不对;3.拿到资源不放手

  • 解决方案:1.让争夺资源的顺序一致;2.使用尝试拿锁的机制,拿到一把锁后尝试拿第二把,如果没拿到就会释放第一把锁

活锁

  • 触发原因:两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到第一把锁后,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。

  • 解决方案:每个线程休眠随机数,错开拿锁的时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值