文章目录
进程与线程
线程指的是进程中的一个执行场景,也就是执行流程
- 每一个进程都是一个应用程序,都有独立的内存空间
- 同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程都有自己的栈内存。)
什么是进程
一个进程对应一个应用程序,例如在Windows操作系统中打开QQ,在Java开发环境下启动jvm,就表示启动了一个线程。在同一个操作系统中,可以同时启动多个线程。
多进程有啥用
单进程计算机只能做一件事情。
对于单核计算机来说,在同一时间点上,游戏进程和音乐进程不会在同时运行。由于计算机将在两个进程之间频繁切换,切换速度很快,让人感觉到游戏和音乐在同时进行。
多进程的作用并不在于提高执行速度,而是提高cpu的使用率。
什么是线程
线程是一个进程中的执行场景,一个进程可以启动多个线程。
多线程有啥用
多线程也不是为了提高执行速度,而是提高应用程序的使用率。
可以给我们人一种错觉:感觉多个线程在同时并发进行。
Java程序的运行原理
Java命令会启动Java虚拟机,等于启动了一个应用程序,启动了一个进程。该进程会自动启动一个“主线程”,这个主线程之后便去调用某个类的main方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的.
线程的创建和启动
Java虚拟机的主线程入口是main方法,用户可以自己创建线程,创建方式有二:
- 继承Thread类
- 实现Runnable接口(推荐使用Runnable接口)
继承Thread类
继承java.lang.Thread+重写run方法
定义线程+创建线程+启动线程
public class ThreadTest {
public static void main(String[] args) {
//创建线程
Thread t=new Processor();//多态
//启动线程
t.start();
//执行瞬间结束,告诉JVM再分配一个新的栈给t线程
//run不需要程序员手动调用,系统线程启动后自动调用run方法
//t.run();//这是普通方法调用,没有start只有run这样做程序只有一个线程
//run方法结束之后,下面程序才能继续执行
//这段代码在主线程中运行
for(int i=0;i<10;i++) {
System.out.println("main-->"+i);
}
//有了多线程之后,main方法结束只是在主线程栈中没有方法栈帧
//但是在其他线程或者是其他栈中还有栈帧
//main方法结束后,程序可能还在运行
}
}
//定义一个线程
class Processor extends Thread{
//这里是重写run方法
public void run() {
for(int i=0;i<30;i++) {
System.out.println("run-->"+i);
}
}
}
如图,一个线程一个栈哈。
贴出运行结果:
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
run-->0
main-->6
main-->7
main-->8
main-->9
run-->1
run-->2
run-->3
run-->4
run-->5
run-->6
run-->7
run-->8
run-->9
run-->10
run-->11
run-->12
run-->13
run-->14
run-->15
run-->16
run-->17
run-->18
run-->19
run-->20
run-->21
run-->22
run-->23
run-->24
run-->25
run-->26
run-->27
run-->28
run-->29
可以看到主线程和t线程其实是不断切换的。
Runnable接口
编程一个类实现java.lang.Runnable接口+实现run方法
定义线程+创建线程+启动线程
public class ThreadTest1 {
public static void main(String[] args) {
//创建线程
Thread t=new Thread(new Processor1());
//Thread类的构造方法里的参数可以是Runnable类型
//启动线程
t.start();
for(int i=0;i<30;i++) {
System.out.println("main->>"+i);
}
}
}
//推荐这种方式,一个类实现接口之外保留了类的继承
class Processor1 implements Runnable{
public void run() {
for(int i=0;i<10;i++) {
System.out.println("run->>"+i);
}
}
}
贴出运行结果:
main->>0
main->>1
main->>2
run->>0
run->>1
run->>2
run->>3
run->>4
run->>5
main->>3
run->>6
run->>7
run->>8
run->>9
main->>4
main->>5
main->>6
main->>7
main->>8
main->>9
main->>10
main->>11
main->>12
main->>13
main->>14
main->>15
main->>16
main->>17
main->>18
main->>19
main->>20
main->>21
main->>22
main->>23
main->>24
main->>25
main->>26
main->>27
main->>28
main->>29
线程的生命周期
线程的生命周期分为五个阶段:
新建+就绪+运行+阻塞+消亡
新建
:采用new语句创建完成
就绪
:执行start后
运行
:占用CPU时间
阻塞
:执行了wait语句、执行了sleep语句和等待某个对象锁,等待输入的场合
终止
:退出run方法
假设这里我们有三个线程t1,t2,t3
t1,t2,t3去争夺时间片,谁抢到了就去运行,当时间片时间不够,例如第一次t1抢到了结果时间不够,便返回成为就绪状态,重新开始抢夺时间片,如果t1再次抢夺到了时间片,那么t1将从上次没执行完的地方开始执行。
我们从程序执行结果就可以看到:
main->>0
main->>1
main->>2//主线程拿到时间片,奈何时间不够
//主线程进入就绪状态,重新抢时间片
//这一次t线程抢夺到了
//进入运行状态
run->>0
run->>1
run->>2
run->>3
run->>4
run->>5
//时间不够,t线程它发出了嘤嘤嘤的叫声,就绪去了
//主线程又抢到了,并且从上次没执行完的地方开始执行
main->>3
run->>6
run->>7
run->>8
run->>9
//t线程执行完了,它又发出了嘤嘤嘤的叫声,并英勇消亡
main->>4
main->>5
main->>6
main->>7
main->>8
main->>9
main->>10
main->>11
main->>12
main->>13
main->>14
main->>15
main->>16
main->>17
main->>18
main->>19
main->>20
main->>21
main->>22
main->>23
main->>24
main->>25
main->>26
main->>27
main->>28
main->>29
线程的调度与控制
通常我们的计算机只有一个CPU,CPU在某一个时刻只能执行一条指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。
Java虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型。Java使用抢占式调用模型。
分时调用模型
:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调用模型
:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一点。
三个方法:
- 获取当前线程对象Thread.currentThread();
- 给线程取名t.getName(“t1线程”);
- 获取线程的名字t.getName();
public class ThreadTest2 {
public static void main(String[] args) {
//如何获取当前线程对象
Thread t=Thread.currentThread();
//t保存的内存地址指向的线程是“主线程对象”
//获取线程的名字
System.out.println(t.getName());
Processor2 p=new Processor2();
Thread t1=new Thread(p);
t1.start();
//Thread-0
Thread t2=new Thread(new Processor2());
t2.start();
//Thread-1
}
}
//定义线程
class Processor2 implements Runnable{
public void run() {
Thread t=Thread.currentThread();
//t保存的内存地址指向的线程是“t1线程对象”
System.out.println(t.getName());
}
}
程序运行结果为
main
Thread-0
Thread-1
并且我们也可以利用setName()方法来给线程取名:
Processor2 p=new Processor2();
Thread t1=new Thread(p);
t1.setName("t1线程");
t1.start();
//t1线程
Thread t2=new Thread(new Processor2());
t2.setName("t2线程");
t2.start();
//t2线程
线程优先级
线程优先级高的获取的CPU时间片相对多一些
优先级:1-10
最低1,最高10,默认5
public class ThreadTest3 {
public static void main(String[] args) {
Thread t1=new Processor3();
t1.setName("t1线程");
Thread t2=new Processor3();
t2.setName("t2线程");
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());//默认优先级是5
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
//启动线程
t1.start();
t2.start();
}
}
//定义模型
class Processor3 extends Thread{
public void run() {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
//获取当前线程的名字
}
}
}
Thread.sleep()
1.Thread.sleep(毫秒);
2.sleep方法是一个静态方法
3.该方法作用为阻塞当前线程,腾出CPU让给其他线程。
public class ThreadTest4 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Processor4();
t1.setName("t1线程");
t1.start();
//阻塞主线程
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
Thread.sleep(500);
}
}
}
class Processor4 extends Thread{
//Thread中的run方法不抛出异常
//所以重写run方法之后,在run方法的声明位置上不能使用throws
//所以run方法中的异常只能用try...catch...
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
try {
Thread.sleep(1000);//让当前线程阻塞1000毫秒
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadTest5 {
public static void main(String[] args) throws Exception {
//创建线程
Thread t=new Processor5();
//启动线程
t.setName("t");
t.start();
//休眠
//Thread.sleep(5000);
t.sleep(5000);//阻塞了主线程,等同于Thread.sleep(),和t线程无关
System.out.println("延时五秒");
}
}
class Processor5 extends Thread{
public void run() {
for(int i=0;i<3000;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
某线程正在休眠,如何打断他的休眠
(此时,线程发出了嘤嘤嘤的声音)
靠的是异常处理机制
public class ThreadTest6 {
public static void main(String[] args) throws Exception{
Thread t=new Thread(new Processor6());
//现在我们需要在启动线程5秒钟之后打断线程休眠
//给睡懒觉的线程起个名字
t.setName("让我多睡会");
//启动线程
t.start();
//主线程睡了5s之后
Thread.sleep(5000);
//打断t的休眠
t.interrupt();
}
}
class Processor6 implements Runnable{
public void run() {
try {
Thread.sleep(10000000);
System.out.println("hello");//不会运行了
}catch(InterruptedException e) {//通过异常处理机制中断休眠
e.printStackTrace();
}
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
如何正确的更好的终止一个正在运行的线程
需求:线程启动5s之后终止
通常定义一个标记,来判断标记的状态停止线程的运行
public class ThreadTest7 {
public static void main(String[] args) throws Exception {
Processor7 p=new Processor7();
Thread t=new Thread(p);
t.setName("t线程");
t.start();
//5秒之后终止
Thread.sleep(5000);
//终止
p.run=false;
}
}
class Processor7 implements Runnable{
boolean run=true;
public void run() {
for(int i=0;i<10;i++) {
if(run) {
try {
Thread.sleep(1000);
}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
else {
return;
}
}
}
}
Thread.yield
该方法是一个静态方法。它与sleep类似,只是不能由用户指定暂停多长时间,yield时间不固定。并且yield方法只能让同优先级的线程有执行的机会。
即使当前线程由运行状态变为就绪状态。cpu会从众多的运行状态里选择,也就是说,当前也就是刚刚那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
public class ThreadTest8 {
public static void main(String[] args) {
Thread t=new Processor8();
t.setName("t线程");
t.start();
//在主线程中
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor8 extends Thread{
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
if(i%20==0) {
Thread.yield();
}
}
}
}
t.join
此方法为成员方法。当前线程可以调用另一个线程的join方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行。
public class ThreadTest9 {
public static void main(String[] args) throws Exception{
Thread t=new Thread(new Processor9());
t.setName("t线程");
t.start();
//合并线程
t.join();//t线程和主线程合并
//现在只有当t线程执行完毕时,才会执行主线程
//主线程内
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
class Processor9 implements Runnable{
public void run() {
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
运行结果为:
t线程-->0
t线程-->1
t线程-->2
t线程-->3
t线程-->4
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
线程的同步(加锁)
异步编程模型:t1线程执行它自己,t2线程执行它自己,两个线程谁也不等谁
同步编程模型:t1线程和t2线程执行,t1线程必须等t2线程执行结束之后才能执行
为什么要引入线程同步?
为了数据的安全。尽管应用程序的使用率变低。线程同步机制使程序等同于单线程。
什么条件下使用线程同步?
第一:必须是多线程环境
第二:多线程环境共享同一个数据
第三:共享的数据涉及到修改操作
以下程序演示取款例子: