可以看出只有就绪状态和运行状态能够相互转换
1、线程的生命周期?线程有几种状态
2、sleep()、wait()、join()、yield()的区别
2.1 首先了解一下等待池和锁池
1.等待池
2.锁池
2.2 sleep和wait的区别:
sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁。也就是说无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的。
2.3 yield和join的区别:
- yield()执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格, 所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
- join()执行后线程进入阻塞状态,例如启动A线程后,立即调用B线程的join()方法,则线程B会进入到阻塞队列,直到线程A结束或中断线程之后线程B才会继续执行
3.volatile、synchronized、Lock的区别
2.1 synchronized与Lock的区别
-
synchronized是Java关键字,在JVM层面实现加锁和解锁;Lock是一个接口,在代码层面实现加锁和解锁。
-
synchronized可以用在代码块上、方法上;Lock只能写在代码里。
-
synchronized在代码执行完或出现异常时自动释放锁;Lock不会自动释放锁,需要在finally中显示释放锁。
-
synchronized会导致线程拿不到锁一直等待;Lock可以设置获取锁失败的超时时间。
-
synchronized无法得知是否获取锁成功;Lock则可以通过tryLock得知加锁是否成功。
-
synchronized锁可重入、不可中断、非公平;Lock锁可重入、可中断、可公平/不公平,并可以细分读写锁以提高效率。
2.2 volatile和synchronized的区别:
- volatile 关键字只能修饰变量,而synchronized可以修饰方法和普通代码块,但不能修饰静态代码块。
- volatile关键字不会发生阻塞,而synchronized关键字可能会阻塞
- volatile关键字主要解决数据的可见性和有序性,但是不完全保证数据的原子性,synchronized保证了数据的可见性、有序性和原子性。
4.并发、并行和串行
区别:
- 串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
- 并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
- 并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行。
多线程三大特性:
1.原子性 关键字:synchronized
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
2.可见性 关键字:volatile、synchronized、final
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3.有序性 关键字:volatile、synchronized
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
总结:
synchronized关键字同时满足多线程三个特性,但是volatile关键字不满足原子性。在某些情况下,volatile的同步机制的性能确实要优于锁(使用synchronized关键字或java.util.concurrent包里面的锁),因为volatile的总开销要比锁低。我们判断使用volatile还是synchronized加锁的唯一依据就是volatile的语义能否满足使用的场景(原子性)。
5.线程通信的三种方式:
1.wait()、notify()、notifyAll()
线程之间采用synchronized来保证线程安全时,可以用Object类下的wait()、notify()、notifyAll()来实现线程通信。原因是每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作。这三个方法都是本地方法,并且被final修饰,无法被重写。
2.await()、signal()、signalAll()
线程之间采用Lock接口来保证线程安全时,因为Condition依赖于Lock接口,则可以利用Condition接口下的await()、signal()、signalAll()来实现线程通信。
3.BlockingQueue
BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。线程之间需要通信,最经典的场景就是生产者与消费者模型,而BlockingQueue就是针对该模型提供的解决方案。
6.多线程多通道处理:
线程内多Channel通道处理IO流由Java NIO支持,NIO采用内存映射文件的方式来处理输入/输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式来进行输入/输出比传统的输入/输出要快得多。
Java NIO 由三个核心组件构成:Selector (选择器), Channel(通道) , Buffer (缓冲区)。 虽然NIO体系还是有其它组件,比如:Pipe
,FileLock
等 它们只是与三个组件结合实际使用类。
- Java NIO中的Selector允许单线程处理多个 Channel,如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件例如有新连接进来,数据接收等。
- Channel(通道):所有的IO在NIO中都从一个Channel开始,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中。Channel有好几种类型,其中比较常用的有FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel等,这些通道涵盖了UDP和TCP网络IO以及文件IO。
- Buffer (缓冲区):Buffer本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。Java NIO里关键的Buffer实现有CharBuffer、ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。这些Buffer覆盖了你能通过IO发送的基本数据类型,即byte、short、int、long、float、double、char。Buffer对象包含三个重要的属性,分别是capacity、position、limit,其中position和limit的含义取决于Buffer处在读模式还是写模式。但不管Buffer处在什么模式,capacity的含义总是一样的。
-
capacity:作为一个内存块,Buffer有个固定的最大值,就是capacity。Buffer只能写capacity个数据,一旦Buffer满了,需要将其清空才能继续写数据往里写数据。
-
position:当写数据到Buffer中时,position表示当前的位置。初始的position值为0。当一个数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity–1。当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
-
limit:在写模式下,Buffer的limit表示最多能往Buffer里写多少数据,此时limit等于capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据,此时limit会被设置成写模式下的position值。
-
三者关联关系:
一个线程对应一个selector, 一个selector 对应多个channel。 多个通道中可以相互切换,是根据事件event来决定使用那个channel。
优点:
NIO单线程处理多通道的好处,处理通道所需要的线程数更少。一个线程可以处理所有的通道。在操作系统层面,线程的上下文切换是成本非常高。每个线程都会对应操作系统的内存。因此,线程数越少越好。
缺点:
但随着CPU技术突破,多核超线程出现,若使用单线程来处理,有点浪费cpu资源。
channel是指可以向其写入数据或读取数据的对象。 类似于IO中的strem。但是所有数据的读写都由buffer来进行的,channel不进行任何处理。
一个stream只能是InputStream或者OutputStream。但channel是双向的,channel打开后,即可向其写入数据,也可以同时读取数据。
buffer本身就是一块内存,底层使用数组来实现。buffer非线程安全,因此,在多线程并发的环境下,当访问buffer时,需要做适当的线程控制。