多线程(1)

1、 线程概念

1.1、进程

几乎所有操作系统都支持进程的概念,所有运行中的任务通常对应一条进程(Process)。当一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能,进程是操作系统进行资源分配和调度的一个独立单位。

一般而言,进程包含如下三个特性:

  1. 独立性:进程是操作系统进行资源分配和调度的一个独立单位,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。哪怕在同一台计算机上运行,进程之间通信可能也需要通过网络、独立于进程的文件来交换数据。
  2. 动态性:程序只是一个静态的指令的集合,而进程是一个正在系统中运行的活动的指令集合,即进程是处于运行过程中的程序。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同状态,这些概念在程序中都是不具备的。
  3. 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会相互影响。

现代的操作系统都支持多进程的并发,但在具体的实现细节上可能因为硬件和操作系统的不同而采用不同的策略,目前大多数采用效率更高的抢占式多任务策略。对于一个CPU而言,它在某个时间点上只能执行一个程序,也就是只能运行一个进程,CPU不断的在这些进程之间轮换执行。那么,我们为什么可以一边用开发工具写程序,一边听音乐,一边还上网查资料…,我们没有感觉到中断和轮换呢?这是因为CPU的执行速度相对于我们的感知速度来说实在是太快了,所以我们感觉像是同时运行一样。但是当我们启动足够多的程序时,依然可以感觉到运行速度下降的。

1.2、线程

多线程则扩展了多进程的概念,使得通一个进程可以同时并发处理多个任务。线程(Thread)也被称为轻量级进程。就像进程在操作系统中地位一样,线程在进程中也是独立的、并发的执行流。当进程被初始化后,主线程就被创建了,对于Java程序来说,main线程就是主线程,但我们可以在该进程内创建多条顺序执行路径,这些独立的顺序执行路径就是线程。

进程中的每一个线程可以完成一定的任务,并且是独立的,线程可以拥有自己独立的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。由于线程间的通信是在同一个地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快,因此可以通过简单编程实现多线程相互协同来完成进程所要完成的任务。但是也存在安全问题,因为其中一个线程对共享的系统资源的操作都会给其他线程带来影响,由此可知,多线程中的同步是非常重要的问题。

线程的执行也是抢占式的,也就是说,当前运行的线程在任何时候都可能被挂起,以便另一个线程可以运行。我们说CPU在不同的进程之间轮换,进程又在不同的线程之间轮换,因此线程是CPU执行和调度的最小单元。

总之,一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建一个线程简单的多,而且多个线程共享同一个进程的虚拟空间,所以,使用多线程来实现并发比使用多进程实现并发的性能要高的多。

注意:
并发性(concurrency)和并行性(parallel)是两个概念,并行是指在同一时刻,有多条指令在多个处理器上同时执行;并发是指在同一个时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

2、线程的创建和启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的对象。每个线程的作用是完成一定的任务,实际上就是执行一段代码,我们称之为线程执行体。Java使用run方法来封装这段代码,即run方法的方法体就是线程执行体。

2.1、继承Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:
(1)定义继承Thread的子类,并重写该类的run()。查看Thread类的源代码,发现当我们没有传target时,那么Thread类的run()就是一个什么也没干的方法,因此我们必须重写run()来完成线程的任务。
(2)创建Thread子类的实例,一个实例对象就是一个线程。
(3)调用线程对象的start()来启动线程。

示例代码:

package com.bdit.part01.thread;

public class TestMyThread {
   
	public static void main(String[] args) {
   
		MyThread my1 = new MyThread();
		my1.start();
		
		MyThread my2 = new MyThread();
		my2.start();
		
		for (int i = 10; i >= 1; i--) {
   
			System.out.println(Thread.currentThread().getName() + "线程:" + i);
		}
	}
}
class MyThread extends Thread{
   
	public void run(){
   
		for (int i = 1; i <= 10; i++) {
   
			System.out.println(super.getName() + "线程:" + i);
		}
	}
}

说明:
●Thread.currentThread()方法总是返回当前在执行的线程对象;
●getName()方法是Thread的实例方法,该方法返回当前线程对象的名字,可以通过setName(String name)方法设置线程名称,否则依次为Thread-0,Thread-1…等

提示:
(1)JavaSE的程序至少有一个main主线程,它的方法体即是线程体。
(2)Thread-0和Thread-1线程虽然都是MyThread类的线程对象,但是各自调用自己的run(),互相之间是独立的,因此各打印1-10。
(3)启动线程用start()方法,而不是run()!调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理。但是如果直接调用run(),系统会把线程对象当成一个普通对象,而run()就是一个普通方法。

2.2、实现Runnable接口

实现Runnable接口来创建并启动多线程的步骤:
(1)定义Runnable接口的实现类,并重写该接口的run()方法
(2)创建Runnable实现类的对象
(3)创建Thread类的对象,并将Runnable实现类的对象作为target。其实该Thread类的对象才是真正的线程对象。当JVM调用线程对象的run()时,如果target不为空,就会调用target的run()。
(4)调用线程对象的start()启动线程。

示例代码:

package com.bdit.part01.thread;

public class TestMyRunnable {
   
	public static void main(String[] args) {
   
		MyRunnable my = new MyRunnable();
		new Thread(my).start();
		new Thread(my).start();

		for (int i = 10; i >= 1; i--) {
   
			System.out.println(Thread.currentThread().getName() + "线程:" + i);
		}
	}
}
class MyRunnable implements Runnable{
   
	public void run(){
   
		for (int i = 1; i <= 10; i++) {
   
			System.out.println(Thread.currentThread().getName() + "线程:" + i);
		}
	}
}

3、生命周期

一个完整的生命周期中通常要经历如下的五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值