并发基础知识:死锁和对象监视器

本文是我们学院课程中名为Java Concurrency Essentials的一部分 。

在本课程中,您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识,并学习诸如原子性,同步和线程安全之类的概念。 在这里查看 !

目录

1.活泼
1.1。 僵局 1.2。 饥饿
2.使用wait()和notify()进行对象监控
2.1。 带wait()和notify()的嵌套同步块 2.2。 同步块中的条件
3.设计多线程
3.1。 不变的对象 3.2。 API设计 3.3。 线程本地存储

1.活泼

在开发使用并发实现其目标的应用程序时,您可能会遇到不同线程可能相互阻塞的情况。 由于整个应用程序的运行速度比预期的慢,因此我们可以说该应用程序无法按预期的时间完成。 在本节中,我们将仔细研究可能危害多线程应用程序正常运行的问题。

僵局

术语“死锁”对于软件开发人员来说是众所周知的,即使是大多数普通的计算机用户也经常使用“死锁”这个术语,尽管它并非总是以正确的含义使用。 严格地说,这意味着两个(或多个)线程分别在另一个线程上等待以释放它已锁定的资源,而线程本身已锁定另一个线程在等待的资源:

Thread 1: locks resource A, waits for resource BThread 2: locks resource B, waits for resource A

为了更好地理解该问题,让我们看一下以下源代码:

public class Deadlock implements Runnable {private static final Object resource1 = new Object();private static final Object resource2 = new Object();private final Random random = new Random(System.currentTimeMillis());public static void main(String[] args) {Thread myThread1 = new Thread(new Deadlock(), "thread-1");Thread myThread2 = new Thread(new Deadlock(), "thread-2");myThread1.start();myThread2.start();}public void run() {for (int i = 0; i < 10000; i++) {boolean b = random.nextBoolean();if (b) {System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 1.");synchronized (resource1) {System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 1.");System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 2.");synchronized (resource2) {System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 2.");}}} else {System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 2.");synchronized (resource2) {System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 2.");System.out.println("[" + Thread.currentThread().getName() + "] Trying to lock resource 1.");synchronized (resource1) {System.out.println("[" + Thread.currentThread().getName() + "] Locked resource 1.");}}}}}
}

从上面的代码可以看出,启动了两个线程并尝试锁定两个静态资源。 但是对于死锁,两个线程需要不同的顺序,因此我们利用Random实例选择线程首先要锁定的资源。 如果布尔变量b为true,则首先锁定resource1,然后线程尝试获取对资源2的锁定。如果b为false,则线程首先锁定resource2,然后尝试锁定resource1。 在我们到达第一个死锁之前,该程序不必运行很长时间,即,如果我们不终止它,该程序将永远挂起:

[thread-1] Trying to lock resource 1.[thread-1] Locked resource 1.[thread-1] Trying to lock resource 2.[thread-1] Locked resource 2.[thread-2] Trying to lock resource 1.[thread-2] Locked resource 1.[thread-1] Trying to lock resource 2.[thread-1] Locked resource 2.[thread-2] Trying to lock resource 2.[thread-1] Trying to lock resource 1.

在此执行中,线程1持有资源2的锁并等待对resource1的锁,而线程2持有资源1的锁并等待resource2。

如果将上面示例代码中的布尔变量b设置为true,则不会遇到任何死锁,因为线程1和线程2请求锁的顺序始终相同。 因此,两个线程中的一个首先获取锁,然后请求第二个锁,由于其他线程等待第一个锁,第二个锁仍然可用。

通常,可以确定以下死锁要求:

  • 互斥:有一种资源在任何时间点只能由一个线程访问。
  • 资源持有:锁定一个资源后,线程尝试获取对某个其他排他资源的另一个锁定。
  • 无抢占:没有机制,如果一个线程在特定时间段内持有锁,则该机制可以释放资源。
  • 循环等待:在运行时发生一个星座,其中两个(或更多)线程分别在另一个线程上等待以释放已锁定的资源。

尽管要求列表看起来很长,但是更高级的多线程应用程序存在死锁问题并不罕见。 但是,如果您能够放松上面列出的要求之一,则可以尝试避免死锁:

  • 互斥:这是一项通常不能放宽的要求,因为必须专门使用资源。 但这并非总是如此。 使用DBMS系统时,可以使用一种称为Optimistic Locking的技术,而不是在必须更新的某些表行上使用悲观锁,这是一种可能的解决方案。
  • 在等待另一个排他资源时避免资源持有的可能解决方案是在算法开始时锁定所有必要的资源,并在不可能获得所有锁定的情况下释放所有资源。 当然,这并非总是可能的,也许锁定的资源并不为人所知,或者就像浪费资源一样。
  • 如果无法立即获得锁定,则避免超时的可能解决方案是引入超时。 例如,SDK类ReentrantLock提供了指定锁定超时的可能性。
  • 从上面的示例代码可以看出,如果不同线程之间的锁定请求顺序没有不同,则不会出现死锁。 如果您能够将所有锁定代码放入所有线程都必须通过的一种方法中,则可以轻松地控制它。

在更高级的应用程序中,您甚至可以考虑实现死锁检测系统。 在这里,您将必须实现某种类型的线程监视,其中每个线程都报告已成功获取锁以及其尝试获取锁的尝试。 如果将线程和锁建模为有向图,则可以检测到两个不同的线程何时拥有资源,同时请求另一个阻塞的资源。 如果然后您可以强制阻塞线程释放获得的资源,则可以自动解决死锁情况。

饥饿

调度程序决定下一步应该在状态RUNNABLE中执行的线程 。 该决定基于线程的优先级; 因此,具有较低优先级的线程比具有较高优先级的线程获得的CPU时间更少。 听起来很合理的功能在滥用时也会引起问题。 如果大多数时间具有高优先级的线程被执行,则低优先级的线程似乎“饿死了”,因为它们没有足够的时间正确执行其工作。 因此,建议仅在有充分理由的情况下设置线程的优先级。

线程匮乏的一个复杂示例是例如finalize()方法。 Java语言的此功能可用于在对象被垃圾回收之前执行代码。 但是,当您查看终结器线程的优先级时,您可能会发现它的运行优先级最高。 因此,如果与其他代码相比,对象的finalize()方法花费太多时间,则可能导致线程不足。

执行时间的另一个问题是问题,即未定义线程以哪个顺序传递同步块。 当许多并行线程必须传递封装在同步块中的某些代码时,某些线程可能比其他线程要等待更长的时间才能进入该块。 从理论上讲,它们可能永远不会进入障碍。

后一种问题的解决方案是所谓的“公平”锁定。 在选择下一个要传递的线程时,公平锁会考虑线程的等待时间。 Java SDK提供了一个公平锁的示例实现:java.util.concurrent.locks.ReentrantLock。 如果使用布尔标志设置为true的构造函数,则ReentrantLock授予对等待时间最长的线程的访问权限。 这保证了没有饥饿,但是同时引入了以下问题:没有考虑线程优先级,因此可能会更频繁地执行经常在此屏障处等待的优先级较低的线程。 最后但并非最不重要的一点是,ReentrantLock类当然只能考虑正在等待锁的线程,即,执行频率足以达到锁的线程。 如果线程优先级太低,则可能不会经常发生这种情况,因此,具有更高优先级的线程仍会更频繁地通过锁。

2.使用wait()和notify()进行对象监控

多线程计算中的一项常见任务是让一些工作线程正在等待其生产者为其创建工作。 但是,据我们了解,就CPU时间而言,在循环中忙于等待并检查某些值并不是一个好的选择。 在此用例中,Thread.sleep()方法也没有太大价值,因为我们希望在提交后立即开始工作。

因此,Java编程语言具有另一种可在这种情况下使用的构造:wait()和notify()。 每个对象都从java.lang.Object类继承的wait()方法可用于暂停当前线程执行,并等待直到另一个线程使用notify()方法将我们唤醒。 为了正常工作,调用wait()方法的线程必须持有它已获得的锁,然后才能使用synced关键字。 当调用wait()时,锁被释放,线程等待直到拥有该锁的另一个线程在同一对象实例上调用notify()为止。

在多线程应用程序中,当然可能有多个线程在等待某个对象的通知。 因此,有两种不同的唤醒线程的方法:notify()和notifyAll()。 第一个方法仅唤醒一个等待线程,而notifyAll()方法将它们全部唤醒。 但是请注意,类似于synced关键字,没有规则指定调用notify()时接下来唤醒哪个线程。 在简单的生产者和消费者示例中,这无关紧要,因为我们对哪个线程完全唤醒的事实不感兴趣。

下面的代码演示了如何使用wait()和notify()机制来让使用者线程等待从某些生产者线程推送到队列中的新工作:

package a2;import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;public class ConsumerProducer {private static final Queue queue = new ConcurrentLinkedQueue();private static final long startMillis = System.currentTimeMillis();public static class Consumer implements Runnable {public void run() {while (System.currentTimeMillis() < (startMillis + 10000)) {synchronized (queue) {try {queue.wait();} catch (InterruptedException e) {e.printStackTrace();}}if (!queue.isEmpty()) {Integer integer = queue.poll();System.out.println("[" + Thread.currentThread().getName() + "]: " + integer);}}}}public static class Producer implements Runnable {public void run() {int i = 0;while (System.currentTimeMillis() < (startMillis + 10000)) {queue.add(i++);synchronized (queue) {queue.notify();}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}synchronized (queue) {queue.notifyAll();}}}public static void main(String[] args) throws InterruptedException {Thread[] consumerThreads = new Thread[5];for (int i = 0; i < consumerThreads.length; i++) {consumerThreads[i] = new Thread(new Consumer(), "consumer-" + i);consumerThreads[i].start();}Thread producerThread = new Thread(new Producer(), "producer");producerThread.start();for (int i = 0; i < consumerThreads.length; i++) {consumerThreads[i].join();}producerThread.join();}
}

main()方法启动五个使用者和一个生产者线程,然后等待它们完成。 然后,生产者线程将新值插入队列,然后通知所有等待线程发生了某些事情。 使用者线程获取队列锁,然后进入睡眠状态,以便稍后再次填充队列时被唤醒。 生产者线程完成工作后,会通知所有消费者线程唤醒。 如果我们不做最后一步,那么消费者线程将永远等待下一个通知,因为我们没有为等待指定任何超时。 取而代之的是,我们至少可以在经过一定时间后使用wait(long timeout)方法来唤醒它。

带wait()和notify()的嵌套同步块

如上一节所述,在对象监视器上调用wait()仅释放该对象监视器上的锁。 由同一线程持有的其他锁不会被释放。 因为这很容易理解,所以在日常工作中,调用wait()的线程可能会进一步锁定。 而且,如果其他线程也在等待这些锁,则会发生死锁情况。 让我们看下面的示例代码:

public class SynchronizedAndWait {private static final Queue queue = new ConcurrentLinkedQueue();public synchronized Integer getNextInt() {Integer retVal = null;while (retVal == null) {synchronized (queue) {try {queue.wait();} catch (InterruptedException e) {e.printStackTrace();}retVal = queue.poll();}}return retVal;}public synchronized void putInt(Integer value) {synchronized (queue) {queue.add(value);queue.notify();}}public static void main(String[] args) throws InterruptedException {final SynchronizedAndWait queue = new SynchronizedAndWait();Thread thread1 = new Thread(new Runnable() {public void run() {for (int i = 0; i < 10; i++) {queue.putInt(i);}}});Thread thread2 = new Thread(new Runnable() {public void run() {for (int i = 0; i < 10; i++) {Integer nextInt = queue.getNextInt();System.out.println("Next int: " + nextInt);}}});thread1.start();thread2.start();thread1.join();thread2.join();}
}

正如我们之前所了解的 ,将同步添加到方法签名等同于创建一个synced(this){}块。 在上面的示例中,我们意外地向该方法添加了synced关键字,然后在对象监视器队列上进行了同步,以便在等待队列中的下一个值时将当前线程置于睡眠状态。 然后,当前线程释放队列上的锁保持,但不释放对此的锁保持。 putInt()方法通知睡眠线程已添加新值。 但是,偶然地,我们还向该方法添加了关键字sync。 现在,第二个线程进入睡眠状态时,它仍然保持锁定状态。 然后,第一个线程无法进入方法putInt(),因为此锁由第一个线程持有。 因此,我们陷入僵局,程序挂起。 如果执行上面的代码,则在程序开始后立即发生。

在日常生活中,情况可能不像上面那样清楚。 线程持有的锁可能取决于运行时参数和条件,导致问题的同步块可能与代码中我们放置wait()调用的位置不太接近。 这使得很难找到此类问题,并且可能是这些问题仅在一段时间后或在高负荷下才会出现。

同步块中的条件

在同步对象上执行某些操作之前,通常必须检查是否满足某些条件。 例如,当您有一个队列时,您要等待直到该队列被填满。 因此,您可以编写一种检查队列是否已满的方法。 如果不是,则在唤醒当前线程之前使其处于睡眠状态:

public Integer getNextInt() {Integer retVal = null;synchronized (queue) {try {while (queue.isEmpty()) {queue.wait();}} catch (InterruptedException e) {e.printStackTrace();}}synchronized (queue) {retVal = queue.poll();if (retVal == null) {System.err.println("retVal is null");throw new IllegalStateException();}}return retVal;
}

上面的代码在调用wait()之前在队列上进行同步,然后在while循环内等待,直到队列中至少有一个条目。 第二个同步块再次将队列用作对象监视器。 它轮询()队列中的内部值。 为了演示起见,当poll()返回null时,抛出IllegalStateException。 当队列中没有要轮询的值时,就是这种情况。

运行此示例时,您将看到IllegalStateException很快就会抛出。 尽管我们已经在队列监视器上正确地同步了,但是会引发异常。 原因是我们有两个单独的同步块。 假设我们有两个线程到达了第一个同步块。 第一个线程进入该块并由于队列为空而进入睡眠状态。 第二个线程也是如此。 现在,当两个线程都唤醒时(通过另一个在监视器上调用notifyAll()的线程),它们都在队列中看到一个值(生产者添加的值。然后,两个线程到达第二个屏障。在这里,第一个线程进入轮询队列中的值,当第二个线程进入时,队列已为空,因此它从poll()调用返回的值作为null并引发异常。

为避免出现上述情况,您将必须在同一同步块中执行所有取决于监视器状态的操作:

public Integer getNextInt() {Integer retVal = null;synchronized (queue) {try {while (queue.isEmpty()) {queue.wait();}} catch (InterruptedException e) {e.printStackTrace();}retVal = queue.poll();}return retVal;
}

在这里,我们在与isEmpty()方法相同的同步块中执行poll()方法。 通过同步块,我们可以确保在给定的时间点上只有一个线程正在此监视器上执行方法。 因此,没有其他线程可以从isEmpty()和poll()调用之间的队列中删除元素。

3.设计多线程

正如我们在最后几节中所看到的,实现多线程应用程序有时比乍一看要复杂。 因此,在启动项目时,请务必牢记清晰的设计。

不变的对象

在这种情况下,非常重要的一种设计规则是不变性。 如果在不同线程之间共享对象实例,则必须注意两个线程不会同时修改同一对象。 但是在无法更改的情况下,不可修改的对象很容易处理。 要修改数据时,始终必须构造一个新实例。 基本类java.lang.String是不可变类的示例。 每次您要更改字符串时,都会得到一个新实例:

String str = "abc";String substr = str.substring(1);

尽管创建对象的操作并非没有成本,但是这些成本经常被高估。 但是,如果具有不可变对象的简单设计胜过不使用不可变对象,则总要权衡一下,因为存在存在并发错误的风险,在项目中可能会发现并发错误。

在下面的内容中,您将找到一组要使类不可变的适用规则:

  • 所有字段均应为最终字段和私有字段。
  • 不应使用setter方法。
  • 应该将类本身声明为final,以防止子类违反不变性原则。
  • 如果字段不是原始类型,而是对另一个对象的引用:
    • 不应有将引用直接暴露给调用者的getter方法。

下列类的实例表示一条消息,其中包含主题,消息正文和一些键/值对:

public final class ImmutableMessage {private final String subject;private final String message;private final Map<String,String> header;public ImmutableMessage(Map<String,String> header, String subject, String message) {this.header = new HashMap<String,String>(header);this.subject = subject;this.message = message;}public String getSubject() {return subject;}public String getMessage() {return message;}public String getHeader(String key) {return this.header.get(key);}public Map<String,String> getHeaders() {return Collections.unmodifiableMap(this.header);}
}

该类是不可变的,因为它的所有字段都是final和private。 在构造实例后,没有任何方法可以修改实例的状态。 返回对主题和消息的引用是安全的,因为String本身是一个不变的类。 例如,获得消息引用的呼叫者无法直接对其进行修改。 对于标题映射,我们必须更加注意。 只要返回对Map的引用,调用者就可以更改其内容。 因此,我们必须返回通过调用Collections.unmodifiableMap()获得的不可修改Map。 这将返回Map上的视图,该视图允许调用者读取值(再次为字符串),但不允许修改。 尝试修改Map实例时,将引发UnsupportedOperationException。 在此示例中,返回特定键的值也是安全的,就像在getHeader(String key)中完成操作一样,因为返回的String再次是不可变的。 如果Map包含本身不可变的对象,则此操作将不是线程安全的。

API设计

在设计类的公共方法(即此类的API)时,您也可以尝试将其设计用于多线程使用。 当对象处于特定状态时,您可能有不应执行的方法。 克服这种情况的一个简单解决方案是拥有一个私有标志,该标志指示我们处于哪种状态,并且在不应调用特定方法时抛出IllegalStateException:

public class Job {private boolean running = false;private final String filename;public Job(String filename) {this.filename = filename;}public synchronized void start() {if(running) {throw new IllegalStateException("...");}...}public synchronized List getResults() {if(!running) {throw new IllegalStateException("...");}...}
}

上面的模式通常也称为“禁止模式”,因为该方法一旦在错误的状态下执行便会失败。 但是您可以使用静态工厂方法设计相同的功能,而无需在每个方法中检查对象的状态:

public class Job {private final String filename;private Job(String filename) {this.filename = filename;}public static Job createAndStart(String filename) {Job job = new Job(filename);job.start();return job;}private void start() {...}public synchronized List getResults() {...}
}

静态工厂方法使用私有构造函数创建Job的新实例,并已在实例上调用start()。 返回的Job引用已经处于可以使用的正确状态,因此getResults()方法仅需要同步,而不必检查对象的状态。

线程本地存储

到目前为止,我们已经看到线程共享相同的内存。 就性能而言,这是在线程之间共享数据的好方法。 如果我们将使用单独的进程来并行执行代码,那么我们将拥有更繁重的数据交换方法,例如远程过程调用或文件系统或网络级别的同步。 但是,如果同步不正确,则在不同线程之间共享内存也将难以处理。

Java中的java.lang.ThreadLocal类提供了仅由我们自己的线程而不是其他线程使用的专用内存:

private static final ThreadLocal myThreadLocalInteger = new ThreadLocal();

通用模板参数T给出了应存储在ThreadLocal内的数据类型。在上面的示例中,我们仅使用了Integer,但在这里我们也可以使用任何其他数据类型。 以下代码演示了ThreadLocal的用法:

public class ThreadLocalExample implements Runnable {private static final ThreadLocal threadLocal = new ThreadLocal();private final int value;public ThreadLocalExample(int value) {this.value = value;}@Overridepublic void run() {threadLocal.set(value);Integer integer = threadLocal.get();System.out.println("[" + Thread.currentThread().getName() + "]: " + integer);}public static void main(String[] args) throws InterruptedException {Thread threads[] = new Thread[5];for (int i = 0; i < threads.length; i++) {threads[i] = new Thread(new ThreadLocalExample(i), "thread-" + i);threads[i].start();}for (int i = 0; i < threads.length; i++) {threads[i].join();}}
}

您可能想知道,即使变量threadLocal被声明为静态的,每个线程也输出的正是通过构造函数获得的值。 ThreadLocal的内部实现确保每次调用set()时,给定值都存储在仅当前线程有权访问的内存区域中。 因此,当您事后调用get()时,尽管存在其他线程可能已经调用过set()的事实,但仍会检索之前设置的值。

Java EE世界中的应用程序服务器大量使用ThreadLocal功能,因为您有许多并行线程,但是每个线程都有自己的事务或安全上下文。 由于您不想在每次方法调用中传递这些对象,因此只需将其存储在线程自己的内存中,并在以后需要时访问它。

翻译自: https://www.javacodegeeks.com/2015/09/concurrency-fundamentals-deadlocks-and-object-monitors.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/358105.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常用的30+种未授权访问漏洞汇总

未授权访问漏洞汇总预览 1 、FTP 未授权访问&#xff08;21&#xff09; 2 、LDAP 未授权访问&#xff08;389&#xff09; 3 、Rsync 未授权访问&#xff08;873&#xff09; 4 、ZooKeeper 未授权访问&#xff08;2181&#xff09; 5 、Docker 未授权访问&#xff08;2375&a…

孙叫兽进阶之路之敏捷开发

孙叫兽,前端全栈工程师,java工程师,技术交流请添加主页QQ群,微信公众号:电商程序员

四:理解Page类的运行机制(例:基于PageStatePersister的页面状态存取)

有人说类似gridview datalist这样的控件最好不要用在高并发,IO大的网站中企业应用中为了快速开发到可以用一用因为这是一类"沉重"的组件我们姑且不谈这种看法的正确性(我个人觉得有道理)只谈它为什么笨重:因为这些控件给页面带来了大量的viewstate<input type&quo…

Fiddler使用教程

一、简介及原理 1.1 Fiddler简介 Fiddler 是一个 HTTP 协议调试代理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的 HTTP 通讯。Fiddler 提供了电脑端、移动端的抓包、包括 http 协议和 https 协议都可以捕获到报文并进行分析&#xff1b;可以设置断点调试、截取…

js 引入 缓存_引入故意缓存

js 引入 缓存几周前&#xff0c;我参加了ThoughtWorks 技术雷达研讨会。 我在ThoughtWorks工作了多年&#xff0c;并认为如果有人知道这些人在软件开发方面的发展趋势如何。 在技​​巧上带有上升箭头的数字中&#xff0c;第17位被称为“周到缓存”。 和斯科特肖一起喝酒时&…

我是如何用JSP在网络上架构一个网上招标系统,以推进网站无纸化,过程电子化,管理智能化的发展

声明&#xff1a;部分代码参考与网络&#xff0c;如有侵权请联系博主删除&#xff0c;博主本着学习的态度和大家一起成长。 项目github地址&#xff1a;https://github.com/sunmenglei/sunmengleiwangshangzhaobiao/ 背景&#xff1a;从本世纪初&#xff0c;互联网开始加速发展…

动态语言支持

本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的过程&#xff01; 在这里查看 &#xff01; 目录 1.简…

我是如何使用git把本地代码上传到github上的,值得借鉴

背景&#xff1a;最近开发了一套招标系统&#xff0c;我是如何用JSP在网络上架构一个网上招标系统&#xff0c;以推进网站无纸化&#xff0c;过程电子化&#xff0c;管理智能化的发展。 使用git进行上传。 首先自己得有git工具及github账号&#xff0c;自己没有的提前准备一下。…

期中总结

期中总结&#xff1a; 第一章 计算机系统漫游 1.1 Linux基础 1.Linux命令 command [options] [arguments] //中括号代表是可选的&#xff0c;即有些命令不需要选项也不需要参数 选项&#xff08;options&#xff09;或参数&#xff08;arguments&#xff09; 选项是调整命令执…

Kali WPScan的使用(WordPress扫描工具)

一、WPScan简介 WordPress网站介绍 WordPress是全球流行的博客网站&#xff0c;全球有上百万人使用它来搭建博客。他使用PHP脚本和Mysql数据库来搭建网站。 Wordpress作为三大建站模板之一&#xff0c;在全世界范围内有大量的用户&#xff0c;这也导致白帽子都会去跟踪 WordPr…

【jquery系列|Jquery总结篇】包含各种实例,文末有彩蛋!

孙叫兽,前端全栈工程师,微信公众号:电商程序员,如果本文对你有帮助,记得收藏点赞+关注。 jQuery 是一个 JavaScript 库,极大地简化了 JavaScript 编程,很容易学习。(注:本文不演示效果) 简介:jQuery是一个快速、简洁的JavaScript框架,是继Prototype之后又一个优…

文件包含之日志中毒(User-Agent)

目录 一、本地文件包含 二、日志中毒 获取反弹shell获得目标服务器的完全控制权 方法一&#xff1a; 方法二&#xff1a;/var/log/auth.log 一、本地文件包含 本地文件包含漏洞指的是包含本地的php文件&#xff0c;而通过PHP文件包含漏洞入侵网站&#xff0c;可以浏览同服务器所…

对象池回收对象_回收对象以提高性能

对象池回收对象总览 在上一篇文章中&#xff0c;我说过对象反序列化更快的原因是由于使用了回收对象。 由于两个原因&#xff0c;这可能令人惊讶&#xff1a;1&#xff09;相信如今创建对象是如此之快&#xff0c;无关紧要或与回收自己一样快&#xff0c;2&#xff09;默认情况…

由于业务需求,我是如何在需要页面添加悬浮按钮进行切换并添加水印的

背景:APP页面位置有限,列表形式展示,需要做一个悬浮按钮进行切换列表,并给切换的列表添加水印便于区分。 孙叫兽,前端全栈程序员,java程序员,微信公众号:电商程序员 目录 悬浮按钮的实现; 水印功能的实现: 悬浮按钮的实现; body: <!-- 在首页添加一个悬浮按钮…

昆仑镜Kunlun-M使用方法

文章目录 项目介绍和安装测试项目介绍根据Readme.md进行安装扫描测试 实战扫描扫描实战报错1报错2更新项目并重新扫描 扫描结果分析漏洞验证 项目介绍和安装测试 项目介绍 根据Readme.md进行安装 请使用python3.6运行该工具&#xff0c;已停止维护python2.7环境。 安装命…

NOIP前夕:noi.openjudge,Maximum sum

Maximum sum 总Time Limit: 1000msMemory Limit: 65536kB Description Given a set of n integers: A{a1, a2,..., an}, we define a function d(A) as below: t1 t2 d(A) max{ ∑ai ∑aj | 1 < s1 < t1 < s2 < t2 < n } is1 js2 Your task is to calc…

Java EE中的RESTful计时器

在这篇文章中...。 EJB计时器旋风之旅 通过带有示例实现的简单REST接口即时使用EJB计时器 更新&#xff08;2015年7月14日&#xff09; 现在可以在OpenShift上使用该应用程序的前端 。 由于我是前端新手&#xff0c;因此我在其他来源的帮助下组装了此HTML5 AngularJS应用程…

如何查看node的版本及安装的位置?

好多粉丝说我的node版本不支持win7,我把安装包放到了主页QQ群群文件&#xff0c;CSDN资源也有上传。 那么如何查看电脑版本&#xff1f; 电脑快捷键winR&#xff0c;cmd进入黑窗口 node -v 如何查看node安装的位置&#xff1f; where node 如下图所示&#xff1a;

应用安全测试技术DAST、SAST、IAST对比分析

应用安全测试技术DAST、SAST、IAST对比分析-持续更新 版权来源&#xff1a;安全牛首发文章&#xff0c;本文仅补充完善。 一、全球面临软件安全危机 我们即将处于一个软件定义一切的时代&#xff0c;这是 “一个最好的时代&#xff0c;也是一个最坏的时代”。 无论是生活中离不…

android开发之shape详解

很多时候&#xff0c;使用shape能够实现的效果&#xff0c;你用一张图片也能够实现&#xff0c;但问题是一张图片无论你怎么压缩&#xff0c;它都不可能比一个xml文件小&#xff0c;因此&#xff0c;为了获得一个高性能的手机App&#xff0c;我们在开发中应该遵循这样一个原则&…