多线程
1.进程 : 每一个进程都有自己独立的内存空间,这个内存空间有操作系统分配。
360 : 进程
线程1 :安全监测
线程2 :垃圾清理
线程3 :病毒查杀
2.线程:一个进程中的一条执行流程,线程不可以脱离进程独立执行(计算机最小处理的就是进程),可以理解线程是某一个进程下多条不同的流程互不影响的执行代码流程
线程特点:
- 在对应的进程内存空间中运行,线程可以共享同一块内存和系统资源
- 在java虚拟机进程中,执行程序代码的任务是由 线程 来完成的。
- 每当用java命令启动一个Java虚拟机进程时,Java虚拟机都会创建一个主线程。
- 该线程从程序入口main()方法开始执行。也就是main线程
计算机中机器指令的真正执行者是CPU,线程必须获得CPU的使用权,才能执行一条指令。
代码是执行某一个时间片,执行完成以后会停下来等。
2.线程的分类:
2.1:前台线程(执行线程)
看得到的,执行代码的线程,线程执行main-->main线程,只要有前台现在在执行,jvm就不会停。
2.2:后台线程(守护线程/精灵线程)
GC 当前台线程结束守护线程也会结束 守护进程为当前用户进程服务
注:每次写的main方法中代码是由一个主线程执行的
3.线程的使用:
3.1:使用线程去执行在非常耗时的代码
3.2:使用线程让多个代码同时执行
CPU执行代码,一个cpu 单核 单线程,同一时刻只能执行一段代码
4.线程的生命周期
4.1:新建: 新建了线程对象
4.1.1:一个类继承Thread类 重写run方法->得到了新的线程类
Thread t = new MyThread();
4.1.2:一个类实现接口Runnable重写run方法-->得到新的线程类
MyThread myt = new MyTeacher();
Thread t = new Thread(myt);
4.2:就绪:调用完start方法以后,将线程启动,并不一定立即执行,交给了操作系统,由操作系统决定
t.start(); 调用t.run()相当于调用普通方法,会先执行完run方法,而调用了start,start会自动调用run方法。
4.3:运行:线程得到cpu时间片以后执行代码
4.4:阻塞状态:代码暂停运行
4.4.1:挂起:sleep(毫秒);join();yield();
恢复为就绪状态:
sleep():睡觉时间完了
join();目标线程执行完毕
yield();让出cup执行权限立刻回到就绪状态
注: 不会释放锁资源
4.4.2:锁池等待 : 在run方法中执行了加锁的代码,但是锁被其他线程获取走了,当前线程就会到锁池等待.
恢复为就绪状态: 当其他线程释放锁资源,立刻回到就绪状态
4.4.3:等待池等待 : 当执行wait()方法以后就会到等待池等待
恢复为锁池等待: 当其他线程执行了notify/notifyall方法的时候回从等待池回到锁池等待.
注: wait()方法会释放锁资源
4.5:死亡状态:当线程执行完run方法中所有代码就会变成死亡状态.run方法执行完毕.
5. 线程调度
计算机通常只有一个CPU, 在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。
所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
在可运行池中,会有多个处于就绪状态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。
线程的调度是指按照特定的机制为多个线程分配CPU的使用权。有两种调度模型:
- 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
- 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。
一个线程会因为以下原因而放弃CPU:
- Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;
- 当前线程因为某些原因而进入阻塞状态;
- 线程运行结束;
- 线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。
- 在某些操作系统中,只要运行中的线程没有阻塞,就不会放弃CPU;
- 在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。
5.线程常用方法使用
- t1.start();线程开始执行 注:线程只能启动一次
- Thread.sleep(1000);//当前线程睡1000毫秒 (放弃cup执行权限)
- Thread.currentThread();//获取当前正在执行的线程
- t1.getName();//获取线程名
- t1.setName("线程名");//设置线程名
- t1.join();//当前线程(t1.join()这个代码在哪个线程里)等待t1线程执行完成以后再执行(当前线程,放弃cup执行权限)
- this.yield();//当前线程让出cpu执行权限回到就绪状态
- this.notify();//随机唤醒一个线程-->等待池
- this.notifyAll();//唤醒所有线程
- this.isAlive();//线程是否是活的
- interrupt();//设置线程中断状态标识位-->中断状态:true
- isInterrupted();//查看返回线程的中断状态标识位 true:有线程要中断当前线程 //不会清除线程的中断状态
- interrupted();//返回线程的中断状态标识位 true:有线程要中断当前线程 //清除线程的中断状态
- setDaemon(true);//把新建状态的线程设置为守护线程。只能是新建状态。
6.线程同步 : synchronized
线程不安全:多个线程操作同一份数据就会导致线程不安全问题
线程同步就是解决这个问题的,通过加锁就可以达到线程同步
- synchronized关键字可以出现的地方
- synchronized(锁){//线程同步的代码}
- public synchronized void say(){}-->this
- public static synchronized void say(){}-->Hello.class
注意问题: 多个线程对象使用的锁是不是同一把锁
是,多个线程 对于 锁 上的代码按顺序运行
否, 多个线程 对于 锁 上的代码任意运行
补充:
synchronized实现可见性
可以实现互斥锁(原子性),即同步。但很多人都忽略其内存可见性这一特性
JMM关于synchronized的两条规定:
线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
线程解锁前对共享变量的修改在下棋甲所示对其他线程可见
7.线程通信
使用 锁对象.wait(); 会释放锁资源 放弃cup执行权限
锁对象.notify();
锁对象.notifyAll();
8.线程的死锁
A线程需要的锁被B线程获取
B线程需要的锁被A线程获取
9.线程的让步
yield()方法
Thread.yield()静态方法,
如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
yield()转到就绪状态;
yield()不抛任何异常
yield()并不常用。
10. 调整线程优先级
注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果
线程优先级的使用原则与操作系统有着密切的联系因此在JAVA中的线程的调度是完全受其所运行平台的操作系统的线程调度程序控制的。所有虽然我们可以设置线程的优先级但是在运行的时候不一定能够确切的体现出来。
所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的线程获得较多的运行机会。
Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。
. MAX_PRIORITY: 10, 最高;
. MIN_PRIORITY: 1, 最低;
. NORM_PRIORITY: 5, 默认优先级;
释放对象的锁:
. 执行完同步代码块;
. 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
. 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;
线程不释放锁:
. Thread.sleep()方法,放弃CPU,进入阻塞状态;
. Thread.yield()方法,放弃CPU,进入就绪状态;