Java的进程与线程
进程的相关概念
- 进程是指运行中的程序,比如当我们打开一个应用时,就启动了一个进程,操作系统就会为该进程分配内存空间。
- 进程是程序的一次执行过程,或是也可以说是正在运行的程序。进程是动态过程,有自身的产生,存在和消亡过程。
- 再比如windows系统的任务管理器可以查看当前计算机正在运行的进程以及占用内存情况:
线程的相关概念
-
线程是由进程创建的,是进程的一个实体
-
一个进程可以有多个线程。比如一个QQ进程,可以同时打开多个聊天窗口,这就是多线程的体现
-
同一时刻只允许执行一个线程称为单线程;同样地,同一时刻可以执行多个线程称为多线程
-
线程并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核cpu实现的多任务就是并发
-
线程并行:同一时刻,多个任务同时执行,多核CPU可以实现并行。
-
用Java程序获取当前电脑的CPU数量
public class CpuNum { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); //获取当前电脑的CPU数量 int cpuNums = runtime.availableProcessors(); System.out.println("当前电脑CPU数量="+cpuNums); } }
线程的基本使用
1. 继承Thread类,重写Run方法
-
Thread 类图,Thread类实现了Runnable接口
-
public class ThreadExam extends Thread{ public static void main(String[] args) { ThreadExam threadExam = new ThreadExam(); threadExam.start(); } @Override public void run() {//重写run方法,写上自己的业务逻辑 while (true) { System.out.println("线程运行"); try { Thread.sleep(1000);//线程休眠一秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
为什么启动线程调用的是run方法而不是start方法?
- run方法就是一个普通的方法,没有真正启动一个线程,如果直接调用run方法,主线程mian就会先把run方法执行完毕再向下执行
- 当调用了start方法,主线程main会启动一个子线程Thread-0,此时主线程不会阻塞,会继续执行,主线程和子线程会交替执行
-
源码解读:在start方法打上断点,查看源码
-
//首先调用start方法,其核心方法是star0 public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); //核心方法 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
-
//核心是start0方法 //该方法是一个native方法,是JVM调用,底层是C/C++实现,涉及到操作系统 //真正实现多线程效果的是start0(),而不是run() private native void start0();
-
start方法调用start0方法之后,该线程并不会马上执行,只是将该线程变成了可运行状态,具体什么时候执行取决于CPU,由CPU统一调度
-
2. 实现Runnable接口
-
Java是单继承的,某些情况下一个类可能已经继承了某个父类,这时就不能用继承Thread类的方法来创建线程了,此时可以通过实现Runnable接口来创建线程。
-
代码实例
public class ThreadExam02 implements Runnable{ //通过实现Runnable接口,实现线程 public static void main(String[] args) { ThreadExam02 threadExam02 = new ThreadExam02(); //创建一个Thread对象把实现Runnble接口的对象放入 //再调用start方法,开启线程 //底层使用了设计模式(代理模式) new Thread(threadExam02).start(); } @Override public void run() { while (true){ System.out.println("线程执行"+Thread.currentThread().getName()); try { Thread.sleep(1000);//休眠一秒 } catch (InterruptedException e) { e.printStackTrace(); } } } }
3. 多线程执行实例
public class ThreadExam03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
new Thread(t1).start(); //启动第一个线程
new Thread(t2).start(); //启动第二个线程
}
}
class T1 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class T2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
-
示例图
4. 线程终止
线程终止的方法:
-
当线程执行完毕之后,会自动退出;
-
通过使用变量来控制run方法退出的方式停止线程,即通知方式
-
代码实例
public class ThreadExit { public static void main(String[] args) { T t = new T(); new Thread(t).start(); //如果希望在主线程可以控制t线程终止,可以修改loop属性让t退出run方法从而终止线程 t.setLoop(false); } } class T implements Runnable{ private boolean loop = true; //控制线程终止的变量 public void setLoop(boolean loop) { this.loop = loop; } @Override public void run() { while (loop){ try { Thread.sleep(1000); //休眠一秒 } catch (Exception e) { e.printStackTrace(); } System.out.println("线程正在运行..."); } } }
线程的常用方法
方法 作用 setName 设置线程的名称,使得与参数名称相同 getName 返回该线程的名称 start 使该线程开始执行,底层实际上是JVM调用start0方法 run 调用线程对象的run方法 setPriority 更改线程的优先级 getPriority 获取线程的优先级 sleep 在指定的毫秒数内让正在执行的线程休眠 interrupt 中断线程,但并不是结束线程,一般用于中断正在休眠的线程 yield 线程的礼让。让出CPU,让其他线程先执行,但礼让的时间不确定,礼让不一定成功 join 线程插队。插队的线程一旦插队成功,则先执行完插入的线程的任务 -
-
用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或者通知方式结束
-
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
-
代码示例
public class ThreadExam04 { public static void main(String[] args) throws InterruptedException{ DeomonThread deomonThread = new DeomonThread(); //如果希望main线程结束以后子线程自动结束 //只需要将子线程设置成为守护线程即可,守护线程就是,当所有的用户线程结束以后,守护线程自动结束 deomonThread.setDaemon(true); deomonThread.start(); for (int i = 0; i < 10; i++) { System.out.println("main线程运行"); Thread.sleep(1000); } } } class DeomonThread extends Thread{ @Override public void run() { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行"); } } }
-
线程的生命周期
JDK中用Thread.State枚举表示了线程的几种状态
public enum State {
/**
* 尚未启动的线程处于此状态
*/
NEW,
/**
* 在Java虚拟机中执行的线程处于此状态
*/
RUNNABLE,
/**
* 被阻塞等待监视器锁定的线程处于此状态
*/
BLOCKED,
/**
* 正在等待另一个线程执行特定动作的线程处于此状态
*/
WAITING,
/**
* 正在等待另一个线程执行特定动作达到指定等待时间的线程处于此状态
*/
TIMED_WAITING,
/**
* 已退出的线程处于此状态
*/
TERMINATED;
}
-
线程转换状态示意图
线程的同步
-
线程同步机制
-
在多线程编程中,一些敏感数据不允许被多个线程访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
-
线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程操作完成,其他线程才可以对该内存地址进行操作
-
同步具体方法-Synchronized
-
同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码 //需要被同步的代码 }
-
synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void method(){ //需要被同步的代码 }
-
-
互斥锁的概念
- Java语言中引入对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应于一个可以称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程来访问对象
- 关键字synchronized来与对象的互斥锁联系
- 同步的局限性:导致程序执行效率变低
- 同步方法(非静态)的锁可以是this,也可以是其他对象(要求是同一个对象);默认对象是this
- 同步方法(静态的)锁为当前类本身。
-
释放锁
- 会释放锁的操作
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的error或者exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait方法,当前线程暂停,并释放锁
- 不会释放锁
- 线程执行同步代码块或者同步方法时,程序调用Thread.sleep、Thread.yield方法暂停该线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放锁
- 会释放锁的操作
-
线程的死锁
-
基本概念
-
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,编程时要避免死锁的发生
-
死锁案例
public class DeadLock { public static void main(String[] args) { //模拟死锁现象 DeadLockDemo A = new DeadLockDemo(true); DeadLockDemo B = new DeadLockDemo(false); A.start(); B.start(); } } class DeadLockDemo extends Thread{ static Object o1 = new Object(); //保证多线程共享一个对象 static Object o2 = new Object(); boolean flag ; public DeadLockDemo(boolean flag){ this.flag = flag; } /** * 死锁经典案例分析 * 1.如果flag为true,线程A就会先得到o1对象锁,然后去尝试获取o2对象锁 * 2.如果线程A得不到o2对象锁,就会Blocked * 3.如果flag为false,线程B就会先得到o2对象锁,然后尝试获取o1对象锁 * 4.如果线程B得不到o1对象锁,就会Blocked */ @Override public void run() { if(flag){ synchronized (o1){ System.out.println(Thread.currentThread().getName()+"进入1"); synchronized (o2){ System.out.println(Thread.currentThread().getName()+"进入2"); } } } else{ synchronized (o2){ System.out.println(Thread.currentThread().getName()+"进入3"); synchronized (o1){ System.out.println(Thread.currentThread().getName()+"进入4"); } } } } }
-