【Java】线程安全,死锁,状态,通讯,线程池(一)

简介: 【Java】线程安全,死锁,状态,通讯,线程池

主要内容


  • 线程安全
  • 线程死锁
  • 线程的状态
  • 线程间通讯
  • 线程池

1 线程安全


1.1 线程安全产生的原因


  • 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了
package com.itheima.ticket_demo;
/*
    电影院
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            // 如果票的数量为0 , 那么停止买票
            if (ticketCount == 0) {
                break;
            } else {
                // 有剩余的票 , 开始卖票
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            }
        }
    }
}
package com.itheima.ticket_demo;
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();
        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}
注意 : 以上代码是有问题 , 接下来继续改进
  • 因为出票是有时间的 , 所有现在在每次买票之前, 休眠100毫秒 , 尝试执行代码
package com.itheima.ticket_demo;
/*
    电影院
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            // 如果票的数量为0 , 那么停止买票
            if (ticketCount <= 0) {
                break;
            } else {
                // 模拟出票的时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 有剩余的票 , 开始卖票
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            }
        }
    }
}
package com.itheima.ticket_demo;
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();
        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}
  • 通过上述代码的执行结果 , 发现了出现了负号票 , 和相同的票 , 数据有问题
  • 问题出现的原因 : 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

1.2 线程的同步


  • 概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性
  • 分类
  • 同步代码块
  • 同步方法
  • 锁机制。Lock

1.3 同步代码块


同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现
第一部分 : 格式
           synchronized(任意对象) {
                   多条语句操作共享数据的代码
           }
第二部分 : 注意
           1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
           2 当线程执行完出来了,锁才会自动打开
第三部分 : 同步的好处和弊端
            好处 : 解决了多线程的数据安全问题
            弊端 : 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            synchronized (Ticket.class) {
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            }
        }
    }
}
package com.itheima.synchronized_demo1;
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();
        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

1.4 同步方法


同步方法:就是把synchronized关键字加到方法上
格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }
同步代码块和同步方法的区别:
    1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
    2 同步代码块可以指定锁对象,同步方法不能指定锁对象
注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
    1 对于非static方法,同步锁就是this。
    2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象
package com.itheima.synchronized_demo2;
/*
    同步方法:就是把synchronized关键字加到方法上
    格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }
    同步代码块和同步方法的区别:
        1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
        2 同步代码块可以指定锁对象,同步方法不能指定锁对象
    注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
        1 对于非static方法,同步锁就是this。
        2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    @Override
    public void run() {
        while (true) {
            if (method()) {
                break;
            }
        }
    }
    private synchronized boolean method() {
        // 如果票的数量为0 , 那么停止买票
        if (ticketCount <= 0) {
            return true;
        } else {
            // 模拟出票的时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 有剩余的票 , 开始卖票
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            return false;
        }
    }
}
package com.itheima.synchronized_demo2;
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();
        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

1.5 Lock锁


虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock中提供了获得锁和释放锁的方法
    void lock():获得锁
    void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
    ReentrantLock的构造方法
    ReentrantLock():创建一个ReentrantLock的实例
注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
package com.itheima.synchronized_demo3;
import java.util.concurrent.locks.ReentrantLock;
/*
    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
    Lock中提供了获得锁和释放锁的方法
        void lock():获得锁
        void unlock():释放锁
    Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
        ReentrantLock的构造方法
        ReentrantLock​():创建一个ReentrantLock的实例
    注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();// 加锁
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    Thread.sleep(100);
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();// 释放锁
            }
        }
    }
}
package com.itheima.synchronized_demo3;
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程
 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();
        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}
相关文章
|
21天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
148 60
【Java并发】【线程池】带你从0-1入门线程池
|
10天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
61 23
|
17天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
86 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
117 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
58 13
|
Java
java并发编程:死锁代码示例
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
172 0
|
Java
一个简单的Java死锁示例(转)
在实际编程中,要尽量避免出现死锁的情况,但是让你故意写一个死锁的程序时似乎也不太简单(有公司会出这样的面试题),以下是一个简单的死锁例子,程序说明都写着类的注释里了,有点罗嗦,但是应该也还是表述清楚了的。
820 0
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
2月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
132 17