• Java多线程(三)


    目录

    一、线程的同步(二)

    同步机制释放锁的操作

    不会释放锁的操作

    线程的死锁问题

    死锁

    解决方法

    Lock(锁)

    使用Lock(锁)创建多线程步骤:

    使用Lock解决窗口售票问题

    synchronized与Lock的对比

    练习

     二、线程的通信

    通过例题说明线程间的通信

     wait()与notify()与notifyAll()

    wait()

    notify()

    notifyAll()

    说明

    sleep()和wait()的异同

    练习题

    一、线程的同步(二)

    同步机制释放锁的操作

    1、当前线程的同步方法、同步代码块执行结束。

    2、当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。 3、当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。

    4、当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    不会释放锁的操作

    1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行

    2、线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程

    线程的死锁问题

    死锁

    1、不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁

    2、出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

    解决方法

    1、专门的算法、原则

    2、尽量减少同步资源的定义

    3、尽量避免嵌套同步

    Lock(锁)

    从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同 步锁对象来实现同步。同步锁使用Lock对象充当。

     java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

    ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

    使用Lock(锁)创建多线程步骤:

    1)实例化ReentrantLock

    2)调用锁定方法:lock()

    3)调用解锁方法:unlock()

    try{

            xxx.lock();

            //保证线程安全的代码

    }finally{

            xxx.unlock();

    }

    使用Lock解决窗口售票问题

    1. import java.util.concurrent.locks.ReentrantLock;
    2. public class LockTest {
    3. public static void main(String[] args) {
    4. Window w=new Window();
    5. Thread t1=new Thread(w);
    6. Thread t2=new Thread(w);
    7. Thread t3=new Thread(w);
    8. t1.setName("窗口1");
    9. t2.setName("窗口2");
    10. t3.setName("窗口3");
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }
    16. class Window implements Runnable{
    17. private int ticket=100;
    18. //1.实例化ReentrantLock
    19. private ReentrantLock lock=new ReentrantLock();
    20. @Override
    21. public void run() {
    22. while(true) {
    23. try {
    24. lock.lock();
    25. if (ticket > 0) {
    26. System.out.println(Thread.currentThread().getName() + ":,卖票,票号为:" + ticket);
    27. ticket--;
    28. } else {
    29. break;
    30. }
    31. }finally {
    32. lock.unlock();
    33. }
    34. }
    35. }
    36. }

    synchronized与Lock的对比

    相同点:二者都可以解决线程安全问题

    不同点:

    • Lock是显示锁,需手动开启和关闭锁;synchronized是隐式所,出了作用域自动释放
    • Lock只有代码块锁;synchronized有代码块锁和方法锁
    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更所得子类)

    练习

    银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

    1. /**
    2. * @auther light
    3. * @Description 银行有一个账户。
    4. * 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打
    5. * 印账户余额
    6. *
    7. * 分析:
    8. * 1.是否是多线程问题? 是:两个储户线程
    9. * 2.是否有共享数据? 有:账户(或账户余额)
    10. * 3.是否有线程安全问题? 有
    11. * 4.需要考虑如何解决线程安全问题? 同步机制:三种方式
    12. * @create 2022-11-19 15:14
    13. */
    14. public class AccountTest {
    15. public static void main(String[] args) {
    16. Account acct=new Account(0);
    17. Customer c1=new Customer(acct);
    18. Customer c2=new Customer(acct);
    19. c1.setName("甲");
    20. c2.setName("乙");
    21. c1.start();
    22. c2.start();
    23. }
    24. }
    25. class Account{
    26. double balance;
    27. public Account( double balance){
    28. this.balance=balance;
    29. }
    30. //存钱
    31. public synchronized void deposit(double amt){
    32. if(amt>0){
    33. balance+=amt;
    34. // try {
    35. // Thread.sleep(1000);
    36. // } catch (InterruptedException e) {
    37. // throw new RuntimeException(e);
    38. // }
    39. System.out.println(Thread.currentThread().getName()+":存钱成功,余额为:"+balance);
    40. }
    41. }
    42. }
    43. class Customer extends Thread{
    44. Account acct;
    45. public Customer(Account acct){
    46. this.acct=acct;
    47. }
    48. @Override
    49. public void run() {
    50. for(int i=0;i<3;i++){
    51. acct.deposit(1000);
    52. }
    53. }
    54. }

    运行结果如下:

     二、线程的通信

    操作同一个资源时多个线程之间不断切换执行时所发出的信号

    通过例题说明线程间的通信

    线程通信例子:使用各个线程交替打印1-100
    
    * 涉及到三个方法:
    * 1.wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    * 2.notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
    * 3.notifyAll():一旦执行此方法就会唤醒所有被wait的线程
    *
    * 说明:
    * 1.wait()、notify()、notifyAll()这三个方法只能使用在同步代码块或同步方法中
    * 2.wait()、notify()、notifyAll()这三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
    * 3.wait()、notify()、notifyAll()这三个方法是定义在java.lang.Object类中的

     

    1. public class ThreadCommunicationTest {
    2. public static void main(String[] args) {
    3. Number n=new Number();
    4. Thread n1=new Thread(n);
    5. Thread n2=new Thread(n);
    6. n1.setName("线程1");
    7. n2.setName("线程2");
    8. n1.start();
    9. n2.start();
    10. }
    11. }
    12. class Number implements Runnable{
    13. private int i=1;
    14. @Override
    15. public void run() {
    16. synchronized (this) {
    17. for (; i <= 100; ) {
    18. notify();
    19. try {
    20. Thread.sleep(10);
    21. } catch (InterruptedException e) {
    22. throw new RuntimeException(e);
    23. }
    24. System.out.println(Thread.currentThread().getName() + ":打印输出:" + i);
    25. i++;
    26. try {
    27. wait();
    28. } catch (InterruptedException e) {
    29. throw new RuntimeException(e);
    30. }
    31. }
    32. }
    33. }
    34. }

    部分运行结果为:

     wait()与notify()与notifyAll()

    wait()

    令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

    notify()

    唤醒正在排队等待同步资源的线程中优先级最高者结束等待

    notifyAll()

    唤醒正在排队等待资源的所有线程结束等待.

    说明

    • wait()、notify()、notifyAll()这三个方法只能使用在同步代码块或同步方法中
    • .wait()、notify()、notifyAll()这三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
    • wait()、notify()、notifyAll()这三个方法是定义在java.lang.Object类中的

    sleep()和wait()的异同

    相同点:一旦执行方法,都可以使得当前线程进入阻塞状态

    不同点:

    • 两个方法声明的位置不同:sleep()声明在Thread类中;wait()声明在Object类中
    • 调用的范围和要求不同:sleep()可以生命在任何需要的场景下;wait()只能声明在同步代码块或同步方法中
    • 关于是否释放同步监视器:如果两个方法必须使用在同步代码块或同步方法中,sleep()不会释放同步监视器;wait()会释放同步监视器 

    练习题

    生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。

    问题分析:

    * 1、是否是多线程问题:是,生产者线程,消费者线程

    * 2、是否有共享数据:是,店员(或产品)

    * 3、如何解决多线程问题:同步机制,三种方法

    * 4、是否涉及线程通信:是

    1. public class ProductTest {
    2. public static void main(String[] args) {
    3. Clerk c=new Clerk();
    4. Producer p1=new Producer(c);
    5. Customer c1=new Customer(c);
    6. Thread t1=new Thread(p1);
    7. Thread t2=new Thread(c1);
    8. t1.setName("生产者1");
    9. t2.setName("消费者1");
    10. t1.start();
    11. t2.start();
    12. }
    13. }
    14. class Clerk {
    15. private int product=0;
    16. public synchronized void produceProduct() {
    17. //生产者生产产品
    18. if(product<20){
    19. product++;
    20. System.out.println(Thread.currentThread().getName()+":开始生产第 "+product+" 个产品");
    21. notify();
    22. }else{
    23. try {
    24. wait();
    25. } catch (InterruptedException e) {
    26. throw new RuntimeException(e);
    27. }
    28. }
    29. }
    30. public synchronized void consumeProduct() {
    31. //消费者消费产品
    32. if(product>0){
    33. System.out.println(Thread.currentThread().getName()+":开始消费第 "+product+" 个产品");
    34. product--;
    35. notify();
    36. }else{
    37. try {
    38. wait();
    39. } catch (InterruptedException e) {
    40. throw new RuntimeException(e);
    41. }
    42. }
    43. }
    44. }
    45. class Producer implements Runnable{
    46. Clerk clerk;
    47. public Producer(Clerk clerk){
    48. this.clerk=clerk;
    49. }
    50. @Override
    51. public void run() {
    52. System.out.println(Thread.currentThread().getName()+"开始生产产品...");
    53. while(true){
    54. try {
    55. Thread.sleep(10);
    56. } catch (InterruptedException e) {
    57. throw new RuntimeException(e);
    58. }
    59. clerk.produceProduct();
    60. }
    61. }
    62. }
    63. class Customer implements Runnable{
    64. Clerk clerk;
    65. public Customer(Clerk clerk){
    66. this.clerk=clerk;
    67. }
    68. @Override
    69. public void run() {
    70. System.out.println(Thread.currentThread().getName()+"开始消费产品...");
    71. while(true){
    72. try {
    73. Thread.sleep(10);
    74. } catch (InterruptedException e) {
    75. throw new RuntimeException(e);
    76. }
    77. clerk.consumeProduct();
    78. }
    79. }
    80. }
  • 相关阅读:
    腾讯WXG前端面经
    Android Compose 修饰符类行为整理
    C++11之防止类型收窄(列表初始化)
    【前端精进之路】JS篇:第11期 深拷贝与浅拷贝
    ElementUI基本介绍及登录注册案例演示
    买阿里云服务器,实操搭建nginx+php+mysql+thinkphp5全过程(5)
    Git三剑客之基础部分
    Mac电脑idea中配置nodejs前端环境
    SpringBoot整合MybatisPlus基本的增删改查,保姆级教程
    Ajax 笔记 01
  • 原文地址:https://blog.csdn.net/zssxcj/article/details/127981677