多线程基础总结
一、什么是进程,什么是线程?
1.1什么是进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
在windows系统中可以在任务管理器中查看window当前运行的进程,即.exe文件的运行。
1.2什么是线程?
线程是比进程更小的执行单位,一个进程在其执行的过程中可以产生许多的线程。同类的多个线程共享进程的堆和方法区资源,每个线程有单独有自己的程序计数器和虚拟机栈和本地方法栈。
1.3进程和线程的关系,区别及优缺点?
一个进程可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器、虚拟机栈和本地方法栈。
总结: 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
二、使用线程
实现线程的三种方式:
1. 继承Thread类;
2. 实现Runnable接口;
3. 实现Callable接口;
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动从而执行的。
2.1 继承Thread类
创建一个类继承Thread类:
需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
class MyThread extends Thread {
public void run() {
//编写程序,这段代码运行在分支线程中(分支栈)。
for (int i = 0; i < 100; i++) {
System.out.println("分支线程--->" + i);
}
}
}
/*
实现程序的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run()方法。
*/
public class ThreadTest01 {
public static void main(String[] args) {
//这是main方法,这里的代码属于主线程,在主栈中运行。
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
//start方法的作用时:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务只是开启一个新的栈空间,只要新的栈空间开出来,start()方法就就结束了。线程就启动成功了。
//启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
//run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main时平级的。
myThread.start();
//这里的代码运行在主线程中。
for (int i = 0; i < 100; i++) {
System.out.println("主线程--->" + i);
}
}
}
2.2 实现Runnable接口:
/*
实现程序的第二种方式:
编写类实现java.lang.Runnable接口。
*/
public class ThreadTest02 {
public static void main(String[] args) {
//创建一个可运行的对象。
//MyRunnable r = new MyRunnable();
//将可运行的对象封装成一个线程对象
//Thread t = new Thread(r);
Thread t = new Thread(new MyRunnable());//合并代码
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程--->" + i);
}
}
}
//这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程--->" + i);
}
}
}
2.3 实现Callable接口:
Callable 可以有返回值,返回值通过 FutureTask 进行封装。
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
2.4 实现接口 VS 继承 Thread
实现接口会更好一些,因为:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
三、线程的生命周期
线程状态变迁:
注:图中join()方法是Thread类中的方法,并不是Object类中的方法。
线程创建之后处于NEW(新建状态),调用start()方法后开始运行,线程这时候处于RUNNABLE(可运行状态)。可运行状态的线程获得CPU时间片之后就会处于RUNNING(运行状态)。
当线程执行wait()方法之后,线程就进入WAITING(等待状态)。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而TIME_WAITING(超时等待状态)相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)
方法或 wait(long millis)
方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞 状态)。线程在执行 Runnable 的run()
方法之后将会进入到 TERMINATED(终止状态)。
四、synchronized关键字
synchronized 关键字最主要的三种使用方式:
1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁
synchronized void method() {
//业务代码
}
2.修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。所以,如果一个线程 A 调用一个实例对象的非静态 synchronized
方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized
方法,是允许的,不会发生互斥现象,因为访问静态 synchronized
方法占用的锁是当前类的锁,而访问非静态 synchronized
方法占用的锁是当前实例对象锁。
synchronized void staic method() {
//业务代码
}
3.修饰代码块 :指定加锁对象,对给定对象/类加锁。synchronized(this|object)
表示进入同步代码库前要获得给定对象的锁。synchronized(类.class)
表示进入同步代码前要获得 当前 class 的锁
synchronized(this) {
//业务代码
}
总结:
synchronized
关键字加到static
静态方法和synchronized(class)
代码块上都是是给 Class 类上锁。synchronized
关键字加到实例方法上是给对象实例上锁。- 尽量不要使用
synchronized(String a)
因为 JVM 中,字符串常量池具有缓存功能!
五、线程死锁
线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
通过代码来描述:
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new MyThread1(o1, o2);
Thread t2 = new MyThread2(o1, o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
注:文章参考JavaGuide博客
地址:https://github.com/Snailclimb/JavaGuide