文章目录
- 一、进程与线程
- 二、线程的创建方式
- 1.通过实现Runnable接口
- 2.通过实现Callable接口
- 3.通过继承Thread类
- 4.线程池(后面会说到)
- 三、线程的优先级
- 四、线程的状态
- 1.新建(New)
- 2.可运行状态(Runable)
- 3.阻塞(Bloking)
- 4.等待(Wait)
- 5.计时等待(Timed Wait)
- 6.终止(Terminated)
- 五、基础线程机制
- 1.线程的休眠(sleep)
- 2.线程的插队(join)
- 3.线程的让出(yield)
- 4.线程的中断 (interrept)
- 5.守护线程 (Daemon Thread)
- 六、synchronized关键字(锁)
- 案例
一、进程与线程
- 进程:进程是程序的一次执行过程,是系统运行程序的基本单位
- 线程: 线程是执行进程中的一个任务的过程,是系统运行程序的最小单位
一个进程中可以包含多个进程
二、线程的创建方式
线程只能通过new Thread()
来创建,但是其执行逻辑共有四种
1.通过实现Runnable接口
- 通过Thread调用start()方法启动线程
public class Test11 {public static void main(String[] args) {ToRunnable toRunnable = new ToRunnable();Thread thread = new Thread(toRunnable);thread.start();}
}
class ToRunnable implements Runnable{@Overridepublic void run() {System.out.println("通过实现Runnable接口创建线程~~~");}
}
2.通过实现Callable接口
- Callable可以有返回值、抛出异常,返回值通过 FutureTask 进行封装。
package com.yk;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ToCallable callable = new ToCallable(1,10);//FutureTask封装了一个计算任务允许在不同的线程中异步执行这个任务FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask);thread.start();Integer sum = futureTask.get();System.out.println("1-10的累加和为"+sum);}
}
class ToCallable implements Callable<Integer> {private int begin,end;public ToCallable(int begin, int end) {this.end = end;this.begin = begin;}@Overridepublic Integer call() throws Exception {int rest = 0;for (int i = begin; i < end; i++) {rest+=i;}return rest;}
}
3.通过继承Thread类
public class Test12 {public static void main(String[] args) {ToTread thread = new ToTread();thread.start();}
}
class ToTread extends Thread{public void run(){System.out.println("继承Tread类~~~");}
}
4.线程池(后面会说到)
三、线程的优先级
在线程中通过setPriority(n)
方法设置优先级,范围是1-10,默认为5
操作系统会对优先级较高的线程进行更频繁的调度
public class Demo04 {public static void main(String[] args) {Thread thread1 = new Pro();Thread thread2 = new Pro2();thread1.setPriority(4);thread2.setPriority(5);thread1.start();thread2.start();}
}class Pro extends Thread {public void run() {for (int i = 0; i < 10; i++) {System.out.println("数字" + i);}}
}class Pro2 extends Thread {public void run(){for (char i = 'A'; i < 'Z'; i++) {System.out.println("字母"+i);}}
}
四、线程的状态
一个线程从创建开始到执行结束,共有6种状态:
1.新建(New)
未执行star()方法
2.可运行状态(Runable)
已经调用star()方法,可能正在运行,也可能在等待cpu分配资源
3.阻塞(Bloking)
运行时的线程在竞争同一把锁时,未争到的线程为阻塞状态
4.等待(Wait)
线程执行时调用.wait()
或者.join()
方法,让当前线程让出cpu资源,进入等待
5.计时等待(Timed Wait)
线程执行时调用.sleep(等待时间毫秒)
、.wait(等待时间毫秒)
或者.join(等待时间毫秒)
方法,使线程进入到计时等待
- 阻塞和等待的区别:
- 阻塞是属于被动,在等待获取到一把锁
- 等待是主动的,通过调用Thread.sleep() 和 Object.wait() 等方法进入
进入方法 | 退出方法 |
---|---|
Thread.sleep(等待时间)方法 | 等时间结束 |
设置了 时间 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 时间 参数的 Object.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
6.终止(Terminated)
线程执行任务(Run方法)
结束后终止,或是线程执行过程中抛出异常而终止
五、基础线程机制
1.线程的休眠(sleep)
线程中可以通过调用Thread.sleep(休眠时长)
,来使线程强制休眠(等待)指定的时间
public class Demo {public static void main(String[] args) {System.out.println("主线程开始~");Thread thread = new Thread(() -> {for (int i = 0; i < 10 ; i++) {System.out.println(i);}});thread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("主线程结束~");}
}
2.线程的插队(join)
join()
方法会使当前线程进入等待池,等待调用该方法的线程执行完才会被唤醒
public static void main(String[] args) throws InterruptedException {MyThread myThread = new MyThread();myThread.start();myThread.join();System.out.println("主线程会在子线程myThread执行完才会执行~");}
}
class MyThread extends Thread {public void run() {for (int i = 0; i < 10; i++) {System.out.println("数字" + i);}}
}
- join()方法的实现原理
join()
底层是利用wait()
方法实现,用synchronized修饰,当主线程执行join()
方法时 ,主线程先获得当前线程对象的锁,随后进入join
方法,调用当前对象的wait()
方法,使主线程进入等待,等到当前线程对象执行完毕之后,线程会自动调用自身的notifyAll()方法,唤醒所有处于等待状态的线程
源码:
public final synchronized void join(long millis)
throws InterruptedException {long base = System.currentTimeMillis();long now = 0;if (millis < 0) {throw new IllegalArgumentException("timeout value is negative");}if (millis == 0) {while (isAlive()) {wait(0);}} else {while (isAlive()) {long delay = millis - now;if (delay <= 0) {break;}wait(delay);now = System.currentTimeMillis() - base;}}
}
3.线程的让出(yield)
- 分时调度模型:所有线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU时间
- 抢占式调度模型:优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,就随机选择一个线程,使其占用CPU(JVM虚拟机采用的是抢占式调度模型)
eg:线程t2在执行过程中调用yield()
让出Cpu,使t1的执行概率变高
public class Demo02 {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}},"数字");Thread t2 = new Thread(() -> {for (char i = 'A'; i < 'Z'; i++) {System.out.println(Thread.currentThread().getName()+":"+i);Thread.yield(); //让当前线程让出cpu}},"字母");t1.start();t2.start();}
}
4.线程的中断 (interrept)
一个线程会在执行完毕后自动结束,但如果在运行的过程中发生了异常就会使线程中断
- 我们可以通过调用
interrup()
方法来中断线程,如果该线程处于阻塞、等待(wait()/join()/sleep()
)状态,就会抛出异常
InterruptedException
使线程提前结束
在这个案例中,由于t1线程调用了join()
方法而抛出了异常,导致l线程t1的中断
public class Demo02 {public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}},"数字");Thread t2 = new Thread(() -> {for (char i = 'A'; i < 'Z'; i++) {System.out.println(Thread.currentThread().getName()+":"+i);Thread.yield(); //让当前线程让出cpu}},"字母");try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}t1.start();t2.start();}
}
- 在线程执行过程中我们还可以通过**
isinterrupted()
**方法判断当先线程是否中断
public class Test3 {public static void main(String[] args) throws InterruptedException {System.out.println("主线程:开始执行");// main主线程创建子线程MyThreadMyThread t = new MyThread();t.start();// 主线程休眠1000毫秒Thread.sleep(1000);// 结束休眠后t.interrupt(); // 中断t线程t.join(); // 等待t线程结束System.out.println("主线程:结束执行");}
}class MyThread extends Thread {public void run() {System.out.println("MyThread线程:开始执行");// MyThread线程创建子线程HelloThreadHelloThread hello = new HelloThread();hello.start(); // 启动HelloThread线程try {hello.join(); // 等待hello线程结束} catch (InterruptedException e) {System.out.println("MyThread线程:结束执行,interrupted!");}// MyThead线程结束后,中断子线程HelloThreadhello.interrupt();}
}class HelloThread extends Thread {public void run() {System.out.println("Hello线程:开始执行");int n = 0;// 检查当前线程是否已经中断while (!isInterrupted()) {n++;System.out.println(n + " hello!");}System.out.println("Hello线程:结束执行!");}
}
5.守护线程 (Daemon Thread)
-
什么是守护线程?
- 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
- 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程
-
设置守护线程(setDaemon)
- 在线程调用star()方法前调用setDaemon(true),将当前线程标记为守护线程
Thread tProject = new Thread(() -> {while (true) {System.out.println("守护线程");}
});
tProject.setDaemon(true);
tProject.start();
六、synchronized关键字(锁)
在多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待:
synchronized关键字可以保证代码块在任意时刻最多只有一个线程可以执行
可以将synchronized加在方法本身,或者单独为一个代码块
在使用synchronized关键字的时候,无论是否有异常,都会在synchronized结束后将锁释放
代码块形式:手动指定锁的对象,可以是this,也可以是自定义的锁
//自定义锁
class Count {public static int count = 0;//创建Object对象,实现同步锁public final static Object LOCK = new Object();
}class DecThread extends Thread {public void run() {for (int i = 0; i < 1000; i++) {synchronized (Count.LOCK) {Counter.count -= 1;}}}
synchronized修饰普通方法时,锁的对象默认为this
//在方法声明上使用synchronized关键字//对整个方法体进行加锁,使用this对象作为锁public synchronized void add() {for (int i = 0; i < 1000; i++) {Counter.count += 1;}}
-
不需要synchronized的操作
- 在我们使用原子操作实现多线程操作时,可以不使用synchronized关键字
JVM规范定义了几种原子操作:
- 基本类型(long 和double 除外)赋值,例如:int n= m
- long和double是64位数据,JVM没有明确规定64位赋值操作是不是一个原子操作,不过在x64平台的JVM是把1ong和double的赋值作为原子操作实的。
- 引用类型赋值,例如:List list = anotherList
案例
- 使用多线程打印字母加数字
数字类
public class Numbers implements Runnable {private final Object lock;public Numbers(Object lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {for (int i = 1; i < 53; i++) {//判断当为奇数时加空格if (i % 2 == 1) {System.out.print(" ");}System.out.print(i);//如果为偶数则等待一下if (i % 2 == 0) {//唤醒字母线程lock.notify();try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
字母类
public class Character implements Runnable {private final Object lock;public Character(Object lock) {this.lock = lock;}@Overridepublic void run() {synchronized (lock) {for (char i = 'A'; i <= 'Z'; i++) {System.out.print(i);//唤醒数字线程lock.notify();//if (i < 'Z') {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
测试:
public class Test {public static void main(String[] args) {final Object LOCK = new Object();Thread t1 = new Thread(new Numbers(LOCK));Thread t2 = new Thread(new Character(LOCK));t1.start();t2.start();}
}
结果:
12A 34B 56C 78D 910E 1112F 1314G 1516H 1718I 1920J 2122K 2324L 2526M 2728N 2930O 3132P 3334Q 3536R 3738S 3940T 4142U 4344V 4546W 4748X 4950Y 5152Z