本文参考和引用如下网址:
https://blog.csdn.net/grey_mouse/article/details/84193844
https://blog.csdn.net/yincheng_917/article/details/82721424
https://www.cnblogs.com/renhui/p/6066852.html
https://www.cnblogs.com/xiaoxi/p/8311034.html
一,进程
进程的概念:
进程是操作系统分配一块独立空间和资源给一个运行的程序,这个程序包含一系列需要执行的任务。
单进程与多进程:
单进程的计算机一次只能做一件事情,而多进程的计算机可以做到一次做不同的事情,比如一边听音乐,一边听打游戏,这两件事情虽然感觉起来是在同时一起进行的,但其实是CPU在做着程序间的高效切换,这才让我们觉得是同时进行的。
进程的创建:
通过Runtime的exec()方法和ProcessBuilder的start()方法可以创建进程。
二,线程
线程就是进程中执行的某个任务。
单线程和多线程
单线程就是一个任务执行到底,期间不去做任务以外的事情,一个程序(app)几乎不可能一条线程跑到底,多线程是指程序同一时间段有多个任务一起执行,多线程的目的不是提供程序的运行速度,而是提高程序的使用率,多线程其实没有同时执行多个任务,而是根据线程执行时的使用频率等情况分配使用资源,只是操作系统运行速度快,让人以为是多个任务同时执行。
线程从创建到消亡,经历的状态包括:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。
java创建一个线程后,并不会马上马上进入准备就绪状态,等运行条件(如内存分配)都准备好后,线程进入就绪状态。当线程进入就绪状态后,并非立刻就能获取CPU执行时间,当得到CPU执行时间后正式进入运行状态。
线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。当由于突然中断或者子任务执行完毕,线程就会被消亡。
这副图描述了线程从创建到消亡之间的状态:
blocked、waiting、time waiting统称为阻塞状态。
java Thread类:
public
class Thread implements Runnable {
//synchronized修饰的锁对象
private final Object lock = new Object();
//线程名称
private volatile String name;
//线程的优先级(最大值为10,最小值为1,默认值为5)
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
//是否守护线程
private boolean daemon = false;
//要执行的任务
private Runnable target;
Thread中关系到线程运行状态的几个常用方法:
(1)start方法:启动一个创建好的线程,当调用此方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
(2)run方法:无需用户调用,通过start方法启动线程后,线程处于就绪状态并获得CPU执行时间后,自动进入run方法执行具体任务。
(3)sleep方法:让线程交出CPU,让CPU去执行其他任务。sleep方法有两个版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds)//第一参数为毫秒,第二个参数为纳秒
注意点:sleep不会主动释放锁,当线程持有某个对象的锁时,即使调用sleep方法,其他线程也无法访问此对象。具体参照的代码:
private class SleepThread extends Thread {
private String tag;
public void setTag(String tag) {
this.tag = tag;
}
@Override
public void run() {
super.run();
Log.e("SleepThread", tag + " start running");
synchronized (lock) {
try {
Log.e("SleepThread", tag + " sleeping now");
Thread.sleep(3000);
} catch (Exception e) {
}
Log.e("SleepThread", tag + " sleeping end");
}
}
}
private void threadSleep() {
SleepThread s1 = new SleepThread();
s1.setTag("Sleep-001");
s1.start();
SleepThread s2 = new SleepThread();
s2.setTag("Sleep-002");
s2.start();
}
执行结果:
E/SleepThread: Sleep-001 start running
E/SleepThread: Sleep-001 sleeping now
E/SleepThread: Sleep-002 start running
E/SleepThread: Sleep-001 sleeping end
E/SleepThread: Sleep-002 sleeping now
E/SleepThread: Sleep-002 sleeping end
s1和s2都执行start方法,s1持有对象lock的锁,虽然s1调用sleep处于休眠状态,但是s1没有释放锁对象lock,所以s2没有执行操作,等s1执行完sleep并释放lock对象后,s2才开始执行。
(4)yield方法:跟sleep方法一样,调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程,跟调用yield方法同级或更高优先级的线程有更大概率抢的CPU,yield不能控制交出CPU的时间,调用yield方法线程会回到准备就绪状态,而不是像sleep方法一样阻塞线程。yield和sleep一样不会主动释放持有的锁对象。
(5)join方法:线程调用join方法后,主线程处于阻塞等待状态,等待此线程执行完毕(调用无参join方法)或指定时间(调用指定时间参数的join方法)后继续执行。
join()
join(long millis)//参数为毫秒
join(long millis,int nanoseconds)//第一参数为毫秒,第二个参数为纳秒
join方法实际调用Object的wait方法:
public final void join(long millis)
throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。子线程调用join方法实际调用Object的wait方法,持有Object的主线程进入阻塞状态,等待子线程执行完毕。
(6)interrupt方法:中断一个处于阻塞状态的线程,处于运行状态的线程无法被interrupt方法中断,所以线程实际使用中最好设置标记tag让线程在需要的情况下可以被中断。
private RunThread r1;
private void startRunThread() {
r1 = new RunThread();
r1.start();
}
private void interruptThread() {
if(r1 != null) {
r1.setRun(false);
r1.interrupt();
}
}
private class RunThread extends Thread {
private boolean run = true;
public void setRun(boolean run) {
this.run = run;
}
@Override
public void run() {
super.run();
try {
while(run) {
Log.e("RunThread", "while-running--");
}
} catch (Exception e) {
Log.e("RunThread", "run-error");
}
}
}
例子中有一个boolean参数run,当为true时while循环正常执行,此时无法被interrupt中断,调用interruptThread方法时将run的值设为false,while循环不执行,此时线程可被interrupt方法中断。
(7)stop方法:stop方法已经是一个废弃的且不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。
(8)destroy方法:废弃的不安全方法,会抛出UnsupportedOperationException异常。
Thread中属性方法:
getId:获取线程ID
getName和setName:线程名称
getPriority和setPriority:线程优先级
currentThread:获取当前线程
setDaemon和isDaemon:设置和判断守护线程
守护线程和用户线程:守护线程依赖于创建它的线程,而用户线程则不依赖。如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
三,synchronize同步代码块
为了解决线程之间的安全问题,java引入synchronize同步锁。当多个线程都要去判断同步锁时,耗费资源又降低程序的运行效率。当一个线程取得某个锁的引用时,其他线程不能引用这个锁,除非正在引用锁的线程主动释放这个锁。
如果多个线程需要调用同一个同步锁对象,锁必须独立于所有需要调用它的线程的内存之外,而不能创建于其中的某个线程之内,否则失去同步锁的意义。
Lock接口:如果用户需要主动加锁和解锁,JDK5以后提供了Lock接口和它的ReentrantLock子类,用户可以收到加解锁。
参考此网址的例子可以明白如何加解锁:https://blog.csdn.net/grey_mouse/article/details/84193844
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunThread implements Runnable{
int runtag = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();//加锁
if (runtag > 0) {
try {
Thread.sleep(1000);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("RunThread", "runtag");
runtag --;
lock.unlock();//解锁
}else {
break;
}
}
}
}
四,死锁问题:死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
参考:https://www.cnblogs.com/xiaoxi/p/8311034.html
Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。这是最容易理解也是最简单的死锁的形式。
产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的。
从上面的网址摘抄一个例子:
public class DeadLock implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + "运行");
if(flag){
synchronized(obj1){
System.out.println(Thread.currentThread().getName() + "已经锁住obj1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj2){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj2");
}
}
}else{
synchronized(obj2){
System.out.println(Thread.currentThread().getName() + "已经锁住obj2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj1){
// 执行不到这里
System.out.println("1秒钟后,"+Thread.currentThread().getName()
+ "锁住obj1");
}
}
}
}
}
public class DeadLockTest {
public static void main(String[] args) {
Thread t1 = new Thread(new DeadLock(true), "线程1");
Thread t2 = new Thread(new DeadLock(false), "线程2");
t1.start();
t2.start();
}
}
//运行结果
线程1运行
线程1已经锁住obj1
线程2运行
线程2已经锁住obj2
线程1锁住了obj1(甲占有桥的一部分资源),线程2锁住了obj2(乙占有桥的一部分资源),线程1企图锁住obj2(甲让乙退出桥面,乙不从),进入阻塞,线程2企图锁住obj1(乙让甲退出桥面,甲不从),进入阻塞,死锁了。 从这个例子也可以反映出,死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的。
避免死锁的方法:
(1)线程按照一定的顺序加锁,还是上面网址摘抄的例子:
public class SyncThread1 implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread1(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (obj1) {
System.out.println(name + " acquired lock on "+obj1);
work();
}
System.out.println(name + " released lock on "+obj1);
synchronized(obj2){
System.out.println("After, "+ name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadDeadTest1 {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Thread t1 = new Thread(new SyncThread1(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread1(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread1(obj3, obj1), "t3");
t1.start();
Thread.sleep(1000);
t2.start();
Thread.sleep(1000);
t3.start();
}
}
//运行结果
t1 acquired lock on java.lang.Object@60e128
t2 acquired lock on java.lang.Object@18b3364
t3 acquired lock on java.lang.Object@76fba0
t1 released lock on java.lang.Object@60e128
t2 released lock on java.lang.Object@18b3364
After, t1 acquired lock on java.lang.Object@18b3364
t3 released lock on java.lang.Object@76fba0
After, t2 acquired lock on java.lang.Object@76fba0
After, t3 acquired lock on java.lang.Object@60e128
t1 released lock on java.lang.Object@18b3364
t1 finished execution.
t2 released lock on java.lang.Object@76fba0
t3 released lock on java.lang.Object@60e128
t3 finished execution.
t2 finished execution.
从结果中看,没有出现死锁的局面。因为在run()方法中,不存在嵌套封锁。
避免嵌套封锁:这是死锁最主要的原因的,如果你已经有一个资源了就要避免封锁另一个资源。如果你运行时只有一个对象封锁,那是几乎不可能出现一个死锁局面的。
(2)设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。
(3)主动设置加锁实现,通过Lock和ReentrantLock类的tryLock主动设置超时时限,超时后主动返回失败信息,使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间,这样就打破了死锁的闭环。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}