多任务处理有两种类型:
多进程
多线程
多线程
理解进程和线程
进程:运行中的应用程序称为进程,进程拥有CPU和内存资源。
线程:线程是进程中的一段代码,一个进程可以有多段代码(一个进程包含多个线程),线程本身不拥有资源,共享所有进程的资源。
多进程:在操作系统中能同时运行多个任务
多线程:在同一个应用程序中有多个顺序流(多段代码)在同时执行(同时用QQ聊天,视频)
代码理解:
public static void main(String[] args) {
method1();
}
public static void method1(){
System.out.println("进入method1");
method2();
System.out.println("退出method1");
}
public static void method2(){
System.out.println("进入method2");
System.out.println("method2 execute");
System.out.println("退出method2");
}
开启这个程序的时候,在操作系统中产生一个进程,程序时main方法开始的,当执行main方法的时候,在当前进程里面开启一个进程(主线程),main调用method1方法,main进入到method1继续执行代码,面里面其他代码无法马上执行,必须等待method1执行完之后再回来执行main的输出语句。
整个程序从开始到结束都只有一个现场,单线程程序,执行路径只有一条。
单线程程序:一个任务完了继续后面任务,不能同时执行,所花时间所有任务加在一起。
进程:
1.把应用程序代码放在内存的代码区,代码放在方法区并没有马上执行,但是这时候已经说明一个进程正在准备开始。进程产生但是没有运行,所以进程是一个静态的概念。
2.进程的执行指的是进程里面的主线程开始执行的时候。main方法的执行,在机器上运行的任务全都是线程。
Windows操作系统是多进程的系统,同时可以执行很多线程,Linux、Unix都是多线程。
Dos是单线程系统。
多线程表示多个任务同时执行,CPU如何分配资源:
CPU的运行速度非常快,一秒钟几亿次,所以将CPU运行时间分成一个一个的小片段,每个任务都可以在一个时间片段里面执行一次,如果在当前片段里面没有执行完,其他任务执行完了继续执行。因为速度很快,给我们的感觉同时在执行
实际上在一个时间节点只有一个任务在执行。
如果你的电脑是双CPU、多核,实现真正多线程。
java是如何创建线程
4种:
1.继承java.lang.thead类,当前类就表示多线程类
public class T03_MyThread3 extends Thread{
public T03_MyThread3(String name){
super(name);
}
/**
* 线程体,多线程任务都在run里面执行
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
//获取到当前线程的名字
System.out.println(Thread.currentThread().getName()+"----i:"+i);
}
}
public static void main(String[] args) {
//(1)产生一个线程对象
T03_MyThread3 t = new T03_MyThread3("线程1");
//(2) 启动线程
t.start();
T03_MyThread3 t2 = new T03_MyThread3("线程2");
//(2) 启动线程
t2.start();
}
}
2.实现runable接口,可以表示多线程
public class T02_MyThread2 implements Runnable{
/**
* 线程体:线程执行核心内容
*/
public void run() { //run方法要一开始就重写
for (int i = 0; i < 1000; i++) {
System.out.println("i:"+i);
}
}
public static void main(String[] args) {
//(创建一个线程对象)
T02_MyThread2 t2 = new T02_MyThread2(); //线程的执行体(任务)
Thread th = new Thread(t2); //产生线程的对象
th.start();
for (int j = 0; j < 1000; j++) {
System.out.println("=====j:"+j);
}
}
}
3.实现callable接口,也可以实现多线程
4.线程池
Thread.currentThead()可以获取当前执行的线程,来获取线程的信息(名字、ID、其他)
启动线程start,为什么调用它而不是run?
api提出start才是启动线程的方法,run线程体,直接调用相当于直接调用方法,产生多线程程序start,将线程放入到线程组(线程队列 先进先出),接下来再调用本地方法start0,通知虚拟机来执行线程,虚拟机默认调用线程的run方法来执行。所以一定执行start方法。
线程优先级的问题
java提供了一个线程调度器来监控程序中启动后进入就绪状态的线程。线程调度器可以根据线程优先级来判断先调度那个线程执行。
线程优先级用我们用1-10来表示,值越大优先级越高,默认缺省值为5(主线程默认为5),程序的运行结果还是由虚拟机来安排,无法预知运行后的结果,线程优先级高低只是一个参考值,相对的结果,理论上是具有高优先权的程序占有更多的CPU使用权,但是有时候并不是这样的结果。
setPriority()设置优先级
getPriority()获取优先级
线程的生命周期
五种状态:
1.新建
2.就绪
3.运行
4.阻塞:等待 同步 其他
5.死亡
*其他阻塞:
sleep()\join()\io操作
线程主动让出执行权:yield()
sleep:Thread.sleep() sleep属于Thread静态方法,单位是毫秒,一旦调用sleep,当前线程处于睡眠状态,当睡眠时间过去后马上恢复到就绪状态。此方法会造成线程阻塞。
join:合并某个线程,如果有两个线程A、B,A必须等B执行完了才能继续往下运行,我们可以调用join来完成这项工作。合并过后就相当于以前直接调用方法
yield:在多线程运行过程中,当前抢到CPU资源的线程想让出使用权,调用yield方法马上就让出使用权变成就绪状态,但是还可能被jvm分配使用权。在实际开发中,无法保证yield让步。
*同步阻塞:
*等待阻塞:
线程的同步
同步:多个线程同时运行,有时候线程之间要共享数据,一个线程需要其他线程的数据,否则就不能保证程序的运行结构。
****并发:同一个时间点,多个线程访问同一资源。
****共享资源(临界资源):多个线程共享数据称为共享资源,或者称为临界资源。
****线程同步:同步就是协同步调,按照预先的先后顺序进行执行,先抢到资源的线程先执行,后抢到资源的线程后执行,两个线程相互配合,共享数据。
****线程互斥:多个线程在共享某个变量,而且都对变量有修改,如果不考虑运行过程中相互协调问题,就会出现数据不一致,数据安全问题。
在java中实现多线程同步:
1.同步块
2.同步方法
3.volatile关键字
***同步块:相当于自动加上内置锁,实现同步操作
语法:
synchronized(){
//代码只能在同一时间一个线程访问
}
class AirPort{
private int number = 30;
public int getNumber(){
return number;
}
//一旦有线程抢到票,余票就要-1
public synchronized void reduce(){
--number;
System.out.println(Thread.currentThread().getName()+"抢票成功 余票为:"+number);
//同步要同步,有一个线程在处理余票,另外线程必须等待,当前处理完了还得将余票数据共享出去
/*synchronized (this) {
--number;
System.out.println(Thread.currentThread().getName()+"抢票成功 余票为:"+number);
}*/
}
}
class TicketThread implements Runnable{
private AirPort air;
public TicketThread(AirPort air){
this.air = air;
}
public void run() {
for (int i = 0; i < 10; i++) {
air.reduce();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
**同步方法:在方法访问修饰符后加上synchronized
public synchronized void add(){}
在java中每个对象都有一个内置锁,当你使用synchronized关键字的时候,内置锁会保护整个方法,在调用方法前需要获取内置锁,否则处于等待状态。
*volatile关键字:给局部变量的访问提供一种免锁机制,一个变量使用它来修饰,会告诉虚拟机该域可能会被其他线程更新,每次使用当前值得时候,都要重新计算一下。默认的值保存在寄存器,所以线程互斥就是直接取寄存器的数据,volatile不会提供任何原子操作,也不能用final来修饰。
private volatile int number = 1;
死锁
死锁:指两个或者两个以上的资源,因争夺资源造成相互等待,若没有外部处理,他们都将无限等待下去。(相互将对象锁起来)
原因:1.系统资源不足
2.进程推进顺序有问题
3.资源分配不当
死锁的案例:
线程1:对a加锁 -- 对b加锁 -- 执行 -- 释放b -- 释放a
线程2:对b加锁 -- 对a加锁 -- 执行 -- 释放a -- 释放b
线程数据共享
在java中,可以使用wait、notify、notifyall完成线程之间的通信,在开发过程中最常见的例子就是生产者和消费者去消费产品,生产者将产品放入队列里面,消费者就可以从队列里面获取数据,如果没有数据那就等待。
wait 、notify 、notifyall
都属于object对象提供,主要来控制线程状态
****wait:用来将线程置入休眠的一个方法,不会自己恢复,必须等待调用notify、notifyall当前线程才能被唤醒。
调用wait的时候,线程必须要获取带对象的对象级别锁。(wait必须在synchronized锁中间)
一旦调用wait,当前线程就处于休眠状态,当前线程就释放,其他线程可以抢资源。
****notify:唤醒在对象监视下面等待下的单个线程,也就是使用了wait休眠的线程,只能唤醒一个操作本对象的线程。如果有多个线程再次对象上等待,会随机唤醒一个线程,并等待获取对象的对象锁(表示当前就算收到了通知,wait对象也不会马上获取对象锁,notify唤醒的线程执行完了才能获得对象锁)
****notifyall :唤醒当前对象上面所阻塞的所有线程,唤醒的时候顺序是随机的,使用时必须在synchronized锁中间完成
-总结sleep和wait区别
-总结notify和notifyall的区别
-什么时候用sleep,什么时候用wait