搞了三年 Android 开发终于把线程、多线程和线程池全搞懂了,掌握这些核心知识 (1)
当调用 obj.notify/notifyAll 后,调用线程依旧持有 obj 锁,因此等待线程虽被唤醒,但仍无法获得 obj 锁,直到调用线程退出 synchronized 块,释放 obj 锁后,其他等待线程才有机会获得锁继续执行。
6、什么导致线程阻塞?
一般线程阻塞
1)线程执行了 Thread.sleep(int millsecond)方法,放弃 CPU,睡眠一段时间,一段时间过后恢复执行;
2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;
3)线程执行了一个对象的 wait()方法,直接进入阻塞态,等待其他线程执行 notify()/notifyAll()操作;
4)线程执行某些 IO 操作,因为等待相关资源而进入了阻塞态,如 System.in,但没有收到键盘的输入,则进入阻塞态。
5)线程礼让,Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得 CPU 时间。
线程自闭,join()方法,在当前线程调用另一个线程的 join()方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程再由阻塞转为就绪态。
6)线程执行 suspend()使线程进入阻塞态,必须 resume()方法被调用,才能使线程重新进入可执行状态
7?线程如何关闭?
1 ) 使用标志位
2)使用 stop()方法,但该方法就像关掉电脑电源一样,可能会发生预料不到的问题
3)使用中断 interrupt()
public class Thread {// 中断当前线程 public void interrupt();// 判断当前线程是否被中断 public boolen isInterrupt();// 清除当前线程的中断状态,并返回之前的值 public static boolen interrupted();
}
但调用 interrupt()方法只是传递中断请求消息,并不代表要立马停止目标线程。
8、讲一下 java 中的同步的方法
之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一性和准确性。
1)synchronized 修饰同步代码块或方法
由于 java 的每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需获得内置锁,否则就处于阴塞状态。
2)volatile 修饰变量
保证变量在线程间的可见性,每次线程要访问 volatile 修饰的变量时都从内存中读取,而不缓存中,这样每个线程访问到的变量都是一样的。且使用内存屏障。
3)ReentrantLock 重入锁,它常用的方法有 ReentrantLock():创建一个 ReentrantLock 实例
lock()获得锁 unlock()释放锁
4)使用局部变量 ThreadLocal 实现线程同步,每个线程都会保存一份该变量的副本,副本之间相互独立,这样每个线程都可以随意修改自己的副本,而不影响其他线程。
常用方法 ThreadLocal()创建一个线程本地变量;get()返回此线程局部的当前线程副本变量;initialValue()返回此线程局部变量的当前线程的初始值;set(T value)将此线程变量的当前线程副本中的值设置为 value
使用原子变量,如 AtomicInteger,常用方法 AtomicInteger(int value)创建个有给定初始值的 AtomicInteger 整数;addAndGet(int data)以原子方式将给定值与当前值相加
6)使用阻塞队列实现线程同步 LinkedBlockingQueue<E>
9、如何保证线程安全?
线程安全性体现在三方法:
1)原子性:提供互斥访问,同一时刻只能有一个线和至数据进行操作。
JDK 中提供了很多 atomic 类,如 AtomicInteger\AtomicBoolean\AtomicLong,它们是通过 CAS 完成原子性。JDK 提供锁分为两种:synchronized 依赖 JVM 实现锁,该关键字作用对象的作用范围内同一时刻只能有一个线程进行操作。另一种是 LOCK,是 JDK 提供的
代码层面的锁,依赖 CPU 指令,代表性是 ReentrantLock。
2)可见性:一个线程对主内存的修改及时被其他线程看到。
JVM 提供了 synchronized 和 volatile,volatile 的可见性是通过内存屏障和禁止重排序实现的,volatile 会在写操作时,在写操作后加一条 store 屏障指令,将本地内存中的共享变量值刷新到主内存;会在读操作时,
在读操作前加一条 load 指令,从内存中读取共享变量。
3)有序性:指令没有被编译器重排序。
可通过 volatile、synchronized、Lock 保证有序性。
10、两个进程同时要求写或者读,能不能实现?如何防止进程的同步?
我认为可以实现,比如两个进程都读取日历进程数据是没有问题,但同时写,应该会有冲突。
可以使用共享内存实现进程间数据共享。
11、线程间操作 List
12、Java 中对象的生命周期
1)创建阶段(Created):为对象分配存储空间,开始构造对象,从超类到子类对 static 成员初始化;超类成员变量按顺序初始化,递归调用超类的构造方法,子类成员变量按顺序初始化,子类构造方法调用。
2)应用阶段(In Use):对象至少被一个强引用持有着。
3)不可见阶段(Invisible):程序运行已超出对象作用域
4)不可达阶段(Unreachable):该对象不再被强引用所持有
5)收集阶段(Collected):假设该对象重写了 finalize()方法且未执行过,会去执行该方法。
6)终结阶段(Finalized):对象运行完 finalize()方法仍处于不可达状态,等待垃圾回收器对该对象空间进行回收。
7)对象空间重新分配阶段(De-allocated):垃圾回收器对该对象所占用的内存空间进行回收或再分配,该对象彻底消失。
13、static synchronized 方法的多线程访问和作用
static synchronized 控制的是类的所有实例访问,不管 new 了多少对象,只有一份,所以对该类的所有对象都加了锁。限制多线程中该类的所有实例同时访问 JVM 中该类对应的代码。
14、同一个类里面两个 synchronized 方法,两个线程同时访问的问题
如果 synchronized 修饰的是静态方法,锁的是当前类的 class 对象,进入同步代码前要获得当前类对象的锁;
普通方法,锁的是当前实例对象,进入同步代码前要获得的是当前实例的锁;
同步代码块,锁的是括号里面的对象,对给定的对象加锁,进入同步代码块库前要获得给定对象锁;
如果两个线程访问同一个对象的 synchronized 方法,会出现竞争,如果是不同对象,则不会相互影响。
15、volatile 的原理
有 volatile 变量修饰的共享变量进行写操作的时候会多一条汇编代码,lock addl $0x0,lock 前缀的指令在多核处理器下会将当前处理器缓存行的数据会写回到系统内存,这个写回内存的操作会引起在其他 CPU 里缓存了该内存地址的数据无效。同时 lock 前缀也相当于一个内存屏障,对内存操作顺序进行了限制。
评论