Java线程(下)

简介: Java线程(下)

多线程售票问题



package ticket;
public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 ticket01 = new SellTicket01();
        SellTicket01 ticket02 = new SellTicket01();
        SellTicket01 ticket03 = new SellTicket01();
        ticket01.start();
        ticket02.start();
        ticket03.start();
    }
}
class  SellTicket01 extends Thread{
    static int ticketNum =  100;
    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0 ){
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 :" + Thread.currentThread().getName() + "售出一张票" +  "  剩余票数为: "+  (--ticketNum));
        }
    }
}

运行结果 :


image-20230411105456756.png


出现超卖问题


原因:


image-20230411105725969.png


也就是在票数为 1 的时候, 他们三者同时进行售卖 ,导致三个线程同时进行,然后就会超卖两张


解决方案:


  • 通知线程退出
  • 线程中断


通知线程退出



public class Thread01 {
    public static void main(String[] args) throws Exception{
        int count = 0;
        //1. 创建一个cat对象,可以当作一个线程来使用
        Cat cat = new Cat();
        cat.start();
        //休息10 秒 让其退出
        Thread.sleep(10000);
        cat.loop = false;
        //当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
    }
}
class Cat extends Thread{
    int times = 0;
    boolean loop = true;
    //todo 通过重写run方法,来实现自己的业务逻辑
    @Override
    public void run() { // 实现了Runnable接口的run方法
        while(loop){
            System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName());
            //让其休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在main方法中控制变量,让另一个线程退出


运行结果:


image-20230411112432368.png


线程中断



就是让正在休眠的线程停止休眠, 继续工作的意思


package Thread;
public class Thread01 {
    public static void main(String[] args) throws Exception{
        int count = 0;
        //1. 创建一个cat对象,可以当作一个线程来使用
        Cat cat = new Cat();
        cat.start();
        //休息10 秒 让其退出
        Thread.sleep(1000);
//        cat.loop = false;
        System.out.println("线程停止休眠 ,继续执行" );
        cat.interrupt(); //中断子线程的休眠
        //Thread.sleep(5000);
        //当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
    }
}
class Cat extends Thread{
    int times = 0;
    boolean loop = true;
    //todo 通过重写run方法,来实现自己的业务逻辑
    @Override
    public void run() { // 实现了Runnable接口的run方法
        while(loop){
            System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName());
            //让其休眠一秒
            try {
                System.out.println("休眠中--");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            }
        }
    }
}

image-20230411115031755.png


线程插队



image-20230411115249226.png

  • yield :将线程让出来,让其他线程执行
  • join : 线程的插队。 插队线程一旦插队成功 ,则肯定先执行完插入的线程所有的任务


用户、守护 线程



  • 用户线程: 也叫工作线程, 当线程的任务执行完成或者通知方式结束
  • 守护线程: 一般是为工作线程服务的,当所有的用户线程结束, 守护线程自动结束
  • 常见的守护线程 : 垃圾回收机制


如何将一个线程设置为守护线程?


public class Thread02 {
    public static void main(String[] args) throws Exception{
        Dog dog = new Dog();
        Catt catt = new Catt();
        Thread thread1 = new Thread(dog);
        //Runnable方式实现 设置为守护线程 ,然后在启动
        thread1.setDaemon(true);    
        thread1.start();
        //Thread 实现 设置为守护线程 ,然后在启动
        catt.setDaemon(true); 
        catt.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("test... ");
            Thread.sleep(1000);
        }
    }
}
class Dog implements Runnable{//通过Runnable接口实现线程
    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程1名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
class Catt extends Thread{//通过Runnable接口实现线程
    int times = 0;
    @Override
    public void run() {
       while (true){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程2 名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

通过//Runnable方式实现 设置为守护线程 ,然后在启动 thread1.setDaemon(true); 的方式实现设置守护线程


注意顺序 ,先设置 再执行


线程的生命周期



线程的7大状态


jdk官方文档中可以看出有六个


但是从我们的线程生命周期转换图可以看出 我们将Runnable(可运行状态)细化为Ready(就绪) 和Running(真正运行)两个状态


image-20230411141508232.png


线程生命周期转换图


image-20230411141628380.png

案例


public class Thread02 {
    public static void main(String[] args) throws Exception{
        Catt catt = new Catt();
        //Thread 实现 设置为守护线程 ,然后在启动
        System.out.println(catt.getName() + " 状态  " + catt.getState());
        catt.start();
        while(Thread.State.TERMINATED != catt.getState()){
            System.out.println(catt.getName() + " 状态  " + catt.getState());
            Thread.sleep(500);
        }
        System.out.println(catt.getName() + " 状态  " + catt.getState());
    }
}
class Catt extends Thread{//通过Runnable接口实现线程
    int times = 0;
    @Override
    public void run() {
       while (times != 5){
           System.out.println("执行一次" + "次数" +(++times)+ "  线程2 名:" + Thread.currentThread().getName());
           //让其休眠一秒
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}

运行结果:


image-20230411143038605.png


线程同步机制—-Synchronized



  • 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  • 也可以这样理解:线程同步,即当有一个线程在对内存进行操作试试,其他的线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作


Synchronized


同步具体方法


同步代码块


//得到对象的锁,才能操作同步代码
synchronized(对象){
    //需要被同步的代码
}


同步方法


public synchronized void m(String name){
    //需要被同步的代码
}

具体理解 ,不管有多少线程来,在某一时刻,只能有一个线程访问


分析同步原理


image-20230411145354638.png

假设 t1 抢到锁之后就开始执行代码 ,当执行完之后就会把锁放回去 ,然后t1、t2、t3 三个继续抢这个锁


互斥锁



  • java语言中引入了互斥锁的概念 ,来保证共享数据操作的完整性
  • 每个对象都对应一个可以称为“互斥锁”的标记, 这个标记用来保证在任意时刻,只能有一个线程访问
  • 同步的局限性: 导致程序的执行效率降低
  • 同步方法(静态的) 的锁为当前类本身
  • 同步方法(非静态的) 的锁可以是this ,也可以是其他对象 (必须是同一对象,可以是不同引用)


**注意事项: **


image-20230411150525051.png


线程死锁



  • 多个线程都占用了对方的所资源, 但是不肯想让 ,导致死锁, 在编程时一定要避免


image-20230411151610946.png

上述情况导致o1、o2都无法释放,导致死锁


必须避免这种两个锁互相锁


释放锁



  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致程序异常结束
  • 当前线程在同步代码块、同步方法中执行了wait()方法,当前线程暂停,并且释放锁


下面的操作不会释放锁


image-20230411152248029.png

目录
相关文章
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
17天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
4天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
22 3
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
7天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
18 1
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
8天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
34 1