Java 中文官方教程 2022 版(九)(3)

简介: Java 中文官方教程 2022 版(九)

Java 中文官方教程 2022 版(九)(2)https://developer.aliyun.com/article/1486343

中断状态标志

中断机制是通过一个称为中断状态的内部标志实现的。调用Thread.interrupt会设置这个标志。当线程通过调用静态方法Thread.interrupted检查中断时,中断状态会被清除。非静态的isInterrupted方法用于一个线程查询另一个线程的中断状态,不会改变中断状态标志。

按照惯例,任何通过抛出InterruptedException退出的方法在这样做时会清除中断状态。然而,另一个线程调用interrupt可能会立即再次设置中断状态。

加入

原文:docs.oracle.com/javase/tutorial/essential/concurrency/join.html

join方法允许一个线程等待另一个线程的完成。如果t是一个当前正在执行的线程的Thread对象,

t.join();

会导致当前线程暂停执行,直到t的线程终止。join的重载允许程序员指定等待时间。然而,与sleep一样,join依赖于操作系统的时间控制,因此你不应该假设join会等待与你指定的时间完全相同。

sleep一样,join在收到InterruptedException时会退出。

简单线程示例

原文:docs.oracle.com/javase/tutorial/essential/concurrency/simple.html

以下示例汇集了本节中的一些概念。SimpleThreads 包含两个线程。第一个是每个 Java 应用程序都有的主线程。主线程从 Runnable 对象 MessageLoop 创建一个新线程,并等待其完成。如果 MessageLoop 线程花费太长时间才能完成,主线程会中断它。

MessageLoop 线程会打印一系列消息。如果在打印完所有消息之前被中断,MessageLoop 线程会打印一条消息然后退出。

public class SimpleThreads {
    // Display a message, preceded by
    // the name of the current thread
    static void threadMessage(String message) {
        String threadName =
            Thread.currentThread().getName();
        System.out.format("%s: %s%n",
                          threadName,
                          message);
    }
    private static class MessageLoop
        implements Runnable {
        public void run() {
            String importantInfo[] = {
                "Mares eat oats",
                "Does eat oats",
                "Little lambs eat ivy",
                "A kid will eat ivy too"
            };
            try {
                for (int i = 0;
                     i < importantInfo.length;
                     i++) {
                    // Pause for 4 seconds
                    Thread.sleep(4000);
                    // Print a message
                    threadMessage(importantInfo[i]);
                }
            } catch (InterruptedException e) {
                threadMessage("I wasn't done!");
            }
        }
    }
    public static void main(String args[])
        throws InterruptedException {
        // Delay, in milliseconds before
        // we interrupt MessageLoop
        // thread (default one hour).
        long patience = 1000 * 60 * 60;
        // If command line argument
        // present, gives patience
        // in seconds.
        if (args.length > 0) {
            try {
                patience = Long.parseLong(args[0]) * 1000;
            } catch (NumberFormatException e) {
                System.err.println("Argument must be an integer.");
                System.exit(1);
            }
        }
        threadMessage("Starting MessageLoop thread");
        long startTime = System.currentTimeMillis();
        Thread t = new Thread(new MessageLoop());
        t.start();
        threadMessage("Waiting for MessageLoop thread to finish");
        // loop until MessageLoop
        // thread exits
        while (t.isAlive()) {
            threadMessage("Still waiting...");
            // Wait maximum of 1 second
            // for MessageLoop thread
            // to finish.
            t.join(1000);
            if (((System.currentTimeMillis() - startTime) > patience)
                  && t.isAlive()) {
                threadMessage("Tired of waiting!");
                t.interrupt();
                // Shouldn't be long now
                // -- wait indefinitely
                t.join();
            }
        }
        threadMessage("Finally!");
    }
}

同步

原文:docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

线程主要通过共享对字段和对象引用字段引用的访问来进行通信。这种形式的通信非常高效,但可能导致两种错误:线程干扰内存一致性错误。防止这些错误所需的工具是同步

然而,同步可能引入线程争用,当两个或更多线程尝试同时访问同一资源导致 Java 运行时执行一个或多个线程更慢,甚至暂停它们的执行时发生。饥饿和活锁是线程争用的形式。有关更多信息,请参阅 Liveness 部分。

本节涵盖以下主题:

  • 线程干扰描述了当多个线程访问共享数据时引入错误的情况。
  • 内存一致性错误描述了由共享内存不一致视图引起的错误。
  • 同步方法描述了一种简单的习语,可以有效地防止线程干扰和内存一致性错误。
  • 隐式锁和同步描述了一种更通用的同步习语,并描述了同步是基于隐式锁的。
  • 原子访问讨论了无法被其他线程干扰的操作的一般概念。

线程干扰

原文:docs.oracle.com/javase/tutorial/essential/concurrency/interfere.html

考虑一个简单的名为 Counter 的类

class Counter {
    private int c = 0;
    public void increment() {
        c++;
    }
    public void decrement() {
        c--;
    }
    public int value() {
        return c;
    }
}

Counter 被设计成每次调用 increment 都会将 c 加 1,每次调用 decrement 都会从 c 减 1。然而,如果从多个线程引用 Counter 对象,线程之间的干扰可能会阻止预期的操作发生。

当两个操作在不同线程中运行,但作用于相同数据时,干扰就会发生。这意味着这两个操作由多个步骤组成,步骤序列会交叉。

对于 Counter 实例的操作似乎不可能交错,因为对 c 的操作都是单个简单语句。然而,即使是简单语句也可以被虚拟机翻译为多个步骤。我们不会检查虚拟机执行的具体步骤 — 知道单个表达式 c++ 可以分解为三个步骤就足够了:

  1. 检索当前值 c
  2. 递增检索到的值 1。
  3. 将递增后的值存储回 c

表达式 c-- 可以以相同方式分解,只是第二步是减少而不是增加。

假设线程 A 大约在同一时间调用 increment,而线程 B 调用 decrement。如果 c 的初始值为 0,它们交错的操作可能会按照这个顺序进行:

  1. 线程 A:检索 c。
  2. 线程 B:检索 c。
  3. 线程 A:递增检索到的值;结果为 1。
  4. 线程 B:减少检索到的值;结果为 -1。
  5. 线程 A:将结果存储在 c 中;c 现在为 1。
  6. 线程 B:将结果存储在 c 中;c 现在为 -1。

线程 A 的结果丢失,被线程 B 覆盖。这种特定的交错只是一种可能性。在不同情况下,可能会丢失线程 B 的结果,或者根本没有错误。由于它们是不可预测的,线程干扰 bug 可能很难检测和修复。

内存一致性错误

原文:docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html

内存一致性错误发生在不同线程对应该是相同数据的不一致视图时。内存一致性错误的原因复杂,超出了本教程的范围。幸运的是,程序员不需要详细了解这些原因。所需的只是避免它们的策略。

避免内存一致性错误的关键在于理解happens-before关系。这种关系简单地保证了一个特定语句的内存写入对另一个特定语句是可见的。为了看到这一点,考虑以下示例。假设定义并初始化了一个简单的int字段:

int counter = 0;

counter字段在两个线程 A 和 B 之间共享。假设线程 A 增加counter

counter++;

然后,不久之后,线程 B 打印出counter

System.out.println(counter);

如果这两个语句在同一个线程中执行,可以安全地假设打印出的值为"1"。但如果这两个语句在不同的线程中执行,打印出的值可能是"0",因为不能保证线程 A 对counter的更改对线程 B 可见,除非程序员在这两个语句之间建立了一个 happens-before 关系。

有几种动作会创建先于关系。其中之一是同步,我们将在接下来的部分中看到。

我们已经看到了两个创建先于关系的动作。

  • 当一个语句调用Thread.start时,与该语句具有先于关系的每个语句也与新线程执行的每个语句具有先于关系。导致创建新线程的代码的效果对新线程可见。
  • 当一个线程终止并导致另一个线程中的Thread.join返回时,那么终止线程执行的所有语句与成功加入后面的所有语句之间存在先于关系。线程中代码的效果现在对执行加入的线程可见。

有关创建先于关系的动作列表,请参考Java java.util.concurrent包的摘要页面。

同步方法

原文:docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

Java 编程语言提供了两种基本的同步习语:同步方法同步语句。其中更复杂的同步语句将在下一节中描述。本节讨论的是同步方法。

要使方法同步,只需在其声明中添加synchronized关键字:

public class SynchronizedCounter {
    private int c = 0;
    public synchronized void increment() {
        c++;
    }
    public synchronized void decrement() {
        c--;
    }
    public synchronized int value() {
        return c;
    }
}

如果countSynchronizedCounter的一个实例,则使这些方法同步会产生两个效果:

  • 首先,不可能让同一对象上的两次同步方法调用交错。当一个线程正在为对象执行同步方法时,所有调用同一对象的同步方法的其他线程都会被阻塞(暂停执行),直到第一个线程完成对象的操作。
  • 其次,当一个同步方法退出时,它会自动与同一对象的任何后续同步方法的调用建立 happens-before 关系。这确保了对对象状态的更改对所有线程都是可见的。

请注意,构造函数不能被同步 — 使用synchronized关键字与构造函数是语法错误。同步构造函数没有意义,因为只有创建对象的线程在构造对象时应该访问它。


警告: 在构建一个将在多个线程之间共享的对象时,一定要非常小心,确保对象的引用不会过早“泄漏”。例如,假设你想要维护一个名为instancesList,其中包含类的每个实例。你可能会诱惑地在构造函数中添加以下行:

instances.add(this);

但是其他线程可以使用instances来访问对象,而在对象构造完成之前。


同步方法为防止线程干扰和内存一致性错误提供了一种简单的策略:如果一个对象对多个线程可见,那么对该对象的变量的所有读取或写入都通过synchronized方法进行。 (一个重要的例外:final字段,在对象构造后无法修改,可以通过非同步方法安全地读取,一旦对象构造完成)这种策略是有效的,但在后面的课程中我们将看到它可能会出现 liveness 问题。

内在锁和同步

原文:docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

同步建立在一个称为内在锁监视器锁的内部实体周围。内在锁在同步的两个方面发挥作用:强制对对象状态的独占访问和建立对可见性至关重要的 happens-before 关系。

每个对象都有一个与之关联的内在锁。按照惯例,需要独占和一致访问对象字段的线程在访问这些字段之前必须获取对象的内在锁,然后在完成后释放内在锁。线程在获取锁和释放锁之间被认为拥有内在锁。只要一个线程拥有内在锁,其他线程就无法获取相同的锁。当另一个线程尝试获取锁时,它将被阻塞。

当一个线程释放一个内在锁时,该操作与后续获取相同锁的任何操作建立 happens-before 关系。

同步方法中的锁

当一个线程调用一个同步方法时,它会自动获取该方法对象的内在锁,并在方法返回时释放它。即使返回是由未捕获的异常引起的,锁也会被释放。

你可能会想知道当调用静态同步方法时会发生什么,因为静态方法与类相关联,而不是对象。在这种情况下,线程会获取与类相关联的Class对象的内在锁。因此,对类的静态字段的访问受到一个与类的任何实例的锁不同的锁的控制。

同步语句

创建同步代码的另一种方法是使用synchronized 语句。与同步方法不同,同步语句必须指定提供内在锁的对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在这个例子中,addName方法需要同步对lastNamenameCount的更改,但也需要避免同步调用其他对象的方法。(从同步代码调用其他对象的方法可能会导致在 Liveness 部分描述的问题。)如果没有同步语句,就必须有一个单独的、非同步的方法,唯一目的是调用nameList.add

同步语句也有助于通过细粒度同步提高并发性。例如,假设类MsLunch有两个实例字段,c1c2,它们永远不会同时使用。所有这些字段的更新必须同步,但没有理由阻止对 c1 的更新与对 c2 的更新交错—这样做会通过创建两个仅用于提供锁的对象来减少并发性。

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }
    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

使用这种习语要非常小心。你必须绝对确定交错访问受影响字段是安全的。

可重入同步

请记住,一个线程不能获取另一个线程拥有的锁。但一个线程可以获取它已经拥有的锁。允许一个线程多次获取相同的锁使可重入同步成为可能。这描述了一种情况,即同步代码直接或间接地调用一个也包含同步代码的方法,并且两组代码使用相同的锁。如果没有可重入同步,同步代码将不得不采取许多额外的预防措施,以避免一个线程导致自己被阻塞。

原子访问

原文:docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

在编程中,原子操作是一种有效地一次性完成的操作。原子操作不能在中途停止:它要么完全发生,要么根本不发生。在原子操作完成之前,不会看到任何副作用。

我们已经看到增量表达式,比如c++,并不描述原子操作。即使是非常简单的表达式也可以定义可以分解为其他操作的复杂操作。然而,有一些操作是可以指定为原子操作的:

  • 对于引用变量和大多数基本变量(除了longdouble之外的所有类型),读取和写入是原子的。
  • 对于所有声明为volatile的变量,读取和写入都是原子的(包括longdouble变量)。

原子操作不能交错,因此可以在不担心线程干扰的情况下使用它们。然而,这并不消除同步原子操作的所有需求,因为内存一致性错误仍然可能发生。使用volatile变量可以减少内存一致性错误的风险,因为对volatile变量的任何写入都会与随后对该变量的读取建立 happens-before 关系。这意味着对volatile变量的更改始终对其他线程可见。更重要的是,这也意味着当线程读取volatile变量时,它不仅看到volatile的最新更改,还看到导致更改的代码的副作用。

使用简单的原子变量访问比通过同步代码访问这些变量更有效,但需要程序员更加小心,以避免内存一致性错误。额外的努力是否值得取决于应用程序的大小和复杂性。

java.util.concurrent包中的一些类提供了不依赖于同步的原子方法。我们将在高级并发对象部分讨论它们。

活跃性

原文:docs.oracle.com/javase/tutorial/essential/concurrency/liveness.html

并发应用程序按时执行的能力被称为其liveness。本节描述了最常见的活跃性问题,死锁,并简要描述了另外两种活跃性问题,饥饿和活锁。

死锁

原文:docs.oracle.com/javase/tutorial/essential/concurrency/deadlock.html

死锁 描述了两个或更多线程永远被阻塞,彼此等待的情况。这里有一个例子。

阿方索和加斯顿是朋友,也是极信奉礼貌的人。一个严格的礼貌规则是,当你向朋友鞠躬时,你必须保持鞠躬的姿势,直到你的朋友有机会回礼。不幸的是,这个规则没有考虑到两个朋友可能同时向对方鞠躬的可能性。这个示例应用程序,死锁,模拟了这种可能性:

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }
    public static void main(String[] args) {
        final Friend alphonse =
            new Friend("Alphonse");
        final Friend gaston =
            new Friend("Gaston");
        new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }).start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }).start();
    }
}

死锁 运行时,当它们尝试调用 bowBack 时,两个线程都很可能被阻塞。由于每个线程都在等待另一个线程退出 bow,因此这两个阻塞永远不会结束。

饥饿和活锁

原文:docs.oracle.com/javase/tutorial/essential/concurrency/starvelive.html

饥饿和活锁问题比死锁问题要少见得多,但仍然是每个并发软件设计者可能会遇到的问题。

饥饿

饥饿描述了一个线程无法定期访问共享资源并且无法取得进展的情况。这种情况发生在"贪婪"线程长时间地使共享资源不可用时。例如,假设一个对象提供了一个经常需要很长时间才能返回的同步方法。如果一个线程频繁调用这个方法,其他也需要频繁同步访问同一对象的线程将经常被阻塞。

活锁

一个线程经常是作为对另一个线程动作的响应。如果另一个线程的动作也是对另一个线程动作的响应,那么可能会发生livelock。与死锁类似,活锁的线程无法取得进一步的进展。然而,这些线程并没有被阻塞 — 它们只是忙于相互响应而无法恢复工作。这就好比两个人试图在走廊里互相让对方通过:阿方斯向左移动让加斯通通过,而加斯通向右移动让阿方斯通过。看到他们仍然互相阻挡,阿方斯向右移动,而加斯通向左移动。他们仍然互相阻挡,所以…

保护块

原文:docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

线程经常需要协调它们的动作。最常见的协调习语是保护块。这样的块开始于轮询一个条件,该条件必须在块可以继续之前为真。为了正确执行此操作,需要遵循一些步骤。

假设,例如guardedJoy是一个方法,必须在另一个线程设置共享变量joy之前才能继续。这样的方法理论上可以简单地循环,直到条件满足,但是该循环是浪费的,因为它在等待时持续执行。

public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
}

一个更有效的保护块调用Object.wait来挂起当前线程。调用wait不会返回,直到另一个线程发出通知,表明可能发生了某个特殊事件,尽管不一定是该线程正在等待的事件:

public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

**注意:**始终在测试等待的条件的循环中调用wait。不要假设中断是为了您正在等待的特定条件,或者该条件仍然为真。


像许多暂停执行的方法一样,wait可能会抛出InterruptedException。在这个例子中,我们可以忽略这个异常,我们只关心joy的值。

为什么这个guardedJoy的版本是同步的?假设d是我们用来调用wait的对象。当一个线程调用d.wait时,它必须拥有d的内在锁,否则会抛出错误。在同步方法中调用wait是获取内在锁的简单方法。

当调用wait时,线程释放锁并暂停执行。在将来的某个时间,另一个线程将获得相同的锁并调用Object.notifyAll,通知所有等待该锁的线程发生了重要事件:

public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

第二个线程释放锁后一段时间,第一个线程重新获取锁,并通过从wait调用返回来恢复执行。


**注意:**还有第二种通知方法,notify,它唤醒单个线程。因为notify不允许您指定被唤醒的线程,所以它只在大规模并行应用程序中有用,即具有大量线程的程序,所有线程都在做类似的工作。在这种应用程序中,您不关心哪个线程被唤醒。


让我们使用保护块来创建一个生产者-消费者应用程序。这种应用程序在两个线程之间共享数据:生产者创建数据,消费者对其进行处理。这两个线程使用共享对象进行通信。协调是必不可少的:消费者线程在生产者线程交付数据之前不得尝试检索数据,生产者线程在消费者尚未检索旧数据之前不得尝试交付新数据。

在这个例子中,数据是一系列文本消息,通过一个类型为Drop的对象共享:

public class Drop {
    // Message sent from producer
    // to consumer.
    private String message;
    // True if consumer should wait
    // for producer to send message,
    // false if producer should wait for
    // consumer to retrieve message.
    private boolean empty = true;
    public synchronized String take() {
        // Wait until message is
        // available.
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = true;
        // Notify producer that
        // status has changed.
        notifyAll();
        return message;
    }
    public synchronized void put(String message) {
        // Wait until message has
        // been retrieved.
        while (!empty) {
            try { 
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status
        // has changed.
        notifyAll();
    }
}

Java 中文官方教程 2022 版(九)(4)https://developer.aliyun.com/article/1486345

相关文章
|
4天前
|
前端开发 Java Maven
【前端学java】全网最详细的maven安装与IDEA集成教程!
【8月更文挑战第12天】全网最详细的maven安装与IDEA集成教程!
21 2
【前端学java】全网最详细的maven安装与IDEA集成教程!
|
9天前
|
存储 网络协议 Oracle
java教程
java教程【8月更文挑战第11天】
14 5
|
1月前
|
SQL 安全 Java
「滚雪球学Java」教程导航帖(更新2024.07.16)
《滚雪球学Spring Boot》是一个面向初学者的Spring Boot教程,旨在帮助读者快速入门Spring Boot开发。本专通过深入浅出的方式,将Spring Boot开发中的核心概念、基础知识、实战技巧等内容系统地讲解,同时还提供了大量实际的案例,让读者能够快速掌握实用的Spring Boot开发技能。本书的特点在于注重实践,通过实例学习的方式激发读者的学习兴趣和动力,并引导读者逐步掌握Spring Boot开发的实际应用。
42 1
「滚雪球学Java」教程导航帖(更新2024.07.16)
WXM
|
25天前
|
Oracle Java 关系型数据库
Java JDK下载安装及环境配置超详细图文教程
Java JDK下载安装及环境配置超详细图文教程
WXM
129 3
|
1月前
|
测试技术 API Android开发
《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)
【7月更文挑战第15天】这是关于自动化测试框架中Selenium API二次封装的教程总结。教程中介绍了如何设计一个支持不同浏览器测试的页面基类(BasePage),该基类包含了对Selenium方法的二次封装,如元素的输入、点击、清除等常用操作,以减少重复代码。此外,页面基类还提供了获取页面标题和URL的方法。
44 2
|
1月前
|
Web App开发 XML Java
《手把手教你》系列基础篇(九十六)-java+ selenium自动化测试-框架之设计篇-跨浏览器(详解教程)
【7月更文挑战第14天】这篇教程介绍了如何使用Java和Selenium构建一个支持跨浏览器测试的自动化测试框架。设计的核心是通过读取配置文件来切换不同浏览器执行测试用例。配置文件中定义了浏览器类型(如Firefox、Chrome)和测试服务器的URL。代码包括一个`BrowserEngine`类,它初始化配置数据,根据配置启动指定的浏览器,并提供关闭浏览器的方法。测试脚本`TestLaunchBrowser`使用`BrowserEngine`来启动浏览器并执行测试。整个框架允许在不同浏览器上运行相同的测试,以确保兼容性和一致性。
47 3
|
1月前
|
存储 Web App开发 Java
《手把手教你》系列基础篇(九十五)-java+ selenium自动化测试-框架之设计篇-java实现自定义日志输出(详解教程)
【7月更文挑战第13天】这篇文章介绍了如何在Java中创建一个简单的自定义日志系统,以替代Log4j或logback。
136 5
|
1月前
|
Java 数据安全/隐私保护
Java无模版导出Excel 0基础教程
经常写数据导出到EXCEL,没有模板的情况下使用POI技术。以此作为记录,以后方便使用。 2 工具类 样式工具: 处理工具Java接口 水印工具 导出Excel工具类 3 测试代码 与实际复杂业务不同 在此我们只做模拟 Controller Service 4 导出测试 使用Postman进行接口测试,没接触过Postman的小伙伴可以看我这篇博客Postman导出excel文件保存为文件可以看到导出很成功,包括水印 sheet页名称自适应宽度。还有一些高亮……等功能可以直接搜索使用
Java无模版导出Excel 0基础教程
|
1月前
|
设计模式 测试技术 Python
《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
【7月更文挑战第10天】Page Object Model (POM)是Selenium自动化测试中的设计模式,用于提高代码的可读性和维护性。POM将每个页面表示为一个类,封装元素定位和交互操作,使得测试脚本与页面元素分离。当页面元素改变时,只需更新对应页面类,减少了脚本的重复工作和维护复杂度,有利于团队协作。POM通过创建页面对象,管理页面元素集合,将业务逻辑与元素定位解耦合,增强了代码的复用性。示例展示了不使用POM时,脚本直接混杂了元素定位和业务逻辑,而POM则能解决这一问题。
43 6
|
1月前
|
设计模式 Java 测试技术
《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
【7月更文挑战第12天】在本文中,作者宏哥介绍了如何在不使用PageFactory的情况下,用Java和Selenium实现Page Object Model (POM)。文章通过一个百度首页登录的实战例子来说明。首先,创建了一个名为`BaiduHomePage1`的页面对象类,其中包含了页面元素的定位和相关操作方法。接着,创建了测试类`TestWithPOM1`,在测试类中初始化WebDriver,设置驱动路径,最大化窗口,并调用页面对象类的方法进行登录操作。这样,测试脚本保持简洁,遵循了POM模式的高可读性和可维护性原则。
27 2