1.进程与线程
1.1什么是进程
进程是程序的一次动态执行过程,它需要经历代码加载、代码执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具有分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快,所以使得所有程序都好像是同时执行一样。进程是系统进行资源分配和调度的独立单位,每一个进程都有它自己的内存空间和系统资源。
1.2什么是线程
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是在进程的基础上进行的进一步划分。所谓多线程是指一个进程内在执行过程中可以产生更多更小的程序单位,这些更小的程序单位成为线程,这些线程可以同时存在、同时运行,一个进程可能包含了多个同时执行的线程。线程也被称为轻量级进程。
1.3线程和进程的区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:只有一个线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也叫轻量级进程。
举一个简单的例子来说明线程和进程的区别:我们每个人使用的word文档就是一个进程,但是word文档除了写文档以外有其他很多的功能,比如纠错检查、自动保存等等,这些都可以看作是一个个的线程。如果我们把word关闭,那么这些功能都会消失,但是我们可以去掉纠错检查这个功能,这并不会影响到我们word程序(进程)的运行。
2.从 JVM 角度说进程和线程之间的关系(待补充)
3.并行与并发
- 并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行)。从物理学的角度来说,同一个时刻可能只有一个任务在执行,但是在一个时间段内是有多个任务在执行,由于CPU上下文切换的速度比较快,不同任务都能分配到对应的时间片,所以并发看起来也是在同时执行。
- 并行: 单位时间内,多个任务同时执行。从物理学的角度来说,同一个时刻多个任务都在同时执行。
4.为什么我们要使用多线程
在CPU核心为单核的时代,我们使用多线程能提高CPU的使用率。只有一个线程的时候,会出现CPU空闲,IO正在操作,或者IO空闲,CPU正在操作的情况,我们使用多线程就能提供CPU的使用率。在多核时代,CPU核心数更多啦,那我们就更需要多线程来充分利用CPU资源啦!而且在目前的互联网发展趋势下,高并发几乎是标配,所以多线程的使用是大势所趋。
5.Java线程有哪些状态
这个问题我们可以直接在Java源码里面找到答案
public enum State {
//新建
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
我们需要注意一点:Java线程状态和操作系统线程状态有一些差别,操作系统线程有五个状态。
1.初始状态(NEW)对应Java中的NEW;
2.可运行状态(READY)
3.运行状态(RUNNING) 这两个状态对应Java中的RUNNABLE,如果Java线程获得了时间片资源,那么它就处于运行状态,如果时间片资源用完了,那么就处于可运行状态
4.等待状态(WAITING)这对应了Java中的BLOCKED,WAITING,TIMED_WAITING
5.终止状态(TERMINATED)对应Java的TERMINATED
当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态
6.java实现多线程
6.1继承Thread,重写run方法
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName()+"->"+x);
}
}
public static void main(String[] args) {
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
myThread1.start();
myThread2.start();
}
}
6.2实现Runnable接口,重写run方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName()+"->"+x);
}
}
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread thread1=new Thread(myRunnable);
Thread thread2=new Thread(myRunnable);
thread1.start();
thread2.start();
}
}
6.3通过Callable和Future创建线程
public class MyCallable implements Callable<String> {
private int ticket=50;
@Override
public String call() throws Exception {
for (int x = 0; x < 200; x++) {
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "抢到了一张票");
ticket--;
}
}
return "票已经卖光";
}
public static void main(String[] args) throws Exception{
MyCallable myCallable1 = new MyCallable();
MyCallable myCallable2 = new MyCallable();
FutureTask<String> futureTask1 = new FutureTask<>(myCallable1);
FutureTask<String> futureTask2 = new FutureTask<>(myCallable2);
new Thread(futureTask1, "有返回值的线程").start();
new Thread(futureTask2, "有返回值的线程").start();
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
}
}
6.4创建三种线程的方式对比
1、采用实现Runnable、Callable接口的方式创建多线程时,
优点:线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
缺点:编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类的方式创建多线程时,
优点:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
缺点:线程类已经继承了Thread类,所以不能再继承其他父类。
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
6.5run和start的区别
run()
:仅仅是封装被线程执行的代码,直接调用是普通方法,无法调用线程start()
:首先启动了线程,然后再由jvm去调用该线程的run()方法。start()方法底层调用了start0()方法,而这个方法是本地方法,所以直接调用run()方法是无法调用多线程的,同时我们也可以知道java的多线程是由jvm调用c或者c++实现
7.Java线程常用API
7.1获取和设置线程名称
public class ThreadApiTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
MyThread myThread=new MyThread();
myThread.start();
myThread.setName("myThread");
System.out.println(myThread.getName());
}
}
简单说明:Java程序每次启动至少启动了两个线程,1个是main线程,1个是GC回收线程。
7.2判断线程是否启动
public class ThreadApiTest01 {
public static void main(String[] args) {
MyThread myThread=new MyThread();
System.out.println("线程开始之前"+myThread.isAlive());
myThread.start();
System.out.println("线程开始之后"+myThread.isAlive());
}
}
7.3线程的强制运行
在线程操作中,可以使用join()方法让一个线程强制运行。在线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
public class ThreadApiTest02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("main线程开始执行");
for (int i = 0; i < 50; i++) {
if (i > 10) {
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"在执行");
}
System.out.println("main线程结束执行");
}
}
7.4线程的休眠
public class ThreadApiTest03 {
public static void main(String[] args) {
System.out.println("main线程开始执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程结束执行");
}
}
7.5线程的等待与唤醒
wait()一定要使用sycronized进行同步,否则会报“java.lang.IllegalMonitorStateException”异常。这是因为wait方法会释放对象锁,而此时因为没有用sycronized同步,就没有锁,就会报异常。
public class WaitTest {
public static void main(String[] args) {
StringBuilder value = new StringBuilder("123");
MyThread myThread1 = new MyThread(value);
MyThread myThread2 = new MyThread(value);
MyThread myThread3 = new MyThread(value);
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread {
StringBuilder value;
public MyThread(StringBuilder value) {
this.value = value;
}
@Override
public void run() {
try {
synchronized (value) {
System.out.println(value);
if ((value.toString()).equals("123")) {
System.out.println(getName() + "开始等待;当前时间秒数:" + Calendar.getInstance().get(Calendar.SECOND));
value.wait(5000);// 注意这里不是说让value进行wait,而是让当前线程进行wait
System.out.println(getName()+"等待完毕");
} else {
value = value.append("2");
System.out.println("当前线程名:" + getName() + ";" + value);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MainTest {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
System.out.println("Main start " + LocalTime.now());
Thread thread = new Thread(new MyThread());
thread.start();
Thread thread2 = new Thread(new MyThread());
thread2.start();
System.out.println("Main sleep 5s " + LocalTime.now());
Thread.sleep(5000);
System.out.println("Main try to get lock... " + LocalTime.now());
synchronized(lock){
System.out.println("Main get lock and notifyAll " + LocalTime.now());
lock.notifyAll();
}
System.out.println("Main end");
}
public static class MyThread implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " run and try to get lock... " + LocalTime.now());
synchronized (lock){
System.out.println(threadName + " get lock and sleep 2s... " + LocalTime.now());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " wait " + LocalTime.now());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " get lock agein and sleep 2s " + LocalTime.now());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(threadName + " end " + LocalTime.now());
}
}
}
wait()和sleep的区别
1、sleep是线程中的方法,但是wait是Object中的方法。
2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。
3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。
4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。
7.6线程的中断
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。具体来说,当对一个线程,调用 interrupt() 时。
① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
public class InterruptDemo {
public static void main(String[] args) {
InterruptThread interruptThread=new InterruptThread();
Thread thread=new Thread(interruptThread);
thread.start();
thread.interrupt();
}
}
class InterruptThread implements Runnable{
@Override
public void run() {
System.out.println("1.进入run方法");
try {
Thread.sleep(2000);
System.out.println("2.已经完成休眠");
} catch (InterruptedException e) {
System.out.println("3.休眠被终止");
e.printStackTrace();
return;
}
System.out.println("4.run方法正常结束");
}
}
public class InterruptDemo01 {
public static void main(String[] args) {
InterruptThread01 interruptThread01=new InterruptThread01();
Thread thread=new Thread(interruptThread01);
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//设置线程中断标志位
thread.interrupt();
}
}
class InterruptThread01 implements Runnable{
@Override
public void run() {
//判断线程是否需要被中断
while (!Thread.interrupted()){
System.out.println("方法正常执行");
}
System.out.println("线程结束啦");
}
}
7.7后台线程
在Java程序中,只要前台有一个线程在运行,则整个Java进程都不会消失,所以此时可以设置一个后台线程,这样即使Java进程结束了,此后台线程依然会继续执行。要想实现这样的操作,直接使用setDaemon()方法即可。
在线程类MyDamonThread 中,尽管run()方法中是死循环的方式,但是程序依然可以执行完,因为方法中的死循环已经设置成后台运行了,通过让主线程休眠3秒我们可以看到控制台会打印守护线程一直在运行。
public class DaemonTest {
public static void main(String[] args) {
MyDamonThread myThread=new MyDamonThread();
Thread thread=new Thread(myThread);
thread.setDaemon(true);
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyDamonThread implements Runnable{
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"在运行");
}
}
}
7.8线程的优先级
优先级越高的线程越有可能竞争到CPU资源,但是不保证一定能竞争到CPU资源
public class PriorityDemo {
public static void main(String[] args) {
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
MyThread myThread3=new MyThread();
//10
myThread1.setPriority(Thread.MAX_PRIORITY);
//5
myThread3.setPriority(Thread.NORM_PRIORITY);
//1
myThread2.setPriority(Thread.MIN_PRIORITY);
myThread1.start();
myThread2.start();
myThread3.start();
//主线程的优先级是5
System.out.println(Thread.currentThread().getPriority());
}
}
7.9线程的礼让
在线程操作中,也可以使用yield()方法将一个线程的操作暂时让给其他线程执行。但是线程的礼让并不一定保证其他线程能执行,礼让的线程虽然让出了自己的CPU时间片资源,但是它还是有可能重新竞争到。
public class YieldDemo {
public static void main(String[] args) {
YieldThread yieldThread=new YieldThread();
Thread thread1=new Thread(yieldThread,"线程A");
Thread thread2=new Thread(yieldThread,"线程B");
thread1.start();
thread2.start();
}
}
class YieldThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <50 ; i++) {
System.out.println(Thread.currentThread().getName()+"运行-->"+i);
if(i==30){
System.out.println("线程礼让:");
Thread.currentThread().yield();
}
}
}
}
代码地址:https://github.com/w227895/thread.git
参考以下文章:
https://blog.csdn.net/kuangsonghan/article/details/80674777
https://www.cnblogs.com/songshu120/p/7966314.html
https://segmentfault.com/a/1190000014428190
https://www.jianshu.com/p/098b3f15a9e6
《java开发实战经典》