首页 > 编程语言 >java_day18_多线程、线程安全问题、死锁、等待唤醒机制

java_day18_多线程、线程安全问题、死锁、等待唤醒机制

时间:2024-10-19 11:01:21浏览次数:6  
标签:java Thread void class 死锁 线程 new 多线程 public

一、线程

1、多线程

进程:是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。
    举例:IDEA, 阿里云盘, wegame, steam
线程:是进程中的单个顺序控制流,是一条执行路径
    一个进程如果只有一条执行路径,则称为单线程程序。
    一个进程如果有多条执行路径,则称为多线程程序。

思考:
    java程序启动时,是单线程程序还是多线程程序?多线程程序的【主线程,垃圾回收线程】


java提供了一个类用来描述线程:Thread
    线程是程序中执行的线程。
    Java虚拟机允许应用程序同时执行多个执行线程。
    每个线程都有优先权, 具有较高优先级的线程优先于优先级较低的线程执行。

创建线程的第一种方式:

将一个类声明为Thread的子类。 这个子类应该重写Thread类的run方法

代码案例:

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //run方法是将来线程对象启动时要执行的计算逻辑
        for(int i=1;i<=200;i++){
            System.out.println(getName()+" - "+i);
        }
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建一个线程对象
//        MyThread t1 = new MyThread();
//        MyThread t2 = new MyThread();
        MyThread t1 = new MyThread("李刚");
        MyThread t2 = new MyThread("钱志强");
        //给线程起名字
        // 方式1:调用setName()方法起名字
//        t1.setName("李刚");
//        t2.setName("钱志强");
        // 方式2:使用构造方法起名字

//        t1.run();
//        t2.run();
        t1.start(); // 系统分配资源给线程t1,启动线程,t1线程具备了执行的资格,具体等到抢到cpu执行权的时候才会执行
        t2.start();
    }
}

创建线程的第二种方式:

创建线程的第二种方式:实现Runnable接口,借助Thread类创建线程对象

若将来每一个线程执行的逻辑是一样的话,推荐采用第二种实现Runnable接口方式实现多线程
若将来每一个线程执行逻辑不一样的话,推荐采用第一种继承Thread类方式实现多线程

代码案例:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 200; i++) {
            //可以先通过获取当前执行的线程对象,调用内部getName()方法
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}

public class RunnableDemo1 {
    public static void main(String[] args) {
        //创建Runnable对象
        MyRunnable r1 = new MyRunnable();
        //创建线程对象
//        Thread t1 = new Thread(r1);
//        Thread t2 = new Thread(r1);
//        Thread t3 = new Thread(r1);
        //创建线程对象同时起名字
        Thread t1 = new Thread(r1, "李刚");
        Thread t2 = new Thread(r1, "祝帅");
        Thread t3 = new Thread(r1, "吴问强");

        //获取线程优先权 默认线程优先级是 5
//        System.out.println("t1: "+t1.getPriority());
//        System.out.println("t2: "+t2.getPriority());
//        System.out.println("t3: "+t3.getPriority());
        t1.setPriority(1); // 1-10
        t1.setPriority(10);
        t1.setPriority(1);

        t1.start();
        t2.start();
        t3.start();
    }
}

休眠线程

休眠线程
public static void sleep(long millis)
代码案例:
class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("我是李刚,现在开始睡觉了...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("我醒了,现在开始学习!!");
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        myThread2.start();
    }
}

加入线程
加入线程:
 public final void join()

代码案例:
class MyThread3 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 200; i++) {
            System.out.println(getName() + "-" + i);
        }
    }
}

public class ThreadDemo3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread3 t1 = new MyThread3();
        MyThread3 t2 = new MyThread3();
        MyThread3 t3 = new MyThread3();


        t1.start();
        t1.join();

        t2.start();
        t3.start();
    }
}
礼让线程

    礼让:
        public static void yield()


代码案例
class MyThread4 extends Thread{
    @Override
    public void run() {
        for(int i=1;i<=200;i++){
            System.out.println(getName()+"- "+i);
            Thread.yield();
        }
    }
}

public class ThreadDemo4 {
    public static void main(String[] args) {
        MyThread4 t1 = new MyThread4();
        MyThread4 t2 = new MyThread4();

        t1.start();
        t2.start();
    }
}

后台线程

后台线程:
    public final void setDaemon(boolean on)


用户线程:优先级高于守护线程
守护线程【后台线程】:当一个程序没有了用户线程,守护线程也就没有了

代码案例:

class MyThread5 extends Thread{
    @Override
    public void run() {
        for(int i=1;i<=200;i++){
            System.out.println(getName()+"- "+i);
        }
    }
}

public class ThreadDemo5 {
    public static void main(String[] args) {
        MyThread5 t1 = new MyThread5();
        MyThread5 t2 = new MyThread5();
        MyThread5 t3 = new MyThread5();

        t1.setName("刘备");
        t2.setName("关羽");
        t3.setName("张飞");

        //将t2和t3线程设置为守护线程
        t2.setDaemon(true);
        t3.setDaemon(true);

        t1.start();
        t2.start();
        t3.start();
    }
}

中断线程

中断线程:
    public final void stop()
    public void interrupt()

java所有的线程要想变成运行状态,必须经过抢cpu执行权

代码案例:

class MyThread6 extends Thread{
    @Override
    public void run() {
        System.out.println("胡海祥准备睡觉了....睡足10秒钟");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("睡醒了,继续敲代码!!!");
    }
}

public class ThreadDemo6 {
    public static void main(String[] args) {
        MyThread6 t1 = new MyThread6();
        t1.start();

        try {
            Thread.sleep(5000);
//            t1.stop();
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

二、线程安全问题

使用实现Runnable接口的方式实现售票

问题1:我们加入了循环和延迟模拟现实生活售票的场景后发现
    1. 出现售卖重复的票号 【计算机中cpu的计算是具备原子性的】
    2. 出现非法的票号  【随机性导致的,cpu小小的时间片,足以执行很多次】

上述的问题1实际上是属于线程安全的问题。
如何判断一个程序是否存在线程安全的问题呢?
三要素,缺一不可:
    1、是否存在多线程环境? 是
    2、是否存在共享数据/共享变量?是tickets
    3、是否存在多条语句操作着共享数据/共享变量? 是

怎么解决?
    方案1:同步代码块
        synchronized(对象){需要同步的代码;}  这里对象要保证多个线程对象唯一的
        同步方法:锁对象是this
        同步静态方法:锁对象是当前类的class文件对象   类.class
    方案2:lock锁

同步代码块

class Window2 implements Runnable{
    int tickets = 200;
    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if(tickets>0){ // 1
                    try {
                        // t1
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("当前 " + Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 张票");
                }
            }

        }
    }
}


public class SellTicketsDemo2 {
    public static void main(String[] args) {
        Window2 window2 = new Window2();

        Thread t1 = new Thread(window2, "窗口1");
        Thread t2 = new Thread(window2, "窗口2");
        Thread t3 = new Thread(window2, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法

同步方法:锁对象是this
同步静态方法:锁对象是当前类的class文件对象   类.class
class Window3 implements Runnable {
    static int tickets = 200;
    int i = 0;

    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                synchronized (Window3.class) {
                    if (tickets > 0) { // 1
                        try {
                            // t1
                            Thread.sleep(20);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
                    }
                }
            } else {
                sell();
            }

            i++;
        }
    }

    public synchronized static void sell() {
        if (tickets > 0) { // 1
            try {
                // t1
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
        }
    }


//    public synchronized void sell() {
//        if (tickets > 0) { // 1
//            try {
//                // t1
//                Thread.sleep(20);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//
//            System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
//        }
//    }
}


public class SellTicketsDemo3 {
    public static void main(String[] args) {
        Window3 window3 = new Window3();

        Thread t1 = new Thread(window3, "窗口1");
        Thread t2 = new Thread(window3, "窗口2");
        Thread t3 = new Thread(window3, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

使用lock锁来解决线程安全的问题

class Window4 implements Runnable {
    int tickets = 200;
    Object obj = new Object();
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 加锁
            lock.lock();
            if (tickets > 0) { // 1
                try {
                    // t1
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
            }
            //释放锁
            lock.unlock();
        }
    }
}


public class SellTicketsDemo4 {
    public static void main(String[] args) {
        Window4 window4 = new Window4();

        Thread t1 = new Thread(window4, "窗口1");
        Thread t2 = new Thread(window4, "窗口2");
        Thread t3 = new Thread(window4, "窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

三、死锁

死锁:线程之间存在相互等待的现象

案例:中国人和外国人
    前提:中国人吃饭必须要两支筷子,外国人吃饭必须一把刀和一把叉
    现在:
        中国人:一支筷子和一把刀
        外国人:一支筷子和一把叉

代码案例:

class Person extends Thread{
    boolean flag;

    public Person(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (Locks.LOCK1){
                System.out.println("if lock1");
                // p1
                synchronized (Locks.LOCK2){
                    System.out.println("if lock2");
                }
            }
        }else {
            synchronized (Locks.LOCK2){
                System.out.println("else lock2");
                // p2
                synchronized (Locks.LOCK1){
                    System.out.println("else lock1");
                }
            }
        }
    }
}

public class DieLock {
    public static void main(String[] args) {
        Person p1 = new Person(true);
        Person p2 = new Person(false);

        p1.start();
        p2.start();
    }
}

四、等待唤醒机制

等待唤醒机制:
    生产者
    消费者
    共享数据
    测试类

等待唤醒机制:前提是要保证程序是线程安全的

生产者

public class Product extends Thread{

    Student s;
    int i =0;

    public Product(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while (true){
            synchronized (s){
                //作为生产者,在生产数据之前,应该先检查一下数据有没有被消费
                //如果没有被消费,就等待消费者消费
                if(s.flag){
                    //等待 锁对象调用方法等待
                    try {
                        s.wait(); // 程序走到这一步,发生阻塞,直到锁对象再次在程序中被调用了notify()方法
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


                if(i%2==0){
                    s.setName("李刚");
                    s.setAge(18);
                }else {
                    s.setName("钱志强");
                    s.setAge(10);
                }

                // 生产完数据后,通知消费者来消费数据
                // 由锁对象来通知
                s.notify();
                s.setFlag(true);

                i++;
            }
        }
    }
}

消费者

public class Consumer extends Thread{
    Student s;

    public Consumer(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
//        Student s = new Student();
        while (true){
            synchronized (s){
                //消费者在消费数据之前,应该先看一看数据有没有产生【flag是否是true】
                //若没有数据产生,等待生产者生产数据
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.getName()+"-"+s.getAge());

                //消费者消费完数据后,通知生产者生产数据
                s.notify();
                s.setFlag(false);
            }

        }
    }
}

共享数据

public class Student {
    private String name;
    private int age;
    boolean flag = false;


    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试类

public class WaitNotifyDemo1 {
    public static void main(String[] args) {
        Student s = new Student();

        //创建生产者线程对象
        Product product = new Product(s);
        //创建消费者线程对象
        Consumer consumer = new Consumer(s);

        product.start();
        consumer.start();
    }
}

标签:java,Thread,void,class,死锁,线程,new,多线程,public
From: https://www.cnblogs.com/w-ll/p/18475615

相关文章

  • 使用 Java 实现验证码识别与自动化登录
    安装所需依赖首先,确保你已经安装了JDK。可以使用Maven来管理依赖。在pom.xml中添加以下依赖:xmlorg.apache.httpcomponentshttpclient4.5.13net.sourceforge.tess4jtess4j5.3.02.下载验证码图片使用ApacheHttpClient下载验证码图片并保存到本地:javaim......
  • Java Spring详解 -- 看完超越99%的同行
    Spring的核心功能IOC(控制反转,依赖注入),AOP(面向切面的编程)IOC:我们在使用过程中不用关注于对象是怎么创建的,只用应用过去,sping自动帮我们完成注入,对象的创建,spring默认创建对象是单例,这样减少了频繁创建对象,让对象重复利用,所有的对象都是放在BeanFactory工厂的AOP:面向切面的编程,......
  • Java 实战虚拟机 进阶 (一 万字)
    实战Java虚拟机-高级篇什么是GraalVMGraalVM是Oracle官方推出的一款**高性能JDK,**使用它享受比OpenJDK或者OracleJDK更好的性能。GraalVM的官方网址:https://www.graalvm.org/官方标语:Buildfaster,smaller,leanerapplications.更低的CPU、内存使用率......
  • Java 初学 day15
    java151、CollectionsCollections:是java针对集合操作专门提供的一个工具类静态方法publicstatic<T>voidsort(List<T>list)publicstatic<T>intbinarySearch(List<?>list,Tkey)publicstatic<T>Tmax(Collection<?>coll)publicstaticvoi......
  • 基于Java大数据背景下求职信息的推荐系统设计与实现
    基于Java大数据背景下求职信息的推荐系统设计与实现计算机毕业设计案例基于Java的食谱/菜谱管理系统基于Java的老年人健康管理系统基于Java的课程评价系统基于微信小程序的充电桩管理系统基于ASP.NET的OA系统基于PHP的在线学习网基于Java的老年人健康管理系统基于S......
  • 使用AES 128位加解密,加解密模式采用CBC,填充模式采用PKCS5Padding的Java工具方法示例
    importjavax.crypto.Cipher;importjavax.crypto.spec.IvParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjava.util.Base64;publicclassAESUtils{privatestaticfinalStringAES_ALGORITHM="AES/CBC/PKCS5Padding";private......
  • java_day17_JDBC、登录注册修改案例
    一、JDBCJDBC编写六步走:1、注册驱动,告诉java程序我们要链接什么数据库【mysql为案例】5.1.x驱动包中的驱动类路径:【com.mysql.jdbc.Driver】8.x.x驱动包中的驱动类路径:【com.mysql.cj.jdbc.Driver】2、创建与数据库的链接对象......
  • 【Java系列】基于SSM框架的房屋中介服务平台设计与实现(源码+文档+部署讲解等)
    文章目录1.前言2.详细视频演示3.程序运行示例图4.文档参考5.技术框架5.1后端采用SpringBoot框架5.2前端框架Vue5.3程序操作流程6.选题推荐7.原创毕设案例8.系统测试8.1系统测试的目的8.2系统功能测试9.代码参考10.为什么选择我?11.获取源码1.前言......
  • java中的类型转换
    一、自动类型转换1.在java中,变量参与运算的时候会自动提升数据类型byte,short,char->int->long->float->double二、强制类型转换1.语句格式:目标数据类型变量名=(目标数据类型)要转型的变量值或者表达式publicclassDataTypeDemo2{publicstaticvoidmain(Str......
  • 【Java系列】基于Javaweb的在线餐饮管理系统设计与实现(源码+文档+部署讲解等)
    文章目录1.前言2.详细视频演示3.程序运行示例图4.文档参考5.技术框架5.1后端采用SpringBoot框架5.2前端框架Vue5.3程序操作流程6.选题推荐7.原创毕设案例8.系统测试8.1系统测试的目的8.2系统功能测试9.代码参考10.为什么选择我?11.获取源码1.前言......