Java 多线程

2024-08-06 14:33:42 浏览数 (2)

多线程

基本概念:

程序:

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合 即指一 段静态的代码,静态对象。

进程:

进程(process)是程序的一次执行过程,或是正在运行的一个程序 是一个动态的过程 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程:

线程(thread) 进程可进一步细化为线程,是一个程序内部的一条执行路径。 若一个进程同一时间 并行 执行多个线程,就是支持多线程的 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

  • 一个进程中的多个线程共享相同的内存单元/内存地址空间
  • 它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效 但多个线程操作共享的系统资源可能就会带来安全的隐患
单核CPU和多核CPU的理解:

单核CPU,其实是一种假的多线程,因为在一个时间单元内,单核同时也只能执行一个线程的任务 例如:

  • 有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过:
  • 那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”晾着他,等他想通了,准备好了钱,再去收费
  • 收费的车就相当于一个个的线程任务,收费员收费执行线程 收完费用执行结束 对于大的任务, 也可以进行挂起,或一点点的处理...
  • 因为CPU时 间单元特别短,因此感觉不出来 一个个的在工作运行,感觉是一起执行的!

如果是多核的话,才能更好的发挥多线程的效率。 CPU一个核就相当于一个工作人员... 现在的服务器都是多核的) 一个Java应用程序java.exe,其实至少有三个线程:

  • main()主线程,gc() 垃圾回收线程,异常处理线程 当然如果发生异常,会影响主线程
并行 与 并发:

并行:

  • 多个CPU同时执行多个任务: 比如:多个人同时做不同的事

并发:

  • 一个CPU(采用时间片)同时执行多个任务: 比如:秒杀、多个人做同一件事

并行与并发: 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。 并发的关键是你有处理多个任务的能力,不一定要同时 并行的关键是你有同时处理多个任务的能力 它们最关键的点就是:是否是『同时』

多线程优点:

  1. 提高应用程序的响应。对图形化界面更有意义 可增强用户体验
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

但:多线程并不一定提高效率: 注意是不一定!

  • 以单核CPU为例,使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短
  • 多线程还要计算,cpu 分配不同时间片来执行时,切换的时间...

何时需要多线程:

程序需要同时执行两个或多个任务

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等; 用户在输入,此时压力并不是特别大,后台还可以做其它事情:校验输入数据 文件上传时间较长可以开个线程缓慢上传… 让用户不用一直等待
  • 需要一些后台运行的程序时… 可以提高程序响应速度..

线程的创建 和 使用:

JAVA实现多线程方式: 4种 JDK5.0之后新增两种:

线程的创建和启动:

Java语言的JVM允许程序运行多个线程,它通过 Java.lang.Thread 类来体现

Thread类:

Java中每个线程都是通过某个特定Thread对象的run()方法来完成操作的 把run()方法的主体称为线程体 通过该Thread对象的start()方法来启动这个线程,而非直接调用run() 构造器:

  • Thread():创建新的Thread对象
  • Thread(String threadname):创建线程并指定线程实例名
  • Thread(Runnable target, String name):创建新的Thread对象
  • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口,中的 run() 方法;
常用方法()

其实 Thread 类本身也是实现了 Runnable 接口 因此 Therad 不是抽象类(类 重写了Runable接口 run(); 方法 Java 通过 Thread类将线程,所必需的功能都封装了起来 Thread类: 支持多线程类提供了大量的方法,控制操作线程

代码语言:javascript复制
void		Thread();					//分配新的 Thread 对象;		
void		Thread(Runnable);			//分配新的 Thread 对象 参数 Runnable 实现类 对象 
void		Thread(Runnable,String);	//分配新的 Thread 对象 参数1 Runnable 实现类 对象 ,参数2 线程名....

void		run();						//通常需要子类重写,将线程执行的操作声明在方法中,对象直接调用单纯执行run()方法; 不是以线程方式;
void		start();					//使线程开始执行 启动当前线程;java虚拟机调用 run() 方法; 
										//注意:一个对象不可以调两次start();方法; 启动两次??  (底层固定好了!)
										
void		sleep(long);				//Theread静态方法(); 在指定毫秒数内让当前正在执行线程,休眠(暂停);需要处理异常!!

String		getName();					//返回线程 名称;
void		setName(String);			//修改线程名; 注意:要在 start(); 之前改名;
int			getPriority();				//返回线程 优先级; 0-10 默认5; 越高 CPU 获取几率越大,也只是概率~ 	
void		setPriority(int);			//更改线程 优先级
static	    Thread.currentTherad();		//Theread类静态方法 返回当前正在执行线程的线程对象Thread; 
										//注意: main();是java主线程,方法内调用 指main();中的线程;
											
boolean		isAlive();					//检查线程是否属于 运行状态;		需要处理异常!!
void		join();						//等待终止线程; 使当前线程运行完毕!
										//线程A调用,此时A进入阻塞状态,直到B线程完全执行完,A才结束阻塞;
										
void		interrupt();				//终止线程;
void		yieId();					//礼让: 暂停当前线程对象允许其他线程获得CPU资源; 
										(停一下则立刻就绪!)该线程仍处于就绪状态,系统随机选择:就绪线程运行; 有可能该线程继续获取到资源!
										
void		stop();						//已过时,当执行此方法,强制结束当前线程; 生命周期结束;
void		wait();						//线程 run()中调用,当前线程就进入阻塞状态,并开启锁,其它线程可访问数据;
void		notify()/notfyAll();		//线程 run()中调用,notify();随机取消一个线程设置wait();  notfyAll();把所有的wait();阻塞线程都取消阻塞;

wait() notfyAll() notfy() 三个方法是在 Java.lang.Object类中 三个方法的调用者必须是:当前同步代码块/同步监视器 否则会出现,IllegalMonitorStateExeption异常;

sleep()和wait()方法的异同:

  • sleep() wait()一旦执行,都可以使当前线程进入阻塞状态;
  • sleep()可以在任何场景下使用,wait()只能在同步代码块/同步方法中 wait需要由 同步监视器来调用!
  • 关于同步建视器,如果两个方法都在同步代码块/同步方法中使用 sleep()不会释放锁(同步监视器); wait()会释放锁(同步监视器);
线程的分类:

Java中的线程分为两类:守护线程 用户线程 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开 守护线程: 是用来服务用户线程的, 通过在 start() 方法前调用 thread.setDaemon(true) 可以把一个用户线程变成一个守护线程 Java垃圾回收就是一个典型的守护线程 main 就是一个用户线程! 若JVM中都是守护线程,当前JVM将退出 守护线程,随着用户线程消亡而消亡 守护线程和其它线程生命周期不同…

线程的生命周期:

线程一般具有五种状态: 创建 就绪 运行 堵塞 死亡

代码语言:javascript复制
1.创建状态:
	//用构造函数 创建 出 
	( Thread )或实现( Runnable )类 的 类对象; 就是线程 创建; 
2.就绪状态:
	//调用了 .start(); 方法的 就是线程 就绪;
3.运行状态:	
	//线程 从就绪到运行 获得了CPU 的资源进入运行状态....失去CUP权限/yieId();就绪专题....
4.堵塞状态:
	//一个 正在运行 的线程 因某种原因不在运行时(一个线程 不能获取 CPU资源) 进入 阻塞状态....
	//常见: 1. .sleep(int); (休眠)  2.yieId(); (线程显示让出CPU资源) 3.I/O 事件阻塞...等待同步锁...
5.死亡状态:
	//线程 的run(); 方法 中代码执行完毕... 不在具有 继续运行的 能力!

JDK中用Thread.State类定义了线程的几种状态: Java线程有6中状态

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选 中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

线程的创建:

JDK1.5之前创建新执行线程有两种方法:

  • 继承Thread类的方式 定义子类继承Thread类 子类中重写Thread类中的run方法 创建Thread子类对象,即创建了线程对象 调用线程对象start方法:启动线程,调用run方法
  • 实现Runnable接口的方式

注意:

  • 想要启动多线程,只有调用:start(); 方法
  • 多线程中的run()方法,不要手动调用,那样并不是多线程执行…只是正常的调用run() 方法调用! 只有通过:线程对象.start(); 才是正确的多线程就绪方法, Java底层会去开启多线程调用run(); 多线程执行run() 中的步骤!
  • Java一个线程对象只能调用一次start()方法启动,如果重复调用了 则将抛出以上 的异常“IllegalThreadStateException”

继承Thread类:

Thread.java

代码语言:javascript复制
package com.wsm.thread;
/**
 * 多线程的创建,方式一:继承于Thread类
 * 1. 创建一个继承于Thread类的子类
 * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
 * 3. 创建Thread类的子类的对象
 * 4. 通过此对象调用start()
 * <p>
 * 例子:遍历100以内的所有的偶数
 *
 */

//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
    //2. 重写Thread类的run()
    @Override
    public void run() {
        //循环输出100行数据
        for (int i = 0; i < 100; i  ) {
            if(i % 2 == 0){
                //不设置默认线程名,默认Tread-0 递增~
                System.out.println(Thread.currentThread().getName()   ":"   i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3. 创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        t1.start();
        //问题一:我们不能通过直接调用run()的方式启动线程。
//      t1.run();

        //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
//      t1.start();

        //我们需要重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();

        //如下操作仍然是在main线程中执行的。
        for (int i = 0; i < 100; i  ) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()   ":"   i   "***********main()************");
            }
        }
    }
}

运行可以看到多线程,交替执行…

扩:匿名子类的方式创建线程

匿名类,当程序只有一处地方使用到该类,则可以创建一个匿名子类 内存用完即丢,快速回收~

创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数

代码语言:javascript复制
public static void main(String[] args) {

   //创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
   //创建Thread类的匿名子类的方式
   new Thread(){
       @Override
       public void run() {
           for (int i = 0; i < 100; i  ) {
               if(i % 2 == 0){
                   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){
                    System.out.println(Thread.currentThread().getName()   ":"   i);

                }
            }
        }
    }.start();
	
	}
}
总结:

继承 Thread 类创建线程:

代码语言:javascript复制
//使用此方式创建线程类    
//此线程类需要继承 Thread类, 并重写类中 run()方法;
//因为Thread类中的 run()方法是,线程要执行的操作任务方法;  所以,线程要执行的操作代码需要写 在 run()方法中;   //并通过子类实例.调用 start();方法来启动线程;
操作:
	A 类 继承Thread类;
	A a = new A();	//创建 A 类实例/线程
	a.start();		//启动 线程.....
	//创建两个线程就是 A a1 = new A(); 
	//每 new A(); 创建一个即每个线程都是独立类对象..只有static修饰的类属性才共享...

实现Runnable接口:

RunnableTest.java

代码语言:javascript复制
package com.wsm.thread;

/**
 * 创建多线程的方式二:实现Runnable接口
 * 1. 创建一个实现了Runnable接口的类
 * 2. 实现类去实现Runnable中的抽象方法:run()
 * 3. 创建实现类的对象
 * 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5. 通过Thread类的对象调用start()
 *
 * 比较创建线程的两种方式。
 * 开发中:优先选择:实现Runnable接口的方式
 * 原因:1. 实现的方式没有类的单继承性的局限性
 *      2. 实现的方式更适合来处理多个线程有共享数据的情况。
 *
 * 联系:public class Thread implements Runnable
 * 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
 */
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable {

    //2. 实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i  ) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()   ":"   i);
            }
        }
    }
}

public class RunnableTest{
    public static void main(String[] args) {
        //3. 创建实现类的对象
        MThread mThread = new MThread();
        //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        t1.setName("线程1");
        //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数
        Thread t2 = new Thread(mThread);
        t2.setName("线程2");
        t2.start();
    }
}
  • 上面的线程:t1 和 t2 都是同一个Runnable实现类对象创建的,类内部的数据是可以共享的!
  • 这里没有表现出,后面举例…
总结:

实现Runnable接口:

代码语言:javascript复制
//因为 Java是单继承的 
//所以如果 一个类继承一个类,又要是 线程类,所以:还可以实现接口来 创建线程了;
//Runnable 
//在java.lang; 包中 其中 有一个 抽象方法 run(); 
//通过实现 Runable 接口重写run(); 方法,方法中写代码完成线程要完成操作;
操作: 
	A 实现了 Runnable接口;
	A  a = new A();			   //创建 A类对象 a;
	Thread xc = new Thread(a); //创建 线程 xc;
	xc.start();				   // 启动线程;
	....................		

注意:
	//如果想要在创建一个线程就不需要 
	new A();
	//只需要在 Thread xc1 = new Thread(a); 即创建新的线程,但每次参数都是实例 a,即创建线程的属性值共享;
	//不想共享某些属性,可以重新 
	A a1 new A();  
	Thread n = new Thread(a1); 
	//当然也可以使用:匿名内部类:匿名子类/匿名实现类形式, 调用启动线程; 前提这个线程只用一次;	

0 人点赞