了解进程与线程的区别
-
进程:
是一个运行中的程序的实例
-
进程的两个特点:
(1)是一个实体,都有自己独立的地址空间,分文本区域,数据区域和堆栈,文本区域用来存储编写的程序的代码,数据区域用来存储用来运行时所需要的数据(动态分配内存),堆栈用来存储运行时涉及到的指令和本地变量
(2)是一个(运行中的程序),程序本身是没有生命的一个实体,只有当处理器赋予它生命时,它才能称之为一个活动实体,即进程,进程是操作系统级别的基本单元
通俗点说,进程就是操作系统运行的一个任务,一个应用程序运行时就对应一个进程
-
线程:
-
进程中所包含的一个或多个
执行单元称为线程(thread),一个线程就是进程中的一个
顺序执行流
进程拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问,即同一个进程中的多个线程共享一块内存空间和一组系统资源
线程只能归属于一个进程并且只能访问该进程所拥有的资源
线程本身也有一个供程序执行的堆栈,在线程切换时,负荷小,线程也被称之为轻负荷进程 - 进程是程序的一次动态执行过程,它经历了从代码加载,执行到执行完毕的一个完整过程,这个过程是进程本身从产生、发展到最终死亡的过程。
- 多线程是实现并发机制的一种有效手段。进程和线程一样,是实现并发的一个基本单位。
-
并发原理
多个线程”同时运行”只是我们感官上的一种表现,其实,线程是并发运行的.
操作系统将时间划分成很多时间片段,尽可能地均匀分配给每一个线程,获取时间片段的线程被CPU运行,二其他线程处于等待状态,所以微观上是走走停停,断断续续的,宏观上同时运行的现象叫并发
但绝不是真正的”同时发生”
掌握java线程的三种实现方式及其区别
在java中如果想实现多线程,可以采用以下三种方式:
- 继承Thread类;
- 实现Runnable接口;
- 继承Callable< E >类;
继承Thread类
Thread类是在java.lang包中定义的,一个类只要继承了Thread类,此类就被称为多线程操作类。在Thread子类中,必须重写Thread类中的run()方法,该方法为线程的主体。最后创建线程对象,用start()方法开启任务。
注意:不是调用run()方法
定义语法:
class 类名 extends Thread {
属性...;
构造器...;
方法...;
public void run(){
线程主体逻辑;
}
}
/**
* 方法1:
* 通过继承Thread,完成线程的定义
*/
public class MyTask extends Thread{
private String str; //添加属性
public MyTask(String name) { //添加构造器
this.str = name;
}
/*添加get()/set()方法*/
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
/*重写run()*/
public void run() {
for(int i=0;i<100;i++){
System.out.println(this.getStr()+":"+i);
}
}
public static void main(String[] args) {
/*创建线程对象,启动任务*/
Thread t1 = new MyTask("A");
Thread t2 = new MyTask("B");
t1.start();
t2.start();
System.out.println("main方法over");
}
}
实现Runnable接口
在java中也可以通过实现Runnable接口的方式实现多线程,Runnable接口只定义了一个抽象方法;
public void run();
将Runnable的实现类对象传给Thread对象,再由Thread对象调用start()方法开启任务
注意:不可以直接使用Runnable的实现类对象直接调用start()方法,该实现类对象相当于Thread对象执行的任务对象
定义语法:
class 类名 implements Runnable{
属性...;
方法...;
public void run(){
线程主体逻辑;
}
}
/**
* 方法二:
* 实现接口,完成任务的定义
*/
public class MyTask1 implements Runnable{
int num = (int)(Math.random()*100+1);
/*重写run(),定义任务体*/
public void run() {
for(int i=0;i<500;i++){
System.out.println(num);
}
}
public static void main(String[] args) {
/*创建线程对象,启动任务*/
Runnable r1 = new MyTask1();
Runnable r2 = new MyTask1();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
System.out.println("main方法结束");
}
}
继承Callable< E >类
使用Callable< E >创建子类对象,然后重写call()方法(相当于run()方法),再将Callable< E >传给FutureTask< E >对象,再将FutureTask< E >对象传给Thread对象,调用start()方法,启动线程
定义语法:
class 类名 extends Callable<E> {
属性...;
构造器...;
方法...;
public E call(){
线程主体逻辑;
}
}
public class ThreadCallableDemo {
public static void main(String[] args) {
/*直接以匿名内部类的形式创建FutureTask对象*/
FutureTask<String> task =
new FutureTask<String>(
new Callable<String>(){
/*实现call()方法*/
public String call(){
int num = 0;
for(int i=0;i<100;i++){
if(i%2!=0){
num+=i;
System.out.println(num);
}
}
return num+"";
}
});
/*创建线程对象,启动任务*/
Thread t1 = new Thread(task);
t1.start();
}
}
三种创建线程实现方式的区别
前两种方式比较:
(1)将线程对象和任务对象分开,降低了耦合度,便于维护
(2)避免了java中单继承的限制
(3)适合相同的任务代码块处理同一个资源
第三种方式:call方法带有返回值
了解线程的操作状态
-
线程的状态图解析:
-
(1)新建状态,即新建一个线程对象,设置好需要执行的任务
(2)就绪状态:调用线程的start方法,进入准备状态,等待CPU分配时间片段
(3)运行状态:CPU将时间片段给了线程后,线程开始执行任务
(4)阻塞状态:正在运行的线程由于某种原因放弃CPU的使用权,即此线程放弃时间片段,进入阻塞状态阻塞状态分为三种: 等待阻塞:运行中的线程调用wait方法,jvm将此线程放入等待池中 同步阻塞:运行中的线程想要获取同步的锁对象时,若锁对象被其他线程占用,则jvm将此线程放入锁池中 其他阻塞:当线程执行到阻塞方法或Thread.sleep()或其他线程的加入时,此线程放弃时间片段,进入阻塞状态
- (5)结束状态:当线程执行完任务后,表示结束