Thread 和 Runnable的区别
- 可以避免由于Java的单继承特性而带来的局限
- 增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
- 适合多个相同程序代码的线程区处理同一资源的情况
public class RunnableDemo{
public static void main(String[] args){
MyThread my = new MyThread();
new Thread(my).start();
new Thread(my).start();
new Thread(my).start();
}
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i=0;i<10;i++)
{
if(ticket > 0){
System.out.println("ticket = " + ticket--);
}
}
}
}
}
- ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测,ticket–并不是原子操作。
- 我们new了3个Thread对象,但只有一个Runnable对象,3个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码,即3个线程各自卖5张票。
- 由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全,比如可能ticket会输出-1,这种情况的出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。
多线程概览
线程的状态流转图
小记:
- java线程的优先级范围:0~10,值越大优先级越高,默认5
- 可运行状态的线程还需要获得CPU的时间片后才能运行
- ThreadLocal:每个线程有一个localValue存储ThreadLocal=>Object键值对,ThreadLocal.put数据的时候把自身作为key与value保存到localValue,get的时候在从localValue里取出
多线程和进程
- 线程依附于进程,每个进程可以有多个线程,每个线程只属于一个进程
- 进程拥有独立内存单元,互不干扰;一个进程里的多个线程共享内存
- 进程是资源分配的基本单位,线程是处理调度的基本单位
Thread和Runnable
- Runnale比Thread更具优势:避免单继承限制,Runnable更适合多线程去处理同一资源(一个Runnable实例多个Thread执行,Thread做不到 http://www.mamicode.com/info-detail-517008.html)
线程池
- newCachedThreadPool 无限线程 空闲回收 不够则创建
- newFixedThreadPool 控制最大并发数 有队列
- newScheduledThreadPool 控制最大并发数 有队列 有周期性
- newSingleThreadExecutor 只有一个线程
- shutdown:线程池置为关闭状态并intercept等待的线程;shutdownNow线程池置为关闭状态并intercept所有线程
线程常用操作
- yield wait sleep (yield sleep属于Thread方法,wait notify属于Object方法)
- wait:可以指定时间也可以不指定,不指定只能由notify、notifyAll唤醒。wait时线程释放执行权和锁
- notify/notifyAll 唤醒指定线程或所有线程(与wait配对,必须放在synchronized里否则抛运行时异常)
- sleep:必须指定时间,sleep时释放执行权但不释放锁
- yield:让步(进入可执行状态),暂停当前线程,让其他线程先走,下次线程调度程序调到又继续执行(不一定成功,因为让步后可能马上又被线程调度程序调起)
- interrupt:用于中断线程,清除解冻状态,sleep/wait都可以用interrupt打断。但interrupt并不会让线程中断而是会抛出InterruptedException异常,我们需要手动添加异常catch并在异常中结束线程否则会继续往下执行,如果interrupt调用时,线程并不在InterruptedException包括范围内,则会等待InterruptedException的出现。isInterrupted()用以判断线程是否处于中断状态(eg:调用线程的interrupt时线程isInterrupted=true,如果线程又不在异常包裹范围则会一直保持这个状态而当抛出异常后isInterrupted则又变为false)。
- setDaemon():设置当前线程为守护线程或用户线程
- A{B.join()}:两个线程需要都启动了才有效。A线程中调用B.join,会暂停A,执行B直到B执行结束后再执行A
线程的暂停、恢复和停止的实践
class TestRunnable implements Runnable
{
private boolean isPause = false;
private boolean isStop = false;
public void run() {
while(true){
if(isStop){
return ;
}
while(isPause){
sleep(200);
}
//doSomething
}
}
}
经典消费者生产者实例
Queue<Integer> buffer = new LinkedList<>();
int maxSize = 10;
Thread producer = new Producer(buffer, maxSize, "PRODUCER");
Thread consumer = new Consumer(buffer, maxSize, "CONSUMER");
producer.start(); consumer.start();
守护线程 和 线程阻塞
Java中有两类线程:User Thread 用户线程、Daemon Thread 守护线程,用户线程即运行在前台的线程,而守护线程是运行在后台的线程。只有前台线程都结束后守护线程才会退出。
普通线程可在start之前可setDaemon设置为守护线程,守护线程中产生的线程也是守护线程。
线程可以阻塞于四种状态:
- 当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断
- 当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止(若制定了超时值的话)
- 线程阻塞与不同I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间
线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)
注意,并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应
https://blog.youkuaiyun.com/ns_code/article/details/17099981
同步锁 volatile synchronized
volatile
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制
声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
synchronized
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
- 一个方法中synchronized锁住的部分不会影响其他没有被synchronized的部分
- synchronized 修饰方法,锁住的是类实例对象
- synchronized关键字不能继承
- 接口方法和构造函数不能使用synchronized关键字
- 同一个类中多个方法用synchronized修饰,则其中一个方法被调用锁住时,其他方法也会被锁住无法被其他线程执行,直到锁被释放
- synchronized(XXX.class) 和 synchronized修饰静态方法,锁住的是类对象
- 同一个类中多个代码块被synchronized(XXX.class)修饰则其中一个代码块被调用执行,其他几个同步代码块也将被锁,直到锁被释放
- synchronized修饰静态方法和synchronized(XXX.class) ,锁住都是类对象,固二者会互相影响
- synchronized(XXX.class)和synch(this)互不影响,因为锁的对象不同,前者是类对象,后者类实例对象
- synchronized 修饰方法 和 synchronized(this)代码块,锁住的都是类实例对象,二者互相影响
- synchronized(obj) 修饰代码块,锁住指定对象
- 同一个类中如果obj不同,则锁住的对象也不同,自然不会互相阻塞。
Object obj1 = new Object();
Object obj2 = new Object();
public void m1(){
synchronized(obj1){
//todo
}
}
public void m2(){
synchronized(obj2){
//todo
}
}
m1被A线程执行的时候不会阻塞B线程执行m2
- synchronized(obj)并不影响其他线程对obj对象的使用,其他线程使用obj对象也不会阻塞
volatile与synchronized:volatile只修饰变量且不保证原子性(取值的时候会从CPU工作内存中取出刷新共享内存得到最新值)。volatile①保证此变量对所有线程的可见性②禁止指令重排序。synchronized 则可以使用在变量、方法、和类级别的,也禁止指令重排序。volatile性能比synchronized好,volatile比较适用于多线程对变量高并发的读操作
如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝
重排序 happen-before 内存屏障
重排序是编译器和处理器为了优化性能而对指令执行的顺序进行重排序。大多数现代处理器都会采用将指令乱序执行的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。
也就是说程序的执行,并不是严格按照程序语句编写的顺序执行,在运行期间可能是被打乱的。
重排序发生的位置:编译器重排序、指令级并行重排序、内存系统重排序
as-if-serial
重排序下如何保证程序的正常执行呢?
原因就是Java遵循as-if-serial语义。它保证,在单线程执行程序时,即使发生重排序,程序的执行结果不能被改变。
简单来说,重排序是为了优化性能,然后通过sa-if-serial语义来保证重排序后程序执行结果不被改变。
happens-before规则
- 程序次序规则: 线程中每个动作A都happens-before于该线程中的每一个动作B。那么在程序中,所有的动作B都能出现在A之后。
- 监视器锁法则: 对一个监视器的解锁happens-before于每个后续对同一监视器锁的加锁
- volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作
- 线程启动法则: 在一个线程中,对于Thread.start的调用会happens-before于每个启动线程的动作。
- 线程终结法则: 线程中的任何动作都happens-before于其他线程检测到这个线程已经终结。
- 中断法则: 一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
- 终结法则: 一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
- 传递性: 如果A happens-before于B,且B happens-before于C,则A happens-before于C
PS:volatile和synchronized都可以禁止指令的重排序
内存屏障
内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
- LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
- StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
- LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
- StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
死锁
可重入锁
A线程进入同步代码时获得锁,且计数值为1,其他线程无法获得该锁,但A线程还可以获得该锁(即重入锁),并且计数值再次+1,数值为2.退出后计数值-1,直到计数值为0,其他线程方可获取
https://blog.youkuaiyun.com/ns_code/article/details/17014135
public class Father
{
public synchronized void doSomething(){
......
}
}
public class Child extends Father
{
public synchronized void doSomething(){
......
super.doSomething();
}
}
如果没有重入锁机制,这段代码将产生死锁。
新特性
Executor框架与线程池
Lock锁
阻塞队列和阻塞栈
障碍器CyclicBarrier
信号量Semaphore