第一节、 进程和线程
1.1 进程(Process)
1、 正在运行的程序,是一个程序的运行状态和资源占用(内存,CPU)的描述,通过进程ID区分。
2、 进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程。
3 、目前操作系统支持多进程多任务。
进程的特点:
a.独立性:不同的进程之间是独立的,相互之间资源不共享(举例:两个正在上课的教室有各自的财产,相互之间不共享)
b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
c.并发性:多个进程可以在单个处理器上同时进行,且互不影响
1.2 线程(Thread)
1 线程就是一条执行路径。是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务。
线程的特点:
线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。
1.3 进程和线程的关系以及区别
a.一个程序运行后至少有一个进程
b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
c.进程间不能共享资源,但线程之间可以
d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高
第二节 多线程的实现
线程创建方式有三种:
1、 继承Thread类**
2、实现Runnable接口**
3 、实现Callable接口**
2.1 继承Thread类
继承自Thread类,Thread类是所有线程类的父类,实现了对线程的抽取和封装
继承Thread类创建并启动多线程的步骤:
a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
b.创建Thread子类的对象,即创建了子线程
c.用线程对象的start方法来启动该线程
注意: 1 、程序运行时会自动创建一个线程 ,这个线程叫主线程;可以通过主线程创建子线程。
2 、启动线程使用start()方法,不要直接调用run()方法。
2.2 实现Runnable接口
实现Runnable接口创建并启动多线程的步骤:
a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程
实现Runnable接口的例子:
模拟实现四个窗口售票功能。
一、实现Runnable接口:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable{
private int ticket=1000;
Lock lock =new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticket<1){
break;
}else{
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}
} finally {
lock.unlock();
}
}
}
}
二。编写测试类
public class Test {
public static void main(String[] args) {
//创建票对象
Ticket ticket=new Ticket();
//创建线程
Thread w1=new Thread(ticket, "窗口1");
Thread w2=new Thread(ticket, "窗口2");
Thread w3=new Thread(ticket, "窗口3");
Thread w4=new Thread(ticket, "窗口4");
//启动线程
w1.start();
w2.start();
w3.start();
w4.start();
}
}
2.3 两种实现方式的比较
1、 继承Thread类的方式
a.没有资源共享,编写简单 如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用getName() 获取线程名字。
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】
2、 实现Runnable接口的方式
a.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
b.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
2.4 调用start()与run()方法的区别
** 当调用start()方法时将创建新的线程,并且执行run()方法里的代码,但是如果直接调用run()方法,不会创建新的线程。 2.5 线程的第三种创建方式:
使用Callable接口实现多线程 :Callable 有返回值,可以跑出异常。
调用: 1、创建可调用对象 2、创建一个任务 3、创建线程 4、启动线程 5、获取返回值。
例子:
public class MyCallable implements Callable <Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <=100; i++) {
sum+=i;
Thread.sleep(3000);
System.out.println(i);
}
return sum;
}
}
测试类:
public class Test {
public static void main(String[] args) throws Exception{
// 创建可调用对象
MyCallable callable=new MyCallable();
//创建任务
FutureTask<Integer> task=new FutureTask<>(callable);
//创建线程
Thread thread=new Thread(task);
//启动
thread.start();
// 获取
Integer d=task.get();
System.out.println(d);
}
}
第三节 线程的常用方法
3.1 线程休眠:使得当前正在执行的线程休眠,释放时间片,进入阻塞状态。
Thread.sleep(1000); // 相当于当前线程挂起1s。
3.2 设置线程优先级: 优先级高的线程获得较多的执行机会。
*** 优先级范围: 1-10 默认为 5 对应值越大,说明优先级越高,这个方法的设置一定在start()之前。
3.3 合并(加入)线程: 阻塞当前线程,知道加入的线程执行完毕,才继续执行。
*** join之前一定要将线程处于准备状态 (start)。
3.4 后台线程(守护线程): 隐藏起来默默运行的线程,例如jvm。
特征: 如果所有的前台线程都死亡,后台线程自动死亡。
设置后台线程: demoThread.setDaemon(true);
3.5 线程让步: yield (虚伪的让步) 让出CPU的使用权,让出后还参与争夺。只让给优先级和它相同或者优先级比它高的。 Thread.yield();
3.6 线程中段 : 程序在等待的过程中可以使用 interrupt 方法来打断。
第四节 线程的生命周期
新生--->就绪---->运行---->阻塞----->死亡
对于线程,当线程被创建,进入新生状态,调用start()方法 ,进入就绪状态(可执行状态),如果抢到cpu,进入运行状态,运行过程中出现了阻塞,进入阻塞状态,如果程序正常结束进入死亡状态。
New(新生):线程被实例化,但是还没有开始执行
Runnable(就绪):没有抢到时间片
Running(运行):抢到了时间片,CPU开始处理这个线程中的任务
Blocked(阻塞): 线程在执行过程中遇到特殊情况,使得其他线程就可以获得执行的机会,被阻塞的线程会等待合适的时机重新进入就绪状态
Dead(死亡):线程终止
a.run方法执行完成,线程正常结束【正常的死亡】
b.直接调用该线程的stop方法强制终止这个线程
State枚举表示线程的状态(六种)
NEW (新生)
RUNNABLE (可运行、运行)
BLOCKED (锁阻塞)
WAITING (等待)
TIMED_WAITING (限时等待)sleep(5000)
TERMINATED (终止死亡)