多线程篇
(一)实现创建线程的两种方式有哪些
(1)实现Runnable接口
new Thread(new Runnable() {
public void run() {
}
}).start();
(2)继承 Thread类重写run方法
new Thread( {
@Override
public void run() {
}
}).start();
(3)具体是创建Callable接口的实现类,实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程
FutureTask本质上实现了Runnable, Future接口 源码 public interface RunnableFuture extends Runnable, Future 因此 FutureTask可以做为target对象。
Callable callable = new Callable<Integer>() {
public Integer call() throws Exception {
System.out.println("我是线程");
return 1;
}
}; // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(callable); //使用FutureTask来包装MyCallable对象
new Thread(ft).start(); //FutureTask对象作为Thread对象的target创建新的线程
(二) Runnable接口和Thread类方法的底层实现是什么?
(1)Runnable接口只有一个抽象方法 run方法
public abstract void run();
(2)Thread类
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
target 就是现实runnable 接口的对象
(3)总结:因此只有实现runnable接口才走Thread类
里面的run方法 ,继承Thread类会走重写的run方法。
(三)thread.join 使用场景 底层实现?
底层实现:join底层就是调用了wait()方法核心源码
If (millis == 0) {
while (isAlive()) {
wait(0);
}
wait(0)线程一直等待下去,直到让它的等待,直到阻塞它的线程对象执行完毕,由jvm调该对象的notifyAll()方法,唤醒所有在该对象上阻塞的线程。
使用场景:A线程和B线程C线程,如果需要ABC顺序执行则 这里需要使用到join方法。在B线程中使用A.JOIN ,C线程中调B.JOIN,
通过源码我们知道A.JOIN()其实用了A.wait(0),使B线程处于等待状态
只有待A线程执行完毕,由jvm调用A对象的notifyAll()方法,唤醒所有在A对象上阻塞的线程。
(四) wait(),notifyAll(),notify()的使用?
这三个方法必须使用在被synchronized 修饰的代码块或者方法中。
(五)synchronized关键字特点
保证原子性 :synchronized关键字锁住的代码,一次只能被一个线程所操作。
保证可见性:在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存,在主存中拷贝最新变量的副本到工作内存,执行完代码后,将更改后的共享变量的值刷新到主内存中,释放锁。
保证有序性:synchronized通过“一个变量 在同一时刻只允许一个线程对它进行lock操作”,这条规则决定了持有同一个锁的两个同步块只能串行的并入
(六) java中同步锁synchronized与Lock的区别
(1)synchronized是java内置关键字,,Lock是个接口(唯一实现类ReentrantLock);
(2)synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁(tryLock())
(3)synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),
Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
(4)用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
(七)synchronized与volatile的区别
1,volatile不会造成线程的阻塞,synchronized会。
2,synchronized会 造成线程状态的改变,而线程状态的改变又依赖于操作系统,所以效率会比较低。
3,synchronized可以修饰代码块、方法。volatile只能修饰变量。
4,synchronized能保证原子性、volatile不能。
5,volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
(八)什么是可重入锁?
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
实例:
、synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
setA获得锁时setB也会获得锁,synchronized就是可重入锁
(九)Hashtable 和ConcurrentHashMap区别
(1) 相同点:ConcurrentHashMap 和 Hashtable 都是线程安全的
(2) 不同点:Hashtable 底层方法都是synchronized修饰的 只有一把锁效率低下。
ConcurrentHashMap :(1)底层是分段数组+链表实现 被称为分段锁,
(2)将map分成 N个segmen对象,对每个segmen对象单独加luck锁
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap中则是一次锁住一个桶,,原来只能一个线程进入,现在却能同时有多个写线程执行,并发性能的提升是显而易见的。
(十)造成死锁产生的4个必要条件
互斥:一个资源一次只能被一个进程占用。
占有且等待:一个进程占有一个资源 ,等待别的资源。
不可抢占:一个进程占有的资源,不能被别的资源抢占。
循环等待:存在一个进程链 ,每个进程都占有一个资源,并等待下个进程释放它占有的资源的至少 一种资源
(十一)什么是CAS; 乐观锁
CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。
(十二)什么是乐观锁和悲观锁
(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
(十三)ThreadLocal是什么?怎么用?
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
原理:
首先,在Thread类中有一行:
ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap类的定义是在ThreadLocal类中,真正的引用却是在Thread类中。同时,ThreadLocalMap中用于存储数据的entry定义:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
从中我们可以发现这个Map的key是ThreadLocal类的实例对象,value为用户的值,并不是网上大多数的例子key是线程的名字或者标识。ThreadLocal的set和get方法代码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
其中的getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
给当前Thread类对象初始化ThreadlocalMap属性:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
总结:
(1)Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
(2)当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
(3)ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。