java多线程

1、进程,线程之间的区别

进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元。
线程是进程的一个实体,是cpu调度和分派的基本单位,是比程序更小的能独立运行的基本单位.同一进程中的多个线程之间可以并发执行。多个线程共享内存资源,减少切换次数,效率更高。

 

2、创建线程的方式

(1)继承Thread类

Thread类其实是实现了Runnable接口的一个实例,继承Thread类后需要重写run方法并通过start方法启动线程。
继承Thread类耦合性太强了,因为java只能单继承,所以不利于扩展。

(2)实现Runnable接口

实现Runnable接口避免了单继承的局限性,所以较为常用。 
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。 (3)实现Callable接口 通过实现Callable接口并重写call方法,并把Callable实例传给FutureTask对象,再把FutureTask对象传给Thread对象。
它与Thread、Runnable最大的不同是Callable能返回一个异步处理的结果Future对象并能抛出异常,而其他两种不能。

 

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

(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。

(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。

(3) call方法可以抛出异常,run方法不可以。

(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

 

4、Thread类中的start()和run()方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。
当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。 只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。
如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。

 

5、什么是线程安全

多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,
那么就称这个类是线程安全的。 比如无状态对象一定是线程安全的。 线程安全也是有几个级别的: (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等都是线程非安全的类

 

6、你了解守护线程吗?它和非守护线程有什么区别

程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程.守护线程最典型的例子就是GC线程。

 

7、什么是多线程上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

 

8、原子性实现机制

 

处理器提供总线锁定和缓存锁定两种方式来保证复杂内存操作的原子性。

总线型:就是使用处理器提供一个LOCK信号,当一个处理器在总线传输信号时,其他处理器的请求将被阻塞住,那么该处理独占内存。所以总线锁定开销大。

缓存锁定:内存区域如果被缓存在缓存行中,且在在lock期间被锁定,当它执行锁操作写回内存时,
     处理器总线不在锁定而是通过修改内部的内存地址并使用缓存一致性制阻止同时修改保证操作的原子性。
     缓存一致性进制两个以上的处理器同时修改内存区域数据,其他处理器回写被锁定并且使其缓存行无效。

 

9、避免死锁的常见方法:

(1)避免一个线程同时获取多个锁
(2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
(3)尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
(4)对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

 

10、死锁的必要条件?死锁的解决办法?

产生死锁的四个必要条件:

互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解决方法:

撤消陷于死锁的全部进程;
逐个撤消陷于死锁的进程,直到死锁不存在;
从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失。
从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态

 

11、sleep() 和 wait() 区别

sleep() 是线程类(Thread)的静态方法。
导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),因为调用 sleep 不会释放对象锁。 wait() 是 Object 类的方法。
对此对象调用 wait()方法导致本线程放弃对象锁(线程暂停执行),释放资源并进入等待此对象的等待锁定池。
只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态。

 

12、线程同步相关的方法。

wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争;

 

13、线程的几种可用状态

线程在运行周期里有6中不同的状态:

(1)New 新建
(2)RUNNABLE 运行状态,运行与就绪
(3)BLOCKED 阻塞状态
(4)WAITING    等待状态
(5)TIME_WAITING 超时等待
(6)TERMINATED    终止状态

 

 

 

posted @ 2019-09-03 21:54  jet-software  阅读(170)  评论(0编辑  收藏  举报