()Synchronized与Lock区别 --都是可重入锁
答:Lock能够实现Synchronized的所有功能(互斥性、内存可见性),Synchronized会自动释放锁,Lock必须手动释放,将unLock()放到finally中,Synchronized一直等待直到获取锁,Lock可以在指定时间获取不到锁就自动放弃(tryLock),Synchronized无法响应中断,Lock可以响应中断(LockInterruptibly,每次申请锁前,判断线程中断标识是否为true,如果是抛出中断异常)。
Synchronized(非公平锁) synchronized同步基础:java中每个对象都有一把锁。
()java实现同步机制的3种方法:1.synchronized 2.lock 3.wait、notify
()CAS(compare-and-set)-->是一种乐观锁
答: CAS 建立在“硬件指令集”上,是一个原子操作。(更高效,c/c++都有)
CAS操作的三个操作数:
1.变量内存地址D
2.变量旧的预期值V
3.变量的新值N
当预期值V和D相等的时候,将D更新为新值N,否则不更新。
例:if(a==b){ a++;},如果a++之前,a的值被改变,a的值存在问题。
为了保证结果正确,除了用锁解决,可以用CAS。
While(true){
int expect=a;
if(compareAndSet(&a,expect,a+1)){
return;
}
}
()CAS的缺点?解决ABA问题?
答:存在ABA问题。就是一个变量V,CAS前读取的值是A,CAS准备赋值的时候检查其仍然是A,可能在这段期间V的值被修改成B,然后又改回A,CAS操作误以为没有被修改过。为对象添加版本号,每次变量更新的时候对象的版本号加1(判断版本号和CAS合一起是一个原子操作)。
()什么是Lock的AQS(AbstractQueuedSynchronizer抽象队列同步器)?
答:在AQS内部会保存一个状态变量state,通过CAS修改该变量的值,修改成功的线程表示获取到该锁,没有修改成功,或者发现状态state已经是加锁状态,则通过一个Waiter对象封装线程,添加到等待队列中,并挂起等待被唤醒。
()自旋锁-->工作于多处理器环境
答:在线程没有取得锁的时候,不被挂起(不引起上下文切换),而转去执行一个空循环,若在若干次空循环后,线程如果获得锁,则继续执行,如果依然不能获得锁,才会被挂起。 优点:避免线程的切换代价
()原子类的实现
答:CAS死循环尝试更新。如AtomicInteger保证增加、减小操作的原子性。
设计一个并发安全的计数器
(a)LongAdder代替原子类AtomicLong实现计数器
(b)Innodb创建计数器表
()公平锁、非公平锁
答:公平锁:先申请锁的线程先获得锁。
非公平锁:随机挑选线程获得锁。
如何确保N个线程可以访问N个资源同时又不导致死锁?
答:指定获取锁的顺序,强制线程按指定顺序获取锁。
()CountDownLatch --不可重复使用
答:倒计数的锁,当计数为0时,唤醒线程。
(一个线程等待一组线程执行完成)
重要方法:
countDown()-->计数器减1,当计数为0时,唤醒线程
await()-->当计数器大于0时,阻塞线程。
()CyclicBarrier --可重复使用
答:关于计数器的锁
(将一组线程集中到某一点再开始执行)
重要方法:
await()-->计数器减1,当计数器大于0时,阻塞当
前线程,为0时,唤醒所有等待线程。
java计数器:
小顶堆存储定时事件,线程不断从堆顶取出事件判断是需要立即执行还是等待一段时间(等的时间:wait(当前时间-堆顶时间),如果插入堆的事件就是最小的,notify唤醒线程判断是否需要立即执行)。
多线程
线程状态转换图:
(7)sleep()与wait()
答:sleep(静态方法)属于Thread类,sleep不释放锁,可以自动唤醒,wait()属于object类,wait方法释放锁,需要notify/notifyAll唤醒。
Wait:循环中调用。
Synchronized(obj) {While(condition does not hold) obj.wait();}
()yield->使当前运行线程处于就绪状态,不释放锁。
进程(操作系统调度):每个进程都有独立的代码和数据空间(进程上下文),进程的切换会有较大的开销,一个进程包含 1-n个线程。(进程是资源分配的最小单位)。
线程(操作系统调度):同一进程中的线程共享代码和数据空间,每个线程有独立运行栈和程序计数器,线程切换开销小。(线程是cpu调度的最小单位)
进程 线程
占用内存多,创建、销毁、切换代价大 占用内存少,创建、销毁、切换代价小
-------需要频繁创建销毁的优先线程。(web服务器处理连接)
-------需要大量计算的优先线程。(cpu切换频繁)
每个java.lang.Thread类的实例就代表一个线程。
Java的线程调度方式为抢占式。一个线程用完CPU之后,操作系统根据线程优先级、线程饥饿情况等算出一个总的优先级并分配时间片给某个线程。
HotSpot(1:1):Java线程被映射到系统的原生线程上,java中没有办法强制启动一个线程,它是由线程调度器控制着。
3种创建线程方式: --一个线程异常终止,对其他线程无影响
1)继承Thread类 默认优先级5
2)实现Runnable接口
3)使用Callable和Future创建线程
前两种无法获取线程执行结果,Callable产生结果,Future获得结果
检测一个线程是否拥有某个对象的锁:Thread的holdsLock(Object)方法
Java中两类线程:用户线程、守护线程
唯一区别:JVM何时退出
守护线程为用户线程提供服务(最典型的GC),当JVM实例中只存在守护线程时,JVM会退出,只要存在用户线程,JVM就不会离开。
钩子线程:每个进程都可以注册多个钩子线程,钩子线程在进程退出前执行。
Runtime.getRuntime().addShutdownHook(Thread) IdentityHashMap<Thread,Thread>
钩子线程并发执行
线程类的静态块、构造方法在哪个线程调用?
答:new这个线程类所在的线程调用。而run方法才被线程自身调用。
Java进程间通信:
1.共享内存MappedByteBuffer
2.Rmi-->本质还是socket
Join()方法
(java.lang.Thread类的实例方法):thread.join(),线程thread执行完以后,调用此条语句的线程才继续执行。(参数为0)
原理:调用thread.join()时,join方法内部调用wait()方法,此时调用此条语句的线程会被放入thread对象的等待池中,当线程完成后,调用notifyAll()唤醒线程。
Java中断:每个线程对象中都有一个boolean类型的标识(只是一个标志位,本身并不影响线程中断与否),代表是否有中断请求,调用线程对象的interrupt()方法,可以将对应线程对象的标识置为true,通过判断线程的中断标识可以控制线程的执行。(当线程的中断标志位为true后,因为调用sleep、wait、join方法而导致的阻塞会抛出InterruptedException异常)
(例如当线程t1想中断线程t2,只需要在线程t1中调用t2的interrupt(),将线程t2的中断标识置为true,t2执行中判断该标识来决定操作。)
终止线程的方法:1.stop()强行退出,释放锁,过时方法,强行把执行到一半的线程终止,可能引起数据不一致的问题。(例如更新数据到一半)
2.interrupt()方法
线程的run方法无法抛出异常(throws),但线程却可能因为一个异常而终止,导致无法回收一些系统资源,可以为线程设置一个UncaughtExceptionHandler,当出现一个未捕获异常时,JVM会使用该对象进行处理。
线程处理不可捕捉异常:查找线程对象的未捕获异常处理器,如果不存在查找线程对象所在线程组的未捕获异常处理器,如果不存在查找默认的未捕获异常处理器,如果都不存在将堆栈异常记录打印到控制台,退出程序。