多线程学习之解决线程同步的实现方法

简介: 多线程学习之解决线程同步的实现方法

一、卖票的多线程实现

需求:共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

代码实现:

/**
 * @Author:kkoneone11
 * @name:SellTicket1
 * @Date:2023/8/26 11:32
 */
public class SellTicket1 implements Runnable{
    private int tickets = 100;
    @Override
    public void run() {
        while(true){
            if(tickets < 0){
                break;
            }else {
                try{
                    Thread.sleep(100);
                }catch (Exception e){
                    e.printStackTrace();
                }
                tickets--;
                System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket1 st = new SellTicket1();
        Thread thread1 = new Thread(st, "窗口1");
        Thread thread2 = new Thread(st, "窗口2");
        Thread thread3 = new Thread(st, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

可以看到这种程序写法的问题有:

  • 相同的票出现了多次
  • 出现了负数的票  

问题产生的原因分析:这种多线程共享的是同一份数据,线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

二、解决问题的方案

要解决这个问题实际上就是让程序没有安全问题,如何实现其实就是让每次操作的时候只能有一个线程执行成功即可,那么可以实现的方案如下:

同步代码块

实现方法:

synchronized(任意对象) { 
  多条语句操作共享数据的代码 
}

优缺点:

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

实例:

public class SellTicket1 implements Runnable{
    private int tickets = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                //当线程进来的时候就会把这段代码锁起来
                if(tickets <= 0){
                    break;
                }else {
                    try{
                        Thread.sleep(100);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    tickets--;
                    System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
                }
            }
            //到此处锁就会释放了
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket1 st = new SellTicket1();
        Thread thread1 = new Thread(st, "窗口1");
        Thread thread2 = new Thread(st, "窗口2");
        Thread thread3 = new Thread(st, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步方法

实现方法:

锁住的对象是:this

修饰符 synchronized 返回值类型 方法名(方法参数) { 
  方法体;
}

静态同步方法

实现方法:

锁住的对象是:类名.class

修饰符 static synchronized 返回值类型 方法名(方法参数) { 
  方法体;
}

实例:

public class SellTicket1 implements Runnable{
    private static int tickets = 100;
    @Override
    public void run() {
        while(true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean b = synchronizedMthod();
                if(b){
                    break;
                }
            }else if("窗口二".equals(Thread.currentThread().getName())){
                //同步代码块
                synchronized (SellTicket1.class){
                    if(tickets == 0){
                        break;
                    }else{
                        try{
                            Thread.sleep(100);
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                        tickets--;
                        System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
                    }
                }
            }
        }
    }
    private static synchronized boolean synchronizedMthod(){
        if(tickets == 0){
            return true;
        }else{
            try{
                Thread.sleep(100);
            }catch (Exception e){
                e.printStackTrace();
            }
            tickets--;
            System.out.println(Thread.currentThread().getName() + "票数还剩余" + tickets);
            return false;
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket1 st = new SellTicket1();
        Thread thread1 = new Thread(st, "窗口1");
        Thread thread2 = new Thread(st, "窗口2");
        Thread thread3 = new Thread(st, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

总结:

无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。

ReentrantLock()

如果我们想可以直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

方法名 说明
void lock() 获得锁
void unlock() 释放锁

实例:

public class SellTicket1 implements Runnable{
    //票的数量
    private int tickets = 100;
    private Object obj = new Object();
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //synchronized (obj){//多个线程必须使用同一把锁.
            try {
                lock.lock();
                if (tickets <= 0) {
                    //卖完了
                    break;
                } else {
                    Thread.sleep(100);
                    tickets--;
                    System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + tickets + "张票");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            // }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket1 st = new SellTicket1();
        Thread thread1 = new Thread(st, "窗口1");
        Thread thread2 = new Thread(st, "窗口2");
        Thread thread3 = new Thread(st, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
目录
相关文章
|
26天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
56 1
|
24天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
24天前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
34 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
54 2
|
3月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
34 1
|
2月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
82 0
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
71 1
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
49 3
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
56 1