目录
1、基本概念:程序、进程、线程
1.1程序(program)
是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
1.2进程(process)
进程是程序执行的一次执行过程,或是正在允运行的一个程序。是一个动态的过程:有它自身的产生、存在、消亡的过程。——生命周期
1.3线程(thread)
进程可进一步细分为线程,是一个程序内部的一条执行路径
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),多个线程,共享同一个进程中的结构(方法区、堆) 线程切换的开销小
一个进程的多个线程共享相同的内存单元/内存地址空间->他们从同一对堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患
一个Java应用程序java.exe至少有三个进程:main(主线程),gc(垃圾回收线程),异常处理线程。如果发生异常会影响主线程
1.4并行与并发
并行
多个CPU同时执行多任务.比如:多个人同时做不同事
并发
一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做一件事
1.5使用多线程的优点
①提高应用程序的响应。对图形化界面更有意义,可增强用户体验
②提高计算机系统CPU的利用率
③改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
1.6多线程的应用场景
①程序要同时执行两个或多个任务
②程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等
③需要一些后台运行的程序时
2、线程的创建和应用(重点)
2.1创建多线程的方式一:java.lang.Thread
2.1.1通过继承Thread创建线程
步骤:
/**
* 多线程的创建
* 方式一:继承Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写run()方法
* 3.创建子类对象
* 4.通过此对象带调用start()
*/
public class ThreadTest{
//例一遍历一千以内所有偶数
public static void main(String[] args) {
//3.创建子类对象
MyThread t1 = new MyThread();
//4.通过此对象带调用start()
t1.start();
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println("-----主线程----"+i);
}
}
}
}
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
//2.重写run()方法
@Override
public void run() {
//将创建的线程要做的操作声明在run方法中
for (int i = 0; i < 1000; i++) {
if ((i % 2) == 0 ) {
System.out.println(i);
}
}
}
}
结果说明:此代码中存在两个线程,主线程和我们自己创建的线程,这两个线程时同时执行的,所有输出结果没有先后
截取部分输出:
...
main:-----主线程----0
main:-----主线程----2
main:-----主线程----4
main:-----主线程----6
Thread-0:0
Thread-0:2
Thread-0:4
Thread-0:6
Thread-0:8
Thread-0:10
Thread-0:12
Thread-0:14
Thread-0:16
main:-----主线程----8
Thread-0:18
Thread-0:20
Thread-0:22
Thread-0:24
main:-----主线程----10
main:-----主线程----12
main:-----主线程----14
main:-----主线程----16
...
Process finished with exit code 0
2.1.2创建过程中的两个问题
2.1.2.1问题一:不能直接调用run()方法来执行线程
1、start()方法的作用
①启动当前线程
②调用当前线程的run()方法
2、使用start()和直接调用run()方法的区别
上述代码直接调用run()方法的输出
...
main:982
main:984
main:986
main:988
main:990
main:992
main:994
main:996
main:998
main:-----主线程----0
main:-----主线程----2
main:-----主线程----4
main:-----主线程----6
main:-----主线程----8
main:-----主线程----10
main:-----主线程----12
main:-----主线程----14
main:-----主线程----16
main:-----主线程----18
main:-----主线程----20
...
Process finished with exit code 0
无论执行多少次,两个循环的执行结果输出都是固定的先后顺序,且两个循环都属于main线程。这说明,直接调用run方法,并没有成功启动Thread线程,这里只相当于是一个对象去调用了一个方法。
2.1.2.2 问题二:不能对同一线程执行两次start()
示例代码
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
t1.start();
}
运行结果,异常:IllegalThreadStateException
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at com.guigu.java20210927Thread.ThreadTest.main(ThreadTest.java:19)
Thread-0:0
Thread-0:2
解决:创建线程的新对象,并start()
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
MyThread t2 = new MyThread();
t2.start();
}
输出
Thread-1:62
Thread-1:64
Thread-0:90
Thread-1:66
Thread-1:68
Thread-1:70
Thread-1:72
Thread-1:74
Thread-1:76
Thread-1:78
Thread-0:92
Thread-0:94
Thread-0:96
Thread-1:80
Thread-1:82
Thread-1:84
Thread-1:86
2.1.3 创建Thread的匿名子类
例:创建两个线程分别求100以内的奇数和偶数
public class ThreadTest {
public static void main(String[] args) {
//方法一、创建两个Thread的子类
// Thread1 t1 = new Thread1();
// Thread2 t2 = new Thread2();
//
// t1.start();
// t2.start();
//方法二、创建Thread类的匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
continue;
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) != 0 ) {
continue;
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
}
}
class Thread1 extends Thread
{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class Thread2 extends Thread
{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
continue;
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
2.1.4Thread的常用方法
------void start():启动线程,并执行对应的run方法
------run():线程在被调度时执行的操作,通常需要重写
------String getName():返回该线程的名称
------void setName(String name):设置该线程名称
------static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable
setName()、getName、start、run、currentThread()
public static void main(String[] args) {
//创建Thread类的匿名子类
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
continue;
}
//设置线程名称
setName("匿名线程一");
//获取线程名称
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
}
输出:
补充:通过构造器命名线程
public class ThreadMethodTest {
public static void main(String[] args) {
//创建对象时调用带参构造器给线程命名
Thread3 t3 = new Thread3("构造器命名的线程");
t3.start();
//给主线程命名
Thread.currentThread().setName("主线程");
}
}
class Thread3 extends Thread{
Thread3 (){
}
//建造带参构造器
Thread3 (String name){
super(name);
}
//重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
continue;
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
输出
yield():释放当前CPU的执行权
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
if(i%20 == 0){
//当i%20=0时释放当前线程占用的CPU
this.yield();
//另一种写法:Thread.currentThread().yield();
}
}
}
说明:虽然当前线程使用yield(释放了当前占用的CPU但,该线程仍可能立刻再次被分配到CPU
join():释放当前CPU的执行权
在线程a中调用线程b的join方法,此时线程a进入阻塞状态。直到线程b执行完以后,线程a才结束阻塞状态,等待CPU分配资源继续执行
public class ThreadMethodTest {
public static void main(String[] args) {
Thread3 t3 = new Thread3("构造器命名的线程");
t3.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i == 20) {
try {
//此处在主线程中调用t3线程的join方法,则开始执行t3线程直到该线程结束继续执行主线程
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Thread3 extends Thread{
Thread3 (){
}
Thread3 (String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
if(i%20 == 0){
this.yield();
//Thread.currentThread().yield();
}
}
}
}
输出:
开始执行t3
t3线程执行完毕继续执行主线程
sleep(long millitime):当前线程阻塞指定的时间
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
try {
//一旦线程执行到此处,当前线程阻塞1秒
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
if(i%20 == 0){
this.yield();
//Thread.currentThread().yield();
}
}
}
isAlive():判断当前线程是否存活,线程结束返回false
public class ThreadMethodTest {
public static void main(String[] args) {
Thread3 t3 = new Thread3("构造器命名的线程");
t3.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
if (i == 20) {
try {
t3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//此处t3线程已经执行结束,应返回false
System.out.println(t3.isAlive());
}
}
class Thread3 extends Thread{
Thread3 (){
}
Thread3 (String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
try {
//一旦线程执行到此处,当前线程阻塞1秒
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
if(i%20 == 0){
//此处当前线程正在执行,每一次的返回都是true
System.out.println(this.isAlive());
this.yield();
//Thread.currentThread().yield();
}
}
}
}
输出1:执行过程中,返回true
输出情况2:当前线程执行完成,返回false
2.2 线程的调度
2.2.1 调度的策略
- 时间片策略:
- 抢占式:优先级高的线程抢占CPU
2.2.1 Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
2.2.1 Java线程的优先级
三个常量 MAX_PRIORITY:10(最大优先级)、MIN_PRIORITY:1(最小优先级)、NORM_PRIORITY(默认优先级):5
查看优先级和设置优先级
- getPriority():返回线程优先级
public class ThreadMethodTest {
public static void main(String[] args) {
Thread3 t3 = new Thread3("构造器命名的线程");
Thread4 t4 = new Thread4();
t3.start();
t4.start();
}
}
class Thread4 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取当前线程的优先级
System.out.println("优先级:"+getPriority());
}
}
}
class Thread3 extends Thread{
Thread3 (){
}
Thread3 (String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
//获取当前线程的优先级
System.out.println(getName()+":"+i+"优先级:"+getPriority());
}
if(i%20 == 0){
this.yield();
}
}
}
}
输出1:
...
构造器命名的线程:58优先级:5
构造器命名的线程:60优先级:5
优先级:5
优先级:5
优先级:5
构造器命名的线程:62优先级:5
构造器命名的线程:64优先级:5
构造器命名的线程:66优先级:5
构造器命名的线程:68优先级:5
构造器命名的线程:70优先级:5
构造器命名的线程:72优先级:5
优先级:5
优先级:5
构造器命名的线程:74优先级:5
构造器命名的线程:76优先级:5
...
主线程的优先级
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
}
5
Process finished with exit code 0
- setPriority(int newPriority):改变线程优先级
Thread4 t4 = new Thread4();
System.out.println(t3.getPriority());
t3.setPriority(Thread.MAX_PRIORITY);
System.out.println(t3.getPriority());
输出
5
10
Process finished with exit code 0
- 不一定优先级高的线程一定比优先级低的线程先执行
public static void main(String[] args) {
Thread3 t3 = new Thread3("构造器命名的线程");
//修改线程优先级
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+"优先级"+Thread.currentThread().getPriority());
}
}
}
输出:
...
主线程:优先级5
主线程:优先级5
主线程:优先级5
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
主线程:优先级5
主线程:优先级5
主线程:优先级5
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
构造器命名的线程:优先级:10
...
Process finished with exit code 0
说明:线程创建时继承父线程的优先级
低优先级只是获得调度的概率降低,并非一定实在高优先级线程之后才被执行
多窗口售票的例子——继承Thread类实现
public class TicketWidowTest {
public static void main(String[] args) {
Window w1 = new Window("窗口1");
Window w2 = new Window("窗口2");
Window w3 = new Window("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int ticket = 100;
//构造器给线程命名
Window(){}
Window(String name){
super(name);
}
@Override
public void run() {
/* for (;ticket>0;){
ticket--;
System.out.println(getName()+"窗口售出一张票,当前余票"+(ticket));
}*/
while (true){
if (ticket > 0) {
ticket--;
System.out.println(getName()+"窗口售出一张票,当前余票"+(ticket));
}else break;
}
}
}
输出:
...
窗口2窗口售出一张票,当前余票77
窗口1窗口售出一张票,当前余票57
窗口3窗口售出一张票,当前余票68
窗口3窗口售出一张票,当前余票54
窗口3窗口售出一张票,当前余票53
窗口1窗口售出一张票,当前余票55
窗口1窗口售出一张票,当前余票51
窗口1窗口售出一张票,当前余票50
窗口1窗口售出一张票,当前余票49
窗口1窗口售出一张票,当前余票48
窗口2窗口售出一张票,当前余票56
窗口1窗口售出一张票,当前余票47
窗口3窗口售出一张票,当前余票52
窗口3窗口售出一张票,当前余票44
窗口3窗口售出一张票,当前余票43
...
Process finished with exit code 0
2.3创建多线程的方式二:实现Runnable接口
上部分多窗口售票的代码使用static修饰 ticket 属性,以此来保证三个对象共用一个属性,还有一种创建多线程的方式可以替代static达到同样的目的。
- 1、创建一个实现了Runnable接口的类
- 2、实现类去实现Runnable中的抽象方法 run()
- 3、创建实现类的对象
- 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 5、通过Thread类的对象调用start方法
public class ThreadTest1 {
public static void main(String[] args) {
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
}
}
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if ((i % 2) == 0 ) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
多窗口售票的例子——实现Runnable接口实现
/**
* 通过实现Runnable接口实现多窗口售票
* @author fuyaling
* @date 2021-08-28 - 20:25
*/
public class TicketWindowTest1 {
public static void main(String[] args) {
Window1 window1 = new Window1();
Thread t1 = new Thread(window1);
Thread t2 = new Thread(window1);
Thread t3 = new Thread(window1);
t1.setName("1号窗口");
t2.setName("2号窗口");
t3.setName("3号窗口");
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName()+"窗口售出一张票,当前余票"+(ticket));
}else break;
}
}
}
说明:此时的 ticket 变量没有使用static修饰,但由于从始至终都只建了一个关于Window1的对象,所以自然而然,对象t1,t2,t3都共享此变量
匿名内部类的创建方式
Runnable target = new Runnable(){
@Override
public void run(){
...//具体的执行内容
}
}
//将任务对象交给Thread处理
Threa t = new Thread(target);
//启动i线程
t.start();
将上述代码进一步简化
//将任务对象交给Thread处理
new Thread(new Runnable(){
@Override
public void run(){
for(;;){...};
}
}).start();
Runnable是函数式接口,所以再结合lambada表达式进一步简化
//将任务对象交给Thread处理
new Thread(()->{
...;//具体的执行内容
}).start();
Runnable的缺点
- 如果线程由执行结果无法直接返回
2.4两种创建方式的对比
2.4.1开发中优先选择实现接口Runnable的方式去创建线程
原因:
- ①实现的方式没有类的单继承性的局限性
- ②实现的方式更适合来处理多个线程又共享数据的情况。
2.4.2联系
public class Thread implements Runnable
2.4.3 相同点
两种方式都需要重写run方法,将线程执行的逻辑声明在run()方法中
2.5创建多线程的方式三:实现Callable接口 JDK5新增
上述线程创建方式的不足:
- 重写的run方法均无法返回结果
- 不适合需要返回线程执行结果的业务场景
创建多线程的三、四种方式:Callable&FutureTask,线程池