欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

16_多线程

程序员文章站 2022-03-22 19:00:56
多线程 什么是进程? 应用程序的一次运行产生进程。 为什么存在进程的概念? 什么是线程 参考:https://www.cnblogs.com/geeta/p/9474051.html 线程和进程区别 案例:理解上课的进程 实现多线程 继承Thread类 package cn.sxt01.thread ......
多线程
什么是进程?
应用程序的一次运行产生进程。
 
为什么存在进程的概念?
 
什么是线程
参考:https://www.cnblogs.com/geeta/p/9474051.html
 
线程和进程区别
16_多线程
 
案例:理解上课的进程
 
实现多线程
继承thread类
 
package cn.sxt01.thread01;
public class mythread extends thread {
@override
public void run() {
for (int i = 0; i < 10; i++) {
system.out.println("mythread:" + i);
}
}
}
package cn.sxt01.thread01;
public class test01 {
public static void main(string[] args) {
 
// 【1】创建一个线程并执行
mythread mythread = new mythread();
mythread.start();
 
// main线程也称主线程
for (int i = 0; i < 10; i++) {
system.out.println("mainthread:" + i);
}
 
}
}
 
test01中存在两个线程,一个是main线程,也称主线程。另外一个是mythread线程。
两个线程抢占cpu,所以程序运行轨迹不确定。
 
实现runnable接口
runnable接口表示实现类是否具有在多线程中执行的能力。
package cn.sxt01.thread01;
public class myrunnable implements runnable{
@override
public void run() {
for (int i = 0; i < 10; i++) {
system.out.println("myrunnable:" + i);
}
}
}
 
案例:卖票
 
package cn.sxt02.thread02;
public class ticketthread extends thread {
private static int count = 5;
public ticketthread(string name) {
super(name);
}
@override
public void run() {
// 模拟买票
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(super.getname() + "卖出一张,还剩" + count + "张");
}
}
}
}
窗口a卖出一张,还剩4张
窗口b卖出一张,还剩2张
窗口c卖出一张,还剩3张
窗口b卖出一张,还剩0张
窗口a卖出一张,还剩1张
 
分析运行轨迹
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(super.getname() + "卖出一张,还剩" + count + "张");
}
}
a抢占到cpu,count(5),条件成立,count--,输出
窗口a卖出一张,还剩4张
c抢占到cpu,
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(super.getname() + "卖出一张,还剩" + count + "张");
}
}
c开始执行,执行到super.getname() + "卖出一张,还剩" + count + "张" 准备好,线程c挂起。
b抢占到cpu
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(super.getname() + "卖出一张,还剩" + count + "张");
}
}
b开始执行,指定到输出位置,输出
窗口b卖出一张,还剩2张
cpu时间片到,
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(super.getname() + "卖出一张,还剩" + count + "张");
}
}
c抢占到cpu,直接冲上次挂起位置开始执行,输出
窗口c卖出一张,还剩3张
 
结论:
[1]线程在执行过程中,在任意位置时,都有可能被挂起。下次抢占到cpu后,从挂起位置开始执行。
[2]当有多个线程访问共享数据时,会出现数据错乱。
[3]多线程提高了cpu利用率,但同时程序的复杂度增加。
public class myrun implements runnable {
 
private int count = 5;
@override
public void run() {
// 模拟买票
for (int i = 0; i < 5; i++) {
if (count > 0) {
count--;
system.out.println(thread.currentthread().getname()+"卖出一张,还剩" + count + "张");
}
}
}
}
package cn.sxt02.thread02;
public class test02 {
public static void main(string[] args) {
 
myrun run = new myrun();
 
thread t1 = new thread(run,"窗口a");
thread t2 = new thread(run,"窗口b");
thread t3 = new thread(run,"窗口c");
 
t1.start();
t2.start();
t3.start();
}
}
 
比较thread和runnable的区别
[1] 继承thread的线程类不能再继承其他父类;而实现runnable接口还可以继承其他类。
[2] 实现runnable接口的线程实现类,更便于多个线程共享资源。
 
 
 
 
线程的生命周期
生命周期
新生状态
用new关键字建立一个线程后,该线程对象就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态。
就绪状态
处于就绪状态线程具备了运行条件,但还没分配到cpu,处于线程就绪队列,等待系统为其分配cpu。
当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称为“cpu调度”。
运行状态
在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。
如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态。
阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待i/o设备等资源,将让出cpu并暂时停止自己运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的i/o设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。
死亡状态
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过stop方法来终止一个线程【不推荐使用】;三是线程抛出未捕获的异常。
 
线程常见方法
[1]优先级
package cn.sxt03.thread03;
public class test02priority {
public static void main(string[] args) {
 
 
system.out.println(thread.currentthread().getpriority());
 
// 线程优先级的最大最小默认值
system.out.println(thread.max_priority);
system.out.println(thread.min_priority);
system.out.println(thread.norm_priority);
 
thread02 t2 = new thread02("线程b");
t2.setpriority(thread.norm_priority);
 
thread02 t1 = new thread02("线程a");
t1.setpriority(thread.max_priority);
 
t1.start();
t2.start();
}
}
线程优先级越大,表示被cpu调度的可能性增大,并不一定先执行。
 
[2]isalive 检测线程是否处于活动状态。
package cn.sxt03.thread03;
public class test03isalive {
public static void main(string[] args) {
 
thread t1 = new thread("线程a");
system.out.println(t1.isalive());
t1.start();
system.out.println(t1.isalive());
 
}
}
 
[3]join 线程的强行执行
调用该方法的线程强制执行,其它线程处于阻塞状态,该线程执行完毕后,其它线程再执行
public class test04join {
public static void main(string[] args) {
 
thread04 t1 = new thread04("线程a");
t1.start();
 
for (int i = 0; i < 5; i++) {
if(i == 3) {
try {
t1.join();
} catch (interruptedexception e) {
e.printstacktrace();
}
}
system.out.println("mainthread" + " i=" + i);
}
}
}
 
[4]sleep 线程休眠
当前线程休眠,线程进入阻塞状态。到休眠时间到,进入就绪状态。在休眠过程中线程可以被中断。
 
 
[5]yield 线程礼让
public class test06yield {
public static void main(string[] args) {
 
 
thread06 t1 = new thread06("线程a");
t1.start();
 
 
for (int i = 0; i < 5; i++) {
if(i == 3) {
thread.yield();
}
system.out.println("mainthread" + " i=" + i);
}
}
}
可能的结果
mainthread i=0
mainthread i=1
mainthread i=2 => 主线程礼让一次,下次有可能调度主线程,也有可能调度t1
mainthread i=3
mainthread i=4
线程a i=0
线程a i=1
线程a i=2
线程a i=3
线程a i=4
 
线程礼让后,线程进入就绪状态。
线程礼让让当前线程进入就绪状态(礼让一次),但调度者有可能再次调度礼让的线程。
 
[6]stop 线程终止
线程终止存在安全隐患,容易导致被锁的资源无法释放,不建议使用。通常使用interrupt代替中断线程,中断线程并通过异常捕获,线程继续执行并正常结束。
 
线程安全
多线程中允许多个线程访问同一个资源(共享资源),多个线程可以对共享资源进行破坏性操作容易导致数据错乱的问题。
 
原子性操作:逻辑上认为多句代码之间应该属于整体的,要么都执行,要么都不执行。不允许存在执行一半的情况。把这样的操作称为原子性操作。
同步代码块
如果是少量的代码,可以把原子性操作放入同步代码块中,使用关键字synchronized语法
synchronized ( 同步监视器/互斥锁 ){
原子性操作
}
同步监视器/互斥锁一定是对象类型。
 
package cn.sxt04.thread04;
public class myrun implements runnable {
 
private int count = 5;
@override
public void run() {
// 模拟买票
for (int i = 0; i < 5; i++) {
// mutex 互斥锁
// 通常将当前对象作为同步对象/互斥锁
synchronized (this) {
if (count > 0) {
count--;
 
try {
thread.sleep(2000);
} catch (interruptedexception e) {
e.printstacktrace();
}
 
system.out.println(thread.currentthread().getname() + "卖出一张,还剩" + count + "张");
}
}
}
}
}
 
同步代码块本质上是给共享资源加锁。锁会导致其他访问共享资源的线程阻塞。
同步方法
如果代码量多,可以考虑使用同步方法。
package cn.sxt04.thread04;
public class myrun implements runnable {
private int count = 5;
@override
public void run() {
// 模拟排队买票
for (int i = 0; i < 5; i++) {
this.buyticket();
}
}
// 同步方法默认把当前对象this加锁
public synchronized void buyticket() {
if (count > 0) {
count--;
try {
thread.sleep(2000);
} catch (interruptedexception e) {
e.printstacktrace();
}
system.out.println(thread.currentthread().getname() + "卖出一张,还剩" + count + "张");
}
}
}
 
同步监视器
  • synchronized(obj){}中的obj称为同步监视器
  • 同步代码块中同步监视器可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的监视器是this,也就是该对象本身
 
同步监视器的执行过程
  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器未锁,锁定并访问
 
死锁(c)
当线程a拥有一个资源r1,再去申请另外一个资源r2时;此时线程b拥有r2资源,再去申请r1,此时两个线程陷入互相等待,陷入阻塞,此时就绪队列为空,cpu空转。
 
package cn.sxt05.thread07;
public class threada extends thread{
private object r1;
private object r2;
 
public threada(object r1, object r2) {
super();
this.r1 = r1;
this.r2 = r2;
}
@override
public void run() {
synchronized (r1) {
system.out.println("已拥有r1");
 
synchronized (r2) {
system.out.println("想申请拥有r2");
}
}
}
}