Java多线程2

本文详细介绍了Java线程的生命周期,包括新建、就绪、运行、阻塞和死亡状态,并通过实例解析了线程API的使用,如线程状态获取、休眠、名字、优先级、礼让和join()方法。此外,还讨论了线程停止的推荐做法、线程同步的实现及优缺点,以及线程阻塞、死锁的概念和避免策略。
摘要由CSDN通过智能技术生成


前言:
Java多线程前半部分文章里,我们大致学习了多线程的定义以及如何用java实现多线程。因此我们继续来学习多线程。

一、线程的生命周期

首先,我们在编写Java代码程序时,要先了解程序是什么样子的过程,才能用JAVA语言描述出来,而线程也是这样的,我们要了解线程怎样开始,到怎样结束才好来写代码。其中对于线程来说,它有这它自己独特的生命周期。

线程生命周期图:
在这里插入图片描述

  1. 新建状态(通过构造): 线程new出来,但是还没有调用start()
  2. 就绪状态(通过start()方法):线程调用start(),没有抢占到CPU资源
  3. 运行状态(通过资源调度,获取资源):线程已经抢占到CPU资源,执行run()方法
  4. 阻塞状态:调用sleep(),没有获取到锁,wait()…线程挂起(暂停)
  5. 死亡状态:正常死亡(run方法结束),非正常死亡(stop(),destory()都已过时)

**例子:**跑步
(1)有sleep()阻塞:

/**
*有sleep()阻塞
 * 跑步类
 */
public class Run implements Runnable{
    
    private static String WIN;//赢的人
    @Override
    public void run() {
        int a = (int) (Math.random() * 3 + 1);//1<=a<3
        for (int i = 1; i <= 5; i++) {
            try {
                Thread.sleep(a * 1000);
                System.out.println(Thread.currentThread().getName() + "  跑完了第" + i + "圈  用时" + a + "秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (i == 5) {
                System.out.println(Thread.currentThread().getName() + "跑完了");
            }
        }
    }
}

(2)无sleep()阻塞:

/**
*无sleep()阻塞
 * 跑步类
 */
public class Run implements Runnable{
    
    private static String WIN;//赢的人
    @Override
    public void run() {
        int a = (int) (Math.random() * 3 + 1);//1<=a<3
        for (int i = 1; i <= 5; i++) {
            // Thread.sleep(a * 1000);
            System.out.println(Thread.currentThread().getName() + "  跑完了第" + i + "圈  用时" + a + "秒");
            if (i == 5) {
                System.out.println(Thread.currentThread().getName() + "跑完了");
            }
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        Run run=new Run();
        Thread t1=new Thread(run,"张三");
        Thread t2=new Thread(run,"李四");
        Thread t3=new Thread(run,"王五");
        t1.start();
        t2.start();
        t3.start();
    }
}

结果(1):

张三  跑完了第1圈  用时1秒
王五  跑完了第1圈  用时2秒
张三  跑完了第2圈  用时1秒
李四  跑完了第1圈  用时3秒
张三  跑完了第3圈  用时1秒
王五  跑完了第2圈  用时2秒
张三  跑完了第4圈  用时1秒
张三  跑完了第5圈  用时1秒
张三跑完了
李四  跑完了第2圈  用时3秒
王五  跑完了第3圈  用时2秒
王五  跑完了第4圈  用时2秒
李四  跑完了第3圈  用时3秒
王五  跑完了第5圈  用时2秒
王五跑完了
李四  跑完了第4圈  用时3秒
李四  跑完了第5圈  用时3秒
李四跑完了

结果(2):

张三  跑完了第1圈  用时1秒
张三  跑完了第2圈  用时1秒
张三  跑完了第3圈  用时1秒
张三  跑完了第4圈  用时1秒
张三  跑完了第5圈  用时1秒
张三跑完了
李四  跑完了第1圈  用时1秒
李四  跑完了第2圈  用时1秒
李四  跑完了第3圈  用时1秒
李四  跑完了第4圈  用时1秒
李四  跑完了第5圈  用时1秒
李四跑完了
王五  跑完了第1圈  用时2秒
王五  跑完了第2圈  用时2秒
王五  跑完了第3圈  用时2秒
王五  跑完了第4圈  用时2秒
王五  跑完了第5圈  用时2秒
王五跑完了

当线程1张三在遇到sleep()时,由运行状态改为阻塞状态,当sleep()的时间到了之后,线程则变为就绪状态,这时线程1张三,线程2李四,线程3王五三者就会重新抢占CPU的资源,谁抢占得就谁先运行,就会有结果(1)。
如果一个线程没有遇到sleep()或join()或IO等阻塞条件时,就会一直进行下去,直至线程的结束,就像结果(2)一样。

二、线程的API

2.1 获取线程状态

Thread.State getState();

在这里插入图片描述

测试:

public class Test {
    public static void main(String[] args) {
        Run run=new Run();
        Thread t1=new Thread(run,"张三");
        Thread t2=new Thread(run,"李四");;
        t1.start();
        System.out.println(t1.getState());
        t2.start();
    }
}

结果:

RUNNABLE   //执行状态
张三  跑完了第1圈  用时1秒
张三  跑完了第2圈  用时1秒
张三  跑完了第3圈  用时1秒
李四  跑完了第1圈  用时3秒
张三  跑完了第4圈  用时1秒
张三  跑完了第5圈  用时1秒
张三跑完了
李四  跑完了第2圈  用时3秒
李四  跑完了第3圈  用时3秒
李四  跑完了第4圈  用时3秒
李四  跑完了第5圈  用时3秒
李四跑完

2.2 线程休眠

sleep(毫秒数),让线程休眠,时间到,自动唤醒并继续执行,它会释放cpu资源,但是不释放锁 。例子看上面

2.3 获取线程名字

Thread.currentThread().getName;   //表示获取当前正在执行的线程对象的名字

2.4 线程的优先级

线程优先极高的与线程优先级低的线程抢占资源,不一定是优先级高的一定能抢占资源,只是优先级高的抢占到资源的概率比较大。

默认优先级是五,线程优先级1-10

2.5 礼让:yield()

某个线程调用yield(),释放cpu资源,与其他线程抢占CPU资源,谁抢到谁先执行

例子:

public class MyThread2 extends Thread {
    public MyThread2(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"正在执行...");
        System.out.println(Thread.currentThread().getName()+"执行结束...");
    }

    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2("A");
        MyThread2 t2 = new MyThread2("B");
        t1.start();
        t2.start();
        t1.yield(); //礼让


    }
}

2.6 join():类似插队

先准备一个A线程,在执行A线程过程中准备B线程,并让B线程就绪,然后A线程执行到一半之后,让B线程调用join方法,此时A线程阻塞,B
线程启动执行,当B线程执行完毕后,A线程再继续执行

例子:

public class MyThread3 extends  Thread{
    public MyThread3(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println(this.getName()+">>"+i);
            if(i == 50){
                MyThread4 thread4 = new MyThread4("地主家的傻儿子");
                thread4.start();
                //插队
                try {
                    thread4.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        MyThread3 thread3 = new MyThread3("普通人");
        thread3.start();
    }
}

三、线程停止

让线程停止有两种方法:

1.调用线程stop(),已过时

2.推荐方式:设置一个标志符flag,控制标志标志符的值

例子(使用标志符):

public class MyRun extends Thread {
    private boolean flag = true;
    public MyRun(String name) {
        super(name);
    }

    @Override
    public void run() {
        int i = 0;
        while(flag){
            i++;
            System.out.println(this.getName()+">>>"+i);
        }
    }
    public void changeFlag(boolean  flag){
        this.flag = flag;
    }

    public static void main(String[] args) throws InterruptedException {
        MyRun t5 = new MyRun("t5");
        t5.start();
        //那个线程执行这行代码,就是那个线程睡眠
        Thread.sleep(1000);
        t5.changeFlag(false);
    }
}

四、线程同步

多线程并发访问一个共享资源,并且修改资源,可能出现以下情况:

1.同步方法

在方法上添加上一个Synchronied修饰符,在对象、方法上加锁

非静态同步方法:

​ 锁加在this对象

静态同步方法:

​ 锁加在类. class(对象)上

锁的释放:当把同步方法执行之后,马上释放

2.同步代码块

public 返回值类型 方法名(){
	Synchronied (对象){
	   //锁住的代码
	}
}

同步缺点:

1.效率低,排队

2.容易造成死锁

例子(多个窗口出售车票):
1、同步方法

public class SellTicketTask implements Runnable{
private int count=10;//票数
private boolean flag=true;//没有卖完

@Override
public void run() {
   //循环购票
   while (flag){
       sellTicket();
   }
}

//买票方法
//分析:买票原子性操作,给买票加锁
//把买票方法设置成同步方法
public synchronized void sellTicket(){
   if (count>0){//有票

       try {
           //线程睡眠,模拟出票所需的时间   让别的线程得到CPU的资源
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //出票
       System.out.println(Thread.currentThread().getName()+":出售第"+count+"张票");
       count--;

   }else {//没有票
       flag=false;
   }
}
}

2、同步代码块

//买票方法
//分析:买票原子性操作,给买票加锁
//把买票方法设置成同步方法
public synchronized void sellTicket(){
   System.out.println("正在出票...");
   synchronized (this){
       if (count>0){//有票

           try {
               //线程睡眠,模拟出票所需的时间   让别的线程得到CPU的资源
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           //出票
           System.out.println(Thread.currentThread().getName()+":出售第"+count+"张票");
           count--;

       }else {//没有票
           flag=false;
       }
   }
}

五、线程阻塞

1.同步阻塞

2.Thread.sleep()的方法:时间到自动醒,释放cpu资源,不释放锁资源,join() 阻塞

3.yield() 礼让

4.wait()等待 位于Object类 的方法,一定要等待唤醒notify() /notifyAll().释放cpu

资源同时也释放锁,一般在线程通讯中

六、线程死锁

死锁产生的必要条件:

​ 1> 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

​ 2> 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

​ 3> 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占用。

​ 4> 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路

​ 在我们的项目中,我们不希望看到死锁的发生,为了避免,以上四个条件,破坏其中任意一个,死锁就结束了

​ 注意:过多的同步会导致死锁

例子(模拟死锁):

/**
* 死锁
*/
public class Demo implements Runnable{
//对象死锁
public Object objA=new Object();
public Object objB=new Object();
private static boolean flag=true;//标签 true获取锁顺序: objA-objB false获取锁顺序: objB-objA


//修改flag值
/*    public void sellFlag(Boolean flag){
   this.flag=flag;
}*/
public void sellFlag(){
   flag=false;
}

public void sell(){
   if(flag==true){
       //请求objA死锁
       synchronized (objA){
           System.out.println(Thread.currentThread().getName()+"获得死锁objA");

       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //请求objB死锁
       synchronized (objB) {
           System.out.println(Thread.currentThread().getName() + "获得死锁objB");
       }
       }//从synchronized (objA)到结束,锁住整个
   }else{
       //请求objB死锁
       synchronized (objB){
           System.out.println(Thread.currentThread().getName()+"获得死锁objB");

       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       //请求objA死锁
       synchronized (objA){
           System.out.println(Thread.currentThread().getName()+"获得死锁objA");
       }
       }
   }
}

@Override
public void run() {
   sell();
}
}

测试:

public class Test {
public static void main(String[] args) {
   Demo demo=new Demo();
   new Thread(demo,"t1").start();
   try {
       Thread.sleep(50);
   } catch (InterruptedException e) {
       e.printStackTrace();
   }
   demo.sellFlag();
   new Thread(demo,"t2").start();
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小温豌豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值