线程

1、什么是活锁、饥饿、无锁、死锁?

死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常执行下去了。

死锁

死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。

举个例子,A同学抢了B同学的钢笔,B同学抢了A同学的书,两个人都相互占用对方的东西,都在让对方先还给自己自己再还,这样一直争执下去等待对方还而又得不到解决。
老师知道此事后就让他们相互还给对方,这样在外力的干预下他们才解决,当然这只是个例子,没有老师他们也能很好解决,计算机不像人类,如果发现这种情况没有外力干预还是会一直阻塞下去的。

活锁

活锁这个概念大家应该很少有人听说或理解它的概念,而在多线程中这确实存在。

活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行。

当多线程中出现了相互谦让,都主动将资源释放给别的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁。

饥饿

我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无法得到执行,这就是饥饿。

当然还有一种饥饿的情况,一个线程一直占着一个资源不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够得到执行的,如那个占用资源的线程结束了并释放了资源。

无锁

无锁,即没有对资源锁定,即所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁典型的特定就是一个修改操作在一个循环内进行,线程会不断的尝试修改共享资源,如果没有冲突就修改成功并退出,否则就会继续下一次循环尝试。
所以,如果有多个线程修改同一个值必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。JDK的CAS原理及应用即是无锁的实现。

可以看出,无锁是一种非常良好的设计,它不会出现线程的跳跃性问题,锁使用不当肯定会出现系统性能问题,虽然无锁无法全面代替有锁,但无锁在某些场合下是非常高效的。

2、线程和进程的区别是什么?

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程奔溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。

线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

3、Java 实现线程有哪几种方式?

  • 1、继承 Thread 类实现多线程
  • 2、实现 Runnable 接口方式实现多线程
  • 3、使用 ExecutorService、Callable、Future 实现由返回结果的多线程
  • 4、通过线程池创建线程

4、启动线程方法 start() 和 run()有什么区别?

只有调用了 start() 方法,才会表现出多线程的特性,不同线程的 run() 方法里面的代码交替执行。如果只是调用 run()方法,那么代码还是同步执行的,必须等待一个线程的 run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。

5、咋样终止一个线程?如何优雅的终止线程?

stop 终止,不推荐。

6、一个线程的生命周期有哪几种状态?它们之间如何流转的?

NEW:毫无疑问表示的是刚创建的线程,还没有开始启动。

RUNNABLE:表示线程已经触发 start() 方法调用,线程正式启动,线程处于运行中状态。

BLOCKED:表示线程阻塞,等待获取锁,如碰到 synchronized、lock等关键字等占用临界区的情况,一旦获取到锁久进行 RUNNABLE 状态继续运行。

WAITING:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如 通过wait()方法进行等待的线程等待一个 notify() 或者 notifyAll() 方法,通过 join() 方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入了RUNNABLE状态继续运行。

TIMED_WAITING:表示线程进入了一个有时限的等待,如 sleep(3000),等待3秒后线程重新进行 RUNNABLE 状态继续运行。

TERMINATED:表示线程执行完毕后,进行终止状态。需要注意的是,一旦线程通过 start 方法启动后就再也不能回到初始 NEW 状态,线程终止也不能再回到 RUNNABLE 状态。

7、线程中的 wait() 和 sleep() 方法有什么区别?

这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的监视器。

8、多线程同步有哪几种方法?

Synchronized 关键字,Lock 锁实现,分布式锁等。

9、多线程有什么用?

1)发挥多核CPU的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程程序,那么再双核 CPU 上就浪费了 50%,在 4核 CPU 上就浪费了 75%。

单核 CPU 上所谓的"多线程"那是 假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。

多核 CPU 上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用 CPU 的目的。

2)防止阻塞

从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核 CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。

但是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。

多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其他任务的执行。

3)便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果吧这个大的任务 A 分解成几个小任务,任务B、任务C、任务D、分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

10、多线程之间如何进行通信?

wait/notify

11、线程咋样拿到返回结果?

实现 Callable 接口。

12、violatile 关键字的作用?

一个非常重要的问题,是每个学习,应用多线程的Java程序员都必须掌握的。理解 volatile 关键字的作用的前提是要理解Java内存模型,volatile 关键字的作用主要有两个:

1)、多线程主要围绕可见性和原子性两个特性而展开,使用 volatile 关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到 volatile 变量,一定是最新的数据。

2)、代码底层执行不像我们看到的高级语言----Java程序那么简单,它的执行是 Java代码 ——>字节码 ——>根据字节码执行对应的 C/C++代码 ——> C/C++代码被编译成汇编语言 ——> 和硬件电路交互,现实中,为了获取更好的性能 JVM 可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。

使用 volatile 则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率从实践角度而言,volatile的一个重要作用就是和CAS结合。

3、新建 T1、T2、T3 三个线程、如何保证它们按顺序执行?

用 join 方法

14、咋样控制同一时间只有 3 个线程运行?

用 Semaphore。

15、为什么要使用线程池?

我们知道不用线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少的话这不会是问题。

而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 和 内存资源,也会造成 GC 频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的。

如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不在有任务处理时它也会自动销毁,而不会长驻内存。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值