线程(基础)
程序就是我们编写的代码,然而
进程是我们把代码运行起来所形成的动态的一个过程
每当我们执行一个程序的时候,我们就会开启一个进程。。。
当我们关闭,即是结束程序的时候,进程就会消失,就会停止。。。
一个进程可以拥有多个线程任务
并行:相对于两个CPU而言,多个CPU实现并行
并发:一个CPU实现多个任务执行,造成一种似乎多个任务同时执行的错觉,多个任务这样执行的情景被称之为并发
八个处理器表示的意思就是八核,具有八个CPU进行处理。
测量当前电脑有多少核?
线程应用案例1-继承Thread类![](https://img-blog.csdnimg.cn/378f6909993e43baa83c26dce37d0420.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF6KW_5aaC5p-S,size_20,color_FFFFFF,t_70,g_se,x_16)
细节![](https://img-blog.csdnimg.cn/41a5d1faf9af42af8b60ce7a05ace0d3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF6KW_5aaC5p-S,size_20,color_FFFFFF,t_70,g_se,x_16)
一开始执行程序形成一个进程,之后先开始执行main线程,执行start方法之后开始调用Thread-0线程 。但是执行Thread-0线程的时候,main线程依然是继续并发执行的。。。
记住一句话:
对于一个程序执行,产生进程,产生main线程(主线程)以及多个子线程(Thread-0线程) ,
并不是说当main线程结束之后进程就挂掉了,而是等所有线程的任务都结束完成了之后进程才会结束挂掉。。。
并且记住:
不仅是main线程才可以产生子线程,子线程(如Thread-0线程)也可以再一次产生一个线程去执行。
只有当所有的线程执行完之后,进程才会结束。。。。
为什么不直接调用run方法,而是通过start方法创建出一个子线程去调用呢?
run方法就是一个普通的方法,如果这样直接调用,等于说还是执行的main这个线程的方法,并没有再启动一个线程实现并发。。。所以我们调用start方法实现多线程并发执行的高效效果
直接调用run方法的后果就是:
当run方法执行完之后才会执行main线程下面的任务方法。。。。
native方法
native标识的方法start0是本地方法,只可以由JVM机进行调用,我们不可以调用。。。
start方法底层分析
源码:
真正实现多线程效果的是start0方法而不是run方法
我们通过main线程,之后通过一个类(这个类继承了Thread线程类)的对象引用进行调用start方法
实现多线程并发执行
追及源码可知,start方法底层就是start0方法,所以实现多线程效果的是start0而不是run方法
线程应用2-实现Runnable接口
我们模拟一下这种实现Runnable接口来启动多线程并发的底层实现源码:
来解释一下为什么这样传?
这里的ThreadProxy类就相对于后面的Thread类
Thread类编译器底层源码截取:
实现的原理和我们上面模拟的一模一样
构造器:
练习
总结:
我们把相应的业务代码写到对应的子线程(由main方法各种方式通过start方法启动出来的线程) 中
让各个线程并发分别执行完。
无论哪个线程先执行完,都不会影响其他的线程,直到所有线程执行完之后,整个进程才会退出
T3就是业务实现逻辑类。它是被thread01类和thread02类共享的
最后实现调用start方法,底层调用start0方法,start0方法调用Thread类中的run方法,这个重写的run方法的实现内容就是运行时绑定实现业务逻辑类T3的run方法,业务逻辑的代码就写在T3的run方法中【上面分析过底层源码】
使用synchronized解决售票问题【后面会细讲synchronized】
线程终止![](https://img-blog.csdnimg.cn/b5405b9f237a4b42b7016f5d9a1a336c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF6KW_5aaC5p-S,size_20,color_FFFFFF,t_70,g_se,x_16)
总结:
如果我们希望t1这个线程可以控制t2这个线程什么时候退出,那么我们需要在t1中可以进行设置t2的变量就可以了呀
线程常用方法
当类Th继承了Thread线程类之后,我们创建一个Th类的对象引用,我们就可以通过这个对象引用来调用方法作用于这个线程
细节:
1.如图:t2让出CPU让t1线程自己进行执行
但是也有可能礼让不成功,当CPU能够顾及t1,t2过来的时候,我们就不会进行礼让了。。。
2.插队
在CPU执行的过程中启动两个线程 t1 t2,当线程t1执行到某个代码的时候,这行代码是执行t2.join()方法
【join方法表示插队执行的意思】t2.join表示让t2线程的任务插队优先进行执行
那么CPU开始执行t2的线程任务,当t2的线程任务执行完之后,CPU才会接着执行t1的线程任务
上图就是描述,在main线程中调用 t1.join方法,表示的意思是把t1线程的任务执行完之后,再返回去执行main线程的任务
线程常用方法练习
结果:
有一个细节:
在上面进行插队时,我们应该先调用Thread类中的start方法,先启动子线程,启动完之后再进行调用join方法【在后面将线程的状态会讲解】
守护线程
一般来说,主线程m启动了一个子线程为t1。假设t1是一个无限循环的代码,当主线程m执行完之后,子线程t1依旧会无限循环的执行下去。。。
但是当我们把t1设置成主线程m的守护线程的时候,那么当主线程m执行结束后,守护线程t1也会结束。。。。。
演示代码:
线程的状态
一般官方文档是有六种线程状态 :
NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED
但是有的地方说是七种线程状态
RUNNABLE代表可运行状态,但是它是否真的运行是取决于内核的调度器决定的。因此RUNNABLE又可以被细化为两种状态,Ready就绪状态和Running 运行状态。Running 运行状态是真正占用CPU内存的,真正运行的状态。
yeild方法
当线程start之后进行RUNNABLE状态,我们调用yeild方法,把其搞到Ready状态进行就绪状态,但是内核中线程是被调度器选中执行的,是具有一定的难控制性。它是取决于内核中的资源来决定是否真的进行礼让,如果资源多,那么同时执行。资源过少时才可能进行真的礼让处理。。。
线程被挂起
当线程被挂起时,线程从运行状态Running转化到就绪状态Ready
注意顺序执行:
首先第一步是start启动线程之后到Runnable状态
第二步:
如果进入同步代码块的锁,那么就进入Blocked状态(锁阻塞状态)当获得锁时,才可以返回Runnable状态
如果【剩余两种情况如图所示查看即可】
Synchronized
线程同步:
不管有多少个线程同时进来,在同一个时刻最多只能有一个线程进来实现操作。。。当一个操作完了之后才可以进来第二个执行。。
举个例子:无论有多少个线程进行调用m方法,在同一个时刻,只可以有一个线程进行调用m方法
4.使用synchronized解决售票问题
线程同步原理
分析:
当如图 t1 t2 t3三个线程进去到某一个由synchronized修饰的方法的时候,会有一把锁。然后这三个线程去抢这一把锁,假设t1抢到了这把锁之后,t2 t3只能在门口进行等待,当t1执行完之后把锁还了回去,那么此时再一次线程t1 t2 t3再一次进行抢锁的操作,三者抢到锁的可能性还是一样的。反复进行,但是同一时刻这个方法的执行只可以有一个线程进来。。。。
【这把锁是在对象obj上加的,是在对象的位上。所以这个锁是修饰对象的】
互斥锁![](https://img-blog.csdnimg.cn/4e92bc82304e4d7db7438853b37b2cd3.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5qKF6KW_5aaC5p-S,size_20,color_FFFFFF,t_70,g_se,x_16)
1.如果synchronized是加在非静态方法上:
那么锁可以是this,也可以是其他对象(要求是同一个对象)
如图:
我们也可以在代码块上写synchronized(this):表示这是一个同步代码块。。。
2.如果synchronized是加在静态方法上:
那么锁其实是当前类本身
互斥锁细节:
同步代码块或者同步方法都会使得程序效率降低,所以我们使用同步代码块比较好。因为同步代码块控制的代码范围更加具体,使得效率可以进一步提升。。。。
并且多个线程作用的锁对象是同一个:
但是下图不是代表同一个锁对象,所以不可以写this来代表同一个锁对象:
锁对象不同时:如图:new Object()每一次new代表不同的锁对象
不同的锁对象,就好像是三个门,一个马桶。。。。
线程的死锁
——————————————————————
经典死锁演示:
分析:
如果flag为true时,先等待进入第一个同步代码块,线程A会获得o1这把锁,并且进入第一个同步代码块。等待进入第二个同步代码块,然后尝试获得o2这把锁,如果o2这把锁能够获得,那么就进入第二个同步代码块并且执行代码。执行完之后,释放锁o2;之后释放锁o1;如果不能获得,那么就Blocked。
如果flag为false,同理。进入同步代码块的锁是o2,然后尝试获得o1这把锁,如果可以获得。。。。。同上分析即可
当两个线程同时获得锁o1和锁o2的时候,
那么A线程就不可能再获得o2这把锁,那么就只能Blocked
同理B线程也就不可能再获得o1这把锁,那么也就Blocked
形成死锁。。。。。
——————————————————————————————
如下图所示:
执行流程:
线程进来的时候,先等待进入同步代码块的锁,获得锁对象,进入同步代码块执行。如果获得不了锁对象,Blocked。
释放锁
过时的方法:
练习:
细节1:输出100以内的整数
细节2:输入一个整形字符
细节3:
注意要把A类对象引用传给B类,
不然没办法实现从一个线程控制另外一个线程
代码如下:
分析【重点】:
t1和t2这两个线程过来之后,等待着进入同步代码块。两个线程进行争夺锁,锁对象只有一个,当t1获取这把锁之后,t1线程进入同步代码块,并且执行代码。t2Blocked在外面。当执行完之后,t1释放锁对象,t1和t2重新到这个地方进行争夺锁,抢到锁的进入执行方法或代码块语句。没有抢到锁的在外面被Blocked
注意: