当前位置: 首页 > article >正文

Java基础关键_030_线程(三)

目  录

一、死锁

 二、售票问题

1.线程安全

 2.线程通信

(1)引言

(2)wait()

(3)notify()

(4)notifyAll()

(5)实例1

(6)实例2

(7)比较 wait() 和 sleep()

 三、单例模式

1.饿汉式单例

(1)实现 

(2)实例 

2.懒汉式单例

(1)实现 

(2)实例 

3.懒汉式单例模式的线程安全

 四、ReentrantLock(可重入锁)

1.说明

2.实例

五、实现线程的方式

1.回顾之前的两种实现方式

2.实现 Callable 接口

3.使用线程池实现


一、死锁

public class MyClassThread implements Runnable {
    private Object o1;
    private Object o2;

    public MyClassThread(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        if ("t1".equals(Thread.currentThread().getName())) {
            synchronized (o1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o2) {
                    System.out.println("死锁了吧");
                }
            }
        }
        if ("t2".equals(Thread.currentThread().getName())){
            synchronized (o2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o1) {
                    System.out.println("死锁了吧");
                }
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(new MyClassThread(o1, o2));
        Thread t2 = new Thread(new MyClassThread(o1, o2));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

        程序会陷入死锁状态,即既不报错也不执行结果。所以,对于 synchronized 的使用要谨慎。


 二、售票问题

1.线程安全

        首先,借助售票问题回顾上一节的线程同步机制。

public class Ticket implements Runnable {
    private int ticket = 50;   // 总共放票50张

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket <= 0) {
                    System.out.println("票卖完了");
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "号售票成功!余票" + --ticket + "张");
            }
        }
    }
}
public class Sell {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        Thread t4 = new Thread(ticket, "窗口4");
        Thread t5 = new Thread(ticket, "窗口5");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}


 2.线程通信

(1)引言

        上边利用 synchronized 互斥锁虽然保证了线程安全,但是如何让多个线程顺次交替执行呢?

        这就涉及到了线程通信。线程通信涉及到以下三个方法,这三个方法都是 Object 类的方法。


(2)wait()

  1. wait 方法有三个重载方法:
    1. wait():调用此方法,线程进入 等待状态
    2. wait(long timeoutMillis):调用此方法,线程进入 超时等待状态
    3. wait(long timeoutMillis, int nanos):调用此方法,线程进入 超时等待状态。
  2. 调用 wait 方法需要通过共享对象调用,而不是线程对象;
  3. 例如:调用 wait() 会使在共享对象上活跃的所有线程进入无期限等待,直到该共享对象调用 notify() 唤醒。唤醒后,继上一次调用 wait() 的位置继续向下执行;
  4. 调用该方法之后,会释放之前占用的对象锁。

(3)notify()

  1. 调用该方法之后,唤醒优先级最高的等待线程。若优先级相同,则随机唤醒一个;
  2. 该方法需要通过共享对象调用,而不是线程对象。

(4)notifyAll()

  1. 调用该方法之后,唤醒在共享对象上所有等待的线程;
  2. 该方法需要通过共享对象调用,而不是线程对象。

(5)实例1

        在线程安全的售票问题代码基础上,修改 Sell.java ,示例如下:

public class Ticket implements Runnable {
    private int ticket = 50;   // 总共放票50张

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                this.notify();  // 唤醒当前线程
                if (ticket <= 0) {
                    System.out.println("票卖完了");
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "号售票成功!余票" + --ticket + "张");

                try {
                    this.wait();    // 让当前线程进入无限期等待状态
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

        可以看到,实现了线程交替执行的效果。 

        另外,需要注意的是:在 同步代码块内 或 被 synchronized 修饰的同步方法里 才能使用上述的三个方法,且 调用三个方法的共享对象 需要和 synchronized 里的共享对象一致。


(6)实例2

        题目:利用线程顺次输出字母 A、B、C,循环十次。

public class LetterThreadTest {
    private static Object lock = new Object();
    private static boolean ist1 = true;
    private static boolean ist2 = false;
    private static boolean ist3 = false;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (!ist1) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("A");
                        ist1 = false;
                        ist2 = true;
                        ist3 = false;
                        lock.notifyAll();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (!ist2) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("B");
                        ist1 = false;
                        ist2 = false;
                        ist3 = true;
                        lock.notifyAll();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (!ist3) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("C");
                        ist1 = true;
                        ist2 = false;
                        ist3 = false;
                        lock.notifyAll();
                    }
                }
            }
        }).start();
    }
}


(7)比较 wait() 和 sleep()

  1.  wait 是 Object 的实例方法,sleep 是 Thread 的静态方法;
  2. wait 只能用于同步代码块或同步方法,sleep 无限制;
  3. wait 会释放对象锁,而 sleep 不会释放;
  4. wait 结束条件是凭借 notify 唤醒或到达指定时间,sleep 结束条件是到达指定时间;
  5. 相同点是两者都会引起阻塞。

 三、单例模式

1.饿汉式单例

        类加载时即完成对象创建,无论使用与否。

(1)实现 

  1. 私有化构造方法;
  2. 对外提供一个公开的静态方法,以获取单个实例;
  3. 定义一个静态变量,类加载时初始化。 

    (2)实例 

    // 饿汉式单例模式
    public class SingletonPattern {
    
        // 1. 私有化构造方法,防止外部创建实例
        private SingletonPattern() {
            System.out.println("SingletonPattern constructor");
        }
    
        // 2. 创建一个私有的静态变量,用于存储唯一实例
        private static SingletonPattern instance = new SingletonPattern();
    
        // 3. 提供一个公共的静态方法,用于获取唯一实例
        public static SingletonPattern getInstance() {
            return instance;
        }
    }
    public class SingletonPatternTest {
    
        public static void main(String[] args) {
            SingletonPattern instance1 = SingletonPattern.getInstance();
            SingletonPattern instance2 = SingletonPattern.getInstance();
            System.out.println(instance1 == instance2); // 输出:true
        }
    }

    2.懒汉式单例

             不在类加载时创建对象,需要使用时才创建。

    (1)实现 

    1. 私有化构造方法;
    2. 对外提供一个公开的静态方法,以获取单个实例;
    3. 定义一个静态变量,其值为 null 。

    (2)实例 

    // 懒汉式单例模式
    public class SingletonPattern {
    
        // 1. 私有化构造方法
        private SingletonPattern() {
            System.out.println("SingletonPattern()");
        }
    
        // 2. 私有化静态变量
        private static SingletonPattern instance;
    
        // 3. 创建一个静态方法,返回一个实例
        public static SingletonPattern getInstance() {
            if (instance == null) {
                instance = new SingletonPattern();
            }
            return instance;
        }
    public class SingletonPatternTest {
    
        public static void main(String[] args) {
            SingletonPattern instance1 = SingletonPattern.getInstance();
            SingletonPattern instance2 = SingletonPattern.getInstance();
            System.out.println(instance1 == instance2); // 输出:true
        }
    }

    3.懒汉式单例模式的线程安全

    public class Singleton {
        private static Singleton instance = null;
    
        private Singleton() {
            System.out.println("Singleton");
        }
    
        /* 第一种方式,同步方法
        public synchronized static Singleton getInstance() {
            if (instance == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                instance = new Singleton();
            }
            return instance;
        }*/
    
        // 第二种方式,同步代码块
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {    // 获取Singleton类的类锁
                    if (instance == null) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    public class SingletonThread {
        private static Singleton s1;
        private static Singleton s2;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    s1 = Singleton.getInstance();
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    s2 = Singleton.getInstance();
                }
            });
    
            t1.start();
            t2.start();
    
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);
        }
    }


     四、ReentrantLock(可重入锁)

    1.说明

    1. 使用 Lock 实现线程安全;
    2. Lock 接口从 jdk 5 开始引入;
    3. ReentrantLock 是 Lock 的一个实现类。

    2.实例

             对懒汉式单例模式的线程安全实例中的 Singleton.java 修改。

    public class Singleton {
        private static Singleton instance = null;
        private static final ReentrantLock lock = new ReentrantLock();  // 创建一个可重入锁
    
        private Singleton() {
            System.out.println("Singleton");
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                lock.lock();    // 加锁
                if (instance == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    instance = new Singleton();
                }
            }
            lock.unlock();  // 释放锁
            return instance;
        }
    }

             注意:有 lock() ,就一定要有 unlock() 。但是比 synchronized 更灵活,且 jdk 官方更推荐使用 ReentrantLock 这种方式。


    五、实现线程的方式

    1.回顾之前的两种实现方式

            在线程的第一节里便讨论了实现线程的两种方式:继承 Thread 类实现 Runnable 接口。链接如下:

    Java基础关键_028_线程(一)https://mpbeta.csdn.net/mp_blog/creation/editor/146426314

             回顾之后,再来看看其他的实现方法。


    2.实现 Callable 接口

            这种实现方式,可以获取到线程的返回值。

    public class TaskTest implements Callable<String> {
        @Override
        public String call() throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");
            Date now = new Date();
            String format = sdf.format(now);
            return format;
        }
    }
    public class ThreadTest {
        public static void main(String[] args) {
            TaskTest taskTest = new TaskTest();
            FutureTask<String> futureTask = new FutureTask<>(taskTest);
    
            Thread thread = new Thread(futureTask);
            thread.start();
            try {
                String s = futureTask.get();
                System.out.println(s);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
    }

    3.使用线程池实现

    1. 线程池本质上就是一个缓存;
    2. 服务器在启动时,初始化线程池。即服务器启动时,创建多个线程对象,放入线程池中,在需要时直接从线程池中获取。
    public class ThreadTest {
        public static void main(String[] args) {
            // 创建线程池, 线程池大小为5
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            // 将任务提交给线程池
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        System.out.println(Thread.currentThread().getName() + ": " + i);
                    }
                }
            });
            // 关闭线程池
            executorService.shutdown();
        }
    }


    http://www.kler.cn/a/611755.html

    相关文章:

  4. 347 前k个高频元素
  5. 多 线 程
  6. WPF中的Adorner基础用法详解与实例
  7. True strength lies in embracing vulnerability as a gateway to growth.
  8. 23种设计模式-责任链(Chain of Responsibility)设计模式
  9. AigcPanel v0.8.1 焕新上线:界面升级,新增 Spark - TTS、Fish Speech 模型支持
  10. [Effective C++]条款24:若所有参数皆需类型转换,请为此采用non-menber函数
  11. 【go微服务】跨语言RPC接口工具--protobuf语法与原理解读以及应用实战
  12. [MRCTF2020]套娃
  13. 实战 | 基于 SpringBoot + UniApp 打造国际版打车系统:架构设计与性能优化全解析
  14. SQL优化 | OceanBase是否遵循最左匹配原则?(三)
  15. MDC的原理是什么?
  16. 计算机二级(C语言)考试高频考点总汇(二)—— 控制流、函数、数组和指针
  17. 向量数据库的适用场景与局限性分析
  18. Java爬虫如何解析返回的JSON数据?
  19. Dynamic Soft Contrastive Learning for Time Series Anomaly Detection 解读
  20. 【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)
  21. 套接字Socket
  22. 算法-深度优先搜索
  23. ubuntu单机部署redis集群