原文博客地址:pjmike的博客java
下面比较简单介绍下进程与线程的概念:git
关于进程的定义,其实有不少:github
我的以为比较好的定义是:算法
进程是具备必定独立功能的程序关于某个数据集合上的一次运行过程编程
说白了,进程就是CPU执行的一次任务,在单个CPU中一次只能运行一次任务,即CPU老是运行一个进程,其余进程处于非运行状态。可是如今的操做系统都是多核CPU,因此能够同时运行多个进程,执行多个任务。bash
线程其实是一个进程中的"子任务",在一个进程中能够建立多个线程,打个比方,打开QQ就是执行了一个进程,而在QQ里与他人聊天,同时下载文件等操做就是线程。网络
多线程是指操做系统在单个进程内支持多个并发执行路径的能力。每一个进程中只有一个线程在执行的传统方法称为单线程方法。可是单线程存在不少弊端,它并不能充分利用CPU资源,并且单线程在应对复杂业务时响应时间也是较差的,因此咱们须要使用多线程来帮助咱们。使用多线程有以下好处(缘由):多线程
那咱们为何去使用 多线程而不是使用多进程去进行并发程序的设计,是由于线程间的切换和调度的成本远小于 进程并发
首先有进程,其次才是线程,其实线程的生命周期及各类状态转换和进程相似,下面看一张 进程的状态转换图(图片摘自网络):ide
进程通常有三种基本的状态:
进程被建立后,加入就绪队列等待被调度执行,CPU调度器根据必定的调度算法调度就绪队列中的进程在处理机上执行。
上面简述了进程的基本状态及变化过程,线程也是相似的:
该图表示一个线程的生命周期状态流转图,很清楚的描绘了一个线程从建立到终止的一个过程。线程的全部状态在 Thread类中的State枚举定义,以下:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
复制代码
注意:从New 状态出发后,线程不能再回到 New状态,同理,处于 Terminated 的线程也不能回到 Running状态
下面分别对线程的各类状态进行相关说明
新建线程有两种方式:
Thread类是在java.lang包中定义的,原本咱们能够直接继承Thread,重载 run() 方法来自定义线程,可是Java 是单继承的,若是一个类已经继承了其余类,就没法再继承Thread,因此咱们这时就能够实现 Runnable接口来实现
// 1. 继承Thread类
public class Thread1 extends Thread{
@Override
public void run() {
System.out.println("hello world");
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
//调用start()运行线程,执行内部的run()方法
thread1.start();
}
}
//2. 实现Runnable接口
public class Thread2 implements Runnable{
@Override
public void run() {
System.out.println("hello world");
}
public static void main(String[] args) {
Thread thread = new Thread(new Thread2());
thread.start();
}
}
复制代码
Thread有一个重要的构造方法:
public Thread(Runnable target) 复制代码
它传入一个Runnable 接口的实例,在start()方法调用时,新的线程就会执行 Runnable.run()方法,实际上,默认的Thread.run()就是这么作的:
public void run() {
if (target != null) {
target.run();
}
}
复制代码
默认的Thread.run()就是直接调用 内部的 Runnable接口
通常来讲,线程在执行完毕后就会结束,无须手动关闭,可是仍是有些后台常驻线程可能不会自动关闭。
Thread提供了一个 stop()方法,该方法能够当即将一个线程终止,可是目前stop()已经被废弃,不推荐使用,缘由是 stop()方法太过于简单粗暴,强行把执行到一半的线程终止,可能会引发一些数据不一致的问题。
线程中断是一种重要的线程协做机制,它并不会使线程当即退出,而是给线程发送一个通知,告知目标线程,有人但愿你退出。至于目标线程接到通知后如何处理,则彻底由目标线程自行决定。
与线程中断有关的,有三个方法:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
复制代码
Thread.interrupt()
方法是一个实例方法,通知目标线程中断,设置中断标志位,该标志位代表当前线程已经被中断了Thread.isInterrupted()
方法也是实例方法,判断当前线程是否有被中断(经过检查中断标志位)Thread.interrupted
也是用来判断当前线程的中断状态的,但同时会清楚当前线程的中断标志位状态举例说明:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
//经过中断标志位判断是否被中断
if (Thread.currentThread().isInterrupted()) {
//中断逻辑处理
System.out.println("Interrupted!");
// break退出循环
break;
}
}
});
//开启线程
thread.start();
Thread.sleep(2000);
//设置中断标志位
thread.interrupt();
}
复制代码
Thread有个 sleep
方法,它会让当前线程休眠若干时间,它会抛出一个 InterruptedException
中断异常。InterruptedException
不是运行时异常,也就是说程序必须捕获而且处理它,当线程在 sleep()休眠时,若是被中断,这个异常就产生了。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interrupted When Sleep");
//中断异常会清楚中断标记,从新设置中断状态
Thread.currentThread().interrupt();
}
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted!");
break;
}
}
}
};
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
//output:
Interrupted When Sleep
Interrupted!
复制代码
Thread.sleep() 方法因为中断而抛出异常,此时,它会清楚中断标记,若是不加处理,那么在下一次循环开始时,就没法捕获这个中断,故在异常处理中,再次设置中断标志位
为了支持多线程之间的协做,JDK 提供了两个很是重要的接口线程等待 wait() 方法和通知 notify() 方法,这两个方法定义在 Object类中。
public final void wait(long timeout) throws InterruptedException;
public final void notify();
复制代码
当在一个对象实例上调用了object.wait() 方法,那么它就会进入object对象的等待队列进行等待,这个等待队列中,可能会有多个线程,由于系统运行多个线程同时等待某一个对象。当object.notify()被调用时,它就会从 这个等待队列中,随机选择一个线程,并将其唤醒。
除了notify()方法外,Object 对象还有一个相似的 notifyAll()方法 ,它和notify()的功能同样,不一样的是,它会唤醒这个等待队列中全部等待的线程。
下面看一个代码示例:
public class Example {
public static void main(String[] args) {
final Object object = new Object();
new Thread(() -> {
System.out.println("thread A is waiting to get lock");
synchronized (object) {
System.out.println("thread A already get lock");
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
object.wait();
System.out.println("thread A wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
System.out.println("thread B is waiting to get lock");
synchronized (object) {
System.out.println("thread B already get lock");
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("thread B do wait method");
object.notify();
System.out.println("thread B do notify method");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
复制代码
执行结果:
thread A is waiting to get lock
thread A already get lock
thread B is waiting to get lock
thread A do wait method
thread B already get lock
thread B do wait method
thread B do notify method
thread A wait end
复制代码
上述开启了两个线程A和B,A执行 wait()方法前,A先申请 object 的对象锁,在执行 object.wait()时,持有object的锁,执行后,A会进入等待,并释放 object的锁。
B在执行notify()以前也会先得到 object的对象锁,执行notify()以后,释放object的锁,而后A从新得到锁后继续执行
join方法使当前线程等待调用 join方法的线程结束后才能继续往下执行,下面是代码示例:
public class ThreadExample {
public volatile static int i = 0;
public static class Thread_ extends Thread{
@Override
public void run() {
for (i = 0; i < 1000000; i++) {
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread_ A = new Thread_();
A.start();
//让当前的主线程等待thread_线程执行完毕后才往下执行
A.join();
System.out.println(i);
}
}
复制代码
join()的本质其实是让调用线程wait()在当前线程对象实例上,当线程执行完毕后,被等待线程退出前调用 notifyAll()通知全部的等待线程继续执行
以上面的例子说明,线程A调用join()使main线程 wait()在A对象实例上,等线程A执行完毕,线程A调用notifyAll(),main线程等到通知继续往下执行。
而Thread.yield()方法的定义以下:
public static native void yield();
复制代码
它是静态方法,一旦执行,它会使当前调用该方法的线程让出CPU,可是让出CPU不表明 当前线程不执行,当前线程让出CPU后,还会进行CPU资源的争夺,可是是否可以被分配到,就不必定了。
Thread.yield()的调用就好像在说:
我已经完成了一些最重要的工做了,我应该是能够休息一下了,能够给其余线程一些工做机会了
守护线程是一种特殊的下线程,它是系统的守护者,在后台默默地完成一些系统性的服务,好比垃圾回收线程,JIT线程就能够理解为守护线程,与之对应的是用户线程,用户线程能够理解为系统的工做线程,它会完成这个程序应该要完成的业务操做。当一个Java应用内,只有守护线程时,Java虚拟机就会天然退出。
代码示例以下:
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
while (true) {
System.out.println("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//将线程t设置为守护线程
t.setDaemon(true);
t.start();
//主线程休眠10s
Thread.sleep(10000);
}
}
复制代码
输出结果:
I am alive
I am alive
I am alive
I am alive
I am alive
I am alive
I am alive
I am alive
I am alive
I am alive
复制代码
上面的例子,将t设置为守护线程,系统中只有主线程 main 为用户线程,在 main休眠10s后,守护线程退出,整个线程也退出。
Java 中的线程能够有本身的优先级,优先级高的线程在竞争资源时会更有优点,更可能抢占资源。线程的优先级调度和操做系统密切相关,固然高优先级可能也会有抢占失败的时候,可是大部分状况下,高优先级比低优先级对于抢占资源来讲更有优点。
代码示例以下:
public class PriorityDemo {
private static int count = 0;
public static void main(String[] args) {
Thread A = new Thread() {
@Override
public void run() {
while (true) {
synchronized (PriorityDemo.class) {
count++;
if (count > 100000) {
System.out.println("HighPriority is complete");
break;
}
}
}
}
};
Thread B = new Thread() {
@Override
public void run() {
while (true) {
synchronized (PriorityDemo.class) {
count++;
if (count > 100000) {
System.out.println("LowPriority is complete");
break;
}
}
}
}
};
//设置线程A为高优先级
A.setPriority(Thread.MAX_PRIORITY);
//设置线程B为低优先级
B.setPriority(Thread.MIN_PRIORITY);
//启动线程B
B.start();
//启动线程A
A.start();
}
}
复制代码
运行结果:
HighPriority is complete
LowPriority is complete
复制代码
由上能够初步得出,高优先级的线程在大部分状况下会首先完成任务。
上面简单总结了一部分Java并发编程所涉及到知识点,算是入门Java并发的一个开端。