一、进程与线程
1.进程:系统进行资源分配和调度的一个独立单位。可以通过Runtime.exec()或ProcessBuilder的start方法创建进程
线程:线程是程序执行流的最小单位。继承Thread或实现Runnble接口
1.1 线程thread的方法:start(),stop(),run(),join() 其他线程等待,执行当前线程,直至结束,sleep() 调用该方法该线程进入等待
wait()和notify()方法属于Object的方法,wait()和sleep()的区别:wait()会释放对象锁而sleep()不会释放对象锁
1.2 线程状态:
1)新建状态:新建线程对象,并没有调用start()方法之前
2)就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。
3)运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态
4)阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态
5)死亡状态:线程执行结束,1、run方法正常退出而自然死亡,2、一个未捕获的异常终止了run方法而使线程猝死。可以通过isAlive方法判断
1.3 线程死锁:是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.
活锁:活锁的线程或进程的状态是不断改变的。活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行
1.3.1 产生原因:
1)系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁
2)线程在运行过程中,请求和释放资源的顺序不当,会导致死锁
1.3.2 四个必要条件:只要系统发生死锁则以上四个条件至少有一个成立。
- 互斥条件:一个资源被一线程占有时,则其他线程只能等待其释放。
- 不可剥夺条件:线程所获得的资源在未使用完毕之前,不被其他线程强行剥夺,而只能由获得该资源的线程资源释放。
- 请求和保持条件:请求申请新的资源的同时,继续占用已分配到的资源。
- 循环等待条件:在发生死锁时必然存在一个线程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个线程等待环路,环路中每一个线程所占有的资源同时被另一个申请,也就是前一个线程占有后一个线程所深情地资源。
1.3.3 避免死锁:
1)加锁顺序(如果多个线程顺序地获取锁,就不会发生死锁现象)
2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
3)死锁检测 (比如用jstack检测)
1.4 锁类型
1)可重入锁:在执行对象中所有同步方法不用再次获得锁。lock和synchronized都可重入
2)可中断锁:在等待获取锁过程中可中断。lock 可中断,synchronized不可中断
3)公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利。synchronized非公平锁,lock默认非公平,new ReentrantLock(true)可设置公平
4)读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写
1.5 守护线程和用户线程
- Daemon的作用是为其他线程的运行提供服务,比如说GC线程
- 两者区别:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了
- 非虚拟机内部提供,用户可以自行设定守护线程,public final void setDaemon(boolean on) ,thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常,不能把正在运行的常规线程设置为守护线程,这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别
- 在Daemon线程中产生的新线程也是Daemon的,而守护进程fork()出来的子进程不再是守护进程
- 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了,JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态
- 守护线程实际应用:web服务器中的Servlet,容器启动时后台初始化一个服务线程,即调度线程,负责处理http请求,然后每个请求过来调度线程从线程池中取出一个工作者线程来处理该请求,从而实现并发控制的目的
1.6 java.lang.Thread中有一个方法叫holdsLock()方法来检测一个线程是否拥有锁
1.7 Thread类中的yield():可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行
二、java内存模型
2.java内存模型:只是一个抽象概念。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。数据存在与主内存中,少部分存在cpn寄存器中,主内存是内存共享区域,当要修改主内存中的数据时,需要将数据复制到自己的工作内存,即cpu缓存中,操作完数据后将数据flush到主内存中
2.1 通信:共享内存和消息传递
1.1 共享内存通信方式就是通过共享对象进行通信。同步是显式进行
1.2 消息传递方式就是wait()和notify()。同步是隐式进行的
2.2 共享对象的可见性:
一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位于不同的CPU缓存中
解决共享对象可见性:使用java volatile关键字。volatile原理是基于CPU内存屏障指令实现的
三、volatile
3.volatile:当CPU写数据时,会发出信号通知其他CPU将该变量的缓存行置为无效状态,它就会从内存重新读取
3.1 并发编程中:原子性问题,可见性问题,有序性问题
3.1.1 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
请分析以下哪些操作是原子性操作:
x = 10; //原子性 只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作) 才是原子操作。
y = x; //非原子性
x++; //非原子性
x = x + 1; //非原子性
解决原子性:synchronized和Lock实现
3.1.2 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
解决可见性:volatile。synchronized和Lock也能够保证可见性
3.1.3 有序性:即程序执行的顺序按照代码的先后顺序执行
3.1.3.1 指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,
但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
3.1.3.2 数据依赖性:如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行
3.1.3.3 解决有序性:synchronized和Lock来保证有序性。volatile关键字来保证一定的“有序性”,volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性。
//x、y为非volatile变量.flag为volatile变量
//语句1、2一定发生再4、5之前,但不能保证语句1发生在2之前,4发生在5之前
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
3.1.3.4 Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。
如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序
happens-before原则(先行发生原则):
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作(不排除发生指令重排序可能)
锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
四、synchronized
3.synchronized
3.1 注意事项:
1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法,其他线程能访问该对象的非synchronized方法
2)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),
也不会产生线程安全问题,因为他们访问的是不同的对象
3)如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,
此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁
3.2 synchronized代码块比synchronized灵活,只对需要同步的地方进行同步
3.3 通过反编译指令javap -c 类名,可以知道synchronized代码块实际