学习java的第四十天,线程的优先级、守护线程、线程同步机制、死锁

本文介绍了Java线程的优先级设置,如何通过getPriority和setPriority进行调整,以及守护线程的作用。重点讨论了线程同步的必要性和死锁现象,通过实例演示了同步块的使用和死锁的避免条件。
摘要由CSDN通过智能技术生成

一、线程的优先级(priority)

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围1~10
  1. Thread.Min_Priority = 1; 最小等级
  2. Thread.Max_Priority = 10; 最大等级
  3. Thread.Norm_Priority = 5; 默认等级
  • 使用以下方式改变或获取优先级
  • getPriority 获取当前等级
  • setPriority 改变当前等级(int 1~10)
package com.Thread.state;
//测试线程的优先级  Priority
public class TestPriority {
    public static void main(String[] args) {
        //主线程的优先级
        System.out.println(Thread.currentThread().getName()+"--->等级为:"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority,"t1");  //使用线程对象,将mypriority传入,定义线程名字为t1
        Thread t2 = new Thread(myPriority,"t2");
        Thread t3 = new Thread(myPriority,"t3");
        Thread t4 = new Thread(myPriority,"t4");
        Thread t5 = new Thread(myPriority,"t5");
        //先设置优先级,在启动
        t1.setPriority(2);  //设置优先级2
        t1.start();


        t2.setPriority(10);
        t2.start();

        t3.setPriority(2);
        t3.start();

        t4.setPriority(9);
        t4.start();

        t5.setPriority(6);
        t5.start();

    }


}

class MyPriority implements Runnable{

    @Override
    public void run() {
        //获取当前线程的名字+当前线程的等级!
        System.out.println(Thread.currentThread().getName()+"--->等级为:"+Thread.currentThread().getPriority());
    }
}


在这里插入图片描述

二、线程守护(Daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待。。
package com.Thread.state;
//测试守护线程
//上帝守护着你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);  //默认是false表示用户线程,正常的线程都是用户线程

        thread.start();  //上帝守护线程

        new Thread(you).start();  //用户线程启动
    }
    }

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝守护着你");
        }
    }
}

class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着!");
        }
        System.out.println("goodbye!离开世界!");
    }
}

结果图

在这里插入图片描述

三、线程同步机制

  • 现实生活中,我们会遇到“同一个资源,多个人想使用”的问题,比如:食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来。
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用!
  • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排队锁,独占资源,其他线程必须等待!使用后释放锁即可,存在以下问题:
  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起;
  2. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题!

四、三大不安全的案例

案例一:购买火车票(模拟真实抢票)
package com.Thread.syn;
//模拟不安全的买车票2
public class UnsafeBuyTicket2 {
    public static void main(String[] args) {
        BuyTicket2 buyTicket2 = new BuyTicket2();
        new Thread(buyTicket2,"学生").start();
        new Thread(buyTicket2,"老师").start();
        new Thread(buyTicket2,"黄牛党").start();
    }
}

//买票的类
class BuyTicket2 implements Runnable{
    private int Ticketnums = 10;  //票数量为10张
    boolean flag = true;
    //买票的方法
    public void buy(){
            //如果还有票,则flag为真,继续抢票
            while (flag){
                //如果没票了,则退出循环!
                if (Ticketnums<=0){
                    flag = false;
                    return;
                }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+Ticketnums--+"张票!余票为:"+Ticketnums);

        }
    }
    @Override
    public void run() {
        try {
            //模拟延时抢票
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        buy();  //调用抢票的方法!
    }
}

结果图

在这里插入图片描述

案例二:银行取钱(模拟真实场景)

package com.Thread.syn;
//模拟银行取钱 不安全的取钱
public class UnsafeBank2 {
    public static void main(String[] args) {
        //账户
        Account2 account2 = new Account2(1000,"结婚的礼金");

        //银行
        Drawing2 wo = new Drawing2(account2,510,"我");   //取钱的人
        Drawing2 duixiang = new Drawing2(account2,500,"对象");   //取钱的人

        //开启线程
        wo.start();
        duixiang.start();
    }
}

//账户
class Account2{
    //账户中的余额
    int money;
    //卡号
    String cate;

    public Account2(int money, String cate) {
        this.money = money;
        this.cate = cate;
    }
}

class Drawing2 extends Thread{
    //调用账户
    Account2 account2;

    //取了多少钱
    int drawingMoney;

    //手里有多少钱
    int money;
    //有参构造器
    public Drawing2(Account2 account2,int drawingMoney,String name){
        super(name);
        this.drawingMoney = drawingMoney;
        this.account2 = account2;
    }

    @Override
    public void run() {
        //判断账户是否有钱
        if (account2.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+",您取钱失败,账户余额不足哦!");
            return;
        }
        //模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内的余额
        account2.money = account2.money - drawingMoney;
        //手上的余额
        money = drawingMoney+money;

        //打印当前卡内的余额
        System.out.println("您当前卡内的余额为:"+account2.money);
        //打印当前您手上的余额
        System.out.println("您好,尊敬的"+this.getName()+",您的手上的余额为:"+money);
        //判断账户是否有钱

    }
}

结果图

在这里插入图片描述

案例三:ArrayList(集合存放数据)
package com.Thread.syn;

import java.util.ArrayList;
import java.util.List;

public class UnsafeList2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                //lambda表达式
                list.add(Thread.currentThread().getName());
            }).start();
        }
        //集合的长度
        System.out.println("集合的长度为:"+list.size());
    }
}


结果图
在这里插入图片描述

总结:从可以上面的代码以及运行结果能看出,线程都是不安全的,案例一(购买火车票):多人抢购到了同一张票,导致后面余票成了-1;案例二(模拟银行取钱):两个人同时取钱时,都看到了1000的余额,但是当同时去取钱的时候,超出了卡内余额,仍然可以取出来;案例三(不安全的集合):在使用ArrayList集合去添加元素时,很明显是不安全的,因为在同时添加进去的时候,并没有添加完成,便终止了添加,也就是说有多个集合存放到了同一个数组下。解决方法看下一个练习题

五、同步块

  • 同步块:synchronized(obj){}
  • Obj称之为同步监视器
  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class{反射中讲解}
  • 同步监视器的执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中代码。
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器!
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问!
案例一解决方法:
    private synchronized void buy(){
                //如果没票了,则退出循环!
                if (Ticketnums<=0){
                    flag = false;
                    return;
                }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+Ticketnums--+"张票!余票为:"+Ticketnums);
    }

结果图

总结:在买票的方法上加一个synchronized即可

案例二解决方法:
  @Override
    public void run() {
        //判断账户是否有钱
        synchronized (account2){
        if (account2.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+",您取钱失败,账户余额不足哦!");
            return;
        }
        //模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内的余额
        account2.money = account2.money - drawingMoney;
        //手上的余额
        money = drawingMoney+money;

        //打印当前卡内的余额
        System.out.println("您当前卡内的余额为:"+account2.money);
        //打印当前您手上的余额
        System.out.println("您好,尊敬的"+this.getName()+",您的手上的余额为:"+money);
        //判断账户是否有钱
        }
    }

结果图:
在这里插入图片描述

总结:在重写run方法内,加上synchronized对象锁即可,注意:锁的对象需要是操作的对象,也就是账户,并不是银行!

案例三解决方法:
 for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                synchronized (list){
                //lambda表达式
                list.add(Thread.currentThread().getName());
                }
            }).start();
        }

结果图:

在这里插入图片描述

总结:在匿名内部类中将list对象锁上!使得循环的时候依次存放!

六、死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的庆幸,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题!
package com.Thread.Lock;

//死锁:多个线程互相抱着对方需要的资源,然后行程僵持
public class DeadLock {
    public static void main(String[] args) {
        Makeup makeup1 = new Makeup(0,"小红");
        Makeup makeup2 = new Makeup(1,"小玲");
        makeup1.start();
        makeup2.start();
    }

}

//口红
class Lipstick{
}

//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
    //需要的资源只有一份 ,使用static保证只有一份!
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;  //选择使用什么化妆品
    String girlName;  //使用化妆品的人

    //构造方法
    public Makeup(int choice,String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
       //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0) {  //如果选择的是0
            synchronized (lipstick) {  //获得口红的锁
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(3000);

                synchronized (mirror) { //一秒钟后想获得镜子的
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        } else {
            synchronized (mirror) {  //获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(3000);

                synchronized (lipstick) { //一秒钟后想获得口红的
                    System.out.println(this.girlName + "获得镜子的锁");
                }
            }
        }

    }
}

结果图:

在这里插入图片描述

总结:如上所示,本应小红获得口红之后,下一步便是获得镜子,但是线程增加了同步机制,也就是synchronized,另一个线程的小玲在获取镜子,所以互不相让,导致死锁的发生,都关闭了门,无法拿到对应的锁,导致死锁!

死锁的避免方法
  • 产生死锁的四个必要条件:
  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系!

注:文章仅做个人学习日记,不做学习建议,学习来源:狂神说

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值