关于多线程的注意(I)

前言

1、java多线程有什么用?

一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管他有什么用?在我看来,这个回答更扯淡。所谓"知其然之气所以然“,”会用“只是”知其然“,”为什么用“才是”知其所以然“

(1)可以发挥多核cpu的优势

随着工业的起步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核也都不少见,如果是单线程的程序,那么在双核cpu就浪费了50%,在4核cpu上就浪费了75%。单核CPU上所谓的”多线程“都是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换的比较快,看着像多个线程”同时运行“罢了。多核上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。

(2)防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核cpu上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核cpu我们还要应用多线程,就是为了防止阻塞。试想,如果单线程cpu使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了,多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

 

(3)便于建模

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

2、创建线程的方式

比较常见的一个问题了,一般就是两种:

(1)继承Thread类

(2)实现Runnable类

至于哪个好,不用说肯定最后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。

3、 start()方法和run()方法之间的区别

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

4、Runnable接口和Callable接口的区别

好像有点深了,Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

这其实是非常有用的特性,因为多线程比单线程更难,更复杂的一个原因就是因为多线程充满着未知性,某条线程是否执行了以及某某条线程执行了多久,某条线程执行的时候我们期望的数据是否已经赋值完毕,无法得知,我们能做的只是等待这条多线程的任务执行完毕,而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的非常有用。

5、CyclicBarrier和CountDownLatch的区别

两个看是去有点像的类,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

(1)  CyclicBarrier的某个线程运行到某个点之后,该线程立即停止运行,知道所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某个线程运行到某个点之后,只是给某个数值-1而已,该线程继续运行

6、volatile关键字的作用

一个非常重要的问题,是每个学习,应用多线程的java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解java内存某模型,这里不讲内存模型了,可以参见第31点,volitile关键字的作用主要有两个:

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

(2)代码层执行不像我们看到的高级语言---java程序这么简单,它的执行是java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率

从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,如AtomicInteger。

7、什么是线程安全

又是一个理论的问题 ,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程执行永远都能获得一样的结果,那么你的代码就是线程安全的。

这个问题有值得一提的地方,就是线程安全也是几个级别的:

(1)不可变

像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了他们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用

(2)绝对线程安全

不管运行时环境如何,调用者都不需要额外的同步措施,要做到这一点通常需要付出许多额外的代价,java中标注自己时线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,java中也有,比方 说CopyOnWriteArrayList、CopyOnWriteArraySet

(3)相对线程安全

相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子爆炸,不会被打断,但也仅限于此,如果有个线程在便利某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。

(4)线程非安全

这个就没什么好说的了,ArrayList,LinkedList,HashMap等都是线程非安全的类

8、java中如何获得线程dump文件

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径,所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

(1)获取到线程的pid,可以通过使用jsp命令,在Linux环境下还可以使用ps -ef | grep -i java

  (2)   打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid

另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取到的是具体某个线程当前运行的堆栈

9、一个线程如果出现了运行时异常会怎么样

如果这个异常没有被捕获的话,这个线程就停止执行了,另外重要的一点就是:

如果这个线程持有对应的监视器的话,监视器会被释放。

10、如何在两个线程之间共享数据

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/Signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为了线程之间共享数据设计的

11、sleep() 方法 和 wait()方法有什么区别

sleep先阻塞,在唤醒,才就绪,且一直持有对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会直接就绪,且会放弃这个对象的监视器

12、生产者和消费者模型的作用是什么

这个问题很理论但很重要

(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这个是生产者消费者模型最重要的作用

(2)解耦。这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

13、ThreadLocal有什么用

简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行分离,数据不共享,自然就没有线程安全方面的问题了

14、为社么wait()\notify()\notifyall()方法要在同步代码块中执行

这个是jdk强制的,方法在调用之前都必须先获得对象的锁

15、wait() notify() notifyall()在放弃对象监视器有什么区别

wait()方法,会立即释放,

notify() 以及notifyAll()则会等待线程剩余代码执行完毕后才会放弃对象监视器

16、为什么要使用线程池

避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。

17、怎么检测到一个线程是否持有对象监视器

我也是在网上看到一道多线程面试题才知道有方法可以判断某个线程是否持有对象监视器:Thread类提供了一个holdslock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着“某条线程”指的是当前线程。

18、synchronized和ReentrantLock的区别

synchronized是和if\esle\for\while一样的关键字,ReentrantLock是类,这是二者的本质区别,既然ReentrantLock是类,那么它就提供了比synchronized更多灵活的特性,可以被继承并且可以有方法还可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:

(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁

(2)ReentrantLock可以获取各种锁的信息

(3)ReentrantLock可以灵活地实现多路通知

另外,二者的机制完全不一样,ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中markword,这点还不确定

19、ConcurrentHashMap的并发度是什么

ConcurrentHashMap的并发度就是segment的大小,默认是16,这意味着最多同时16条线程操作ConcurrentHashMap,这也是ConcurrentHahsMap对HashTable的最大优势,任何情况下,Hashtable能同时有两条线程获取HashTabled的数据吗?

20、ReadWriteLock是什么

首先明确一下,ReentrantLock不是不好,只是ReentrantLock某些时候有局限,如果使用ReentrantLock,可能本身就是为了防止线程A在写数据,线程B在读数据造成的数据不一致,但是如果两个线程都是读数据,没有涉及到修改,那么加锁就没有必要了,

还降低了效率

因为这个,才诞生了读写锁ReadWriteLock ,它是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

参考文章

https://www.cnblogs.com/xrq730/p/5060921.html

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值