Java线程同步和并发第1部分

通过优锐课核心java学习笔记中,我们可以看到,码了很多专业的相关知识, 分享给大家参考学习。我们将分两部分介绍Java中的线程同步,以更好地理解Java的内存模型。

介绍

Java线程同步和并发是复杂应用程序各个设计阶段中讨论最多的主题。 线程,同步技术有很多方面,它们可以在应用程序中实现高并发性。 多年来,CPU(多核处理器,寄存器,高速缓存存储器和主内存(RAM))的发展已导致通常是开发人员往往忽略的某些领域-例如线程上下文,上下文切换,变量可见性,JVM内存 型号与CPU内存型号。

在本系列中,我们将讨论Java内存模型的各个方面,包括它如何影响线程上下文,Java中实现并发的同步技术,竞争条件等。在本文中,我们将重点讨论线程,同步的概念 技术以及Java和我们的CPU的内存模型。

概括

在深入研究线程和同步这一主题之前,让我们快速回顾一下一些与线程相关的术语和概念。

1.Lock —锁是线程同步机制。
2.

  1. Java中的每个对象都有一个与之关联的固有锁。线程使用对象的监视器进行锁定或解锁。锁可以视为逻辑上是内存中对象标头的一部分的数据。有关监视器无法实现的扩展功能,请参见ReentrantLock。

3.Java中的每个对象都有同步方法,wait()和notify()[也notifyAll()]。任何调用这些方法的线程都使用其监视器获得该对象的锁。必须使用synced关键字来调用此方法,否则将抛出IllegealMonitorStateException。

4.信号是一种通知线程应该继续执行的方法。这是使用对象方法wait(),notify()和notifyAll()实现的。调用方法notify()或notifyAll()可以使线程单一以唤醒后台线程(通过调用方法wait())。

5.丢失信号-方法notify()和notifyAll()不保存方法调用,也不知道其他线程是否调用过wait()。如果一个线程在被通知的线程调用了wait()之前调用了notify(),则该信号将被等待的线程错过。这可能导致线程无休止地等待,因为它错过了信号

6.Runnable是一个功能性接口,可以由应用程序中的任何类实现,以便线程可以执行它。

7.volatile是分配给变量以使类成为线程安全的另一个关键字。要了解此关键字的用法,必须了解CPU体系结构和JVM内存模型。我们稍后再讨论。

8.ThreadLocal允许创建只能由所有者线程读取/写入的变量。这用于使代码安全。

9.Thread Pool是线程的集合,线程将在其中执行任务。线程的创建和维护非常受服务控制。在Java中,线程池由ExecutorService的实例表示。

10.ThreadGroup是该类提供一种用于将多个线程收集到单个对象中的机制,并允许我们一次操纵/控制这些线程。

11.Daemon线程-这些线程在后台运行。守护程序线程的一个很好的例子是Java Garbage Collector。 JVM在退出以完成其执行之前不等待守护程序线程(而JVM在等待非守护程序线程或用户线程完成其执行之前)。

12.synchronized-关键字,用于在多个线程必须在并发模式下执行同一功能时控制单个线程执行代码。此关键字可用于方法和代码块以实现线程安全。请注意,此关键字没有超时,因此有可能发生死锁情况。

13.死锁-一种情况,一个或多个线程正在等待对象锁被另一线程释放。导致死锁的可能情况是线程正在相互等待释放锁的地方!

14.虚假唤醒-由于莫名其妙的原因,即使未调用notify()和notifyAll(),线程也有可能唤醒。这是一个虚假的唤醒。为了解决此问题,唤醒的线程围绕自旋锁中的条件自旋。

Java

1
public synchronized doWait() {
2
 ?while(!wasSignalled) { // spin-lock check to avoid spurious wake up calls
3
 ? ?wait();
4
  }
5
 ?// do something
6
}
7

8
public synchronized doNotify() {
9
 ?wasSignalled = true;
10
 ?notify();
11
}

线程匮乏

当没有为某个线程分配CPU时间(因为其他线程占用了所有线程)时,就会发生线程饥饿。 (例如,在某个对象上等待的线程(调用了wait())保持无限期等待,因为其他线程不断被唤醒(通过调用notify())。

为了减轻这种情况,我们可以使用Thread.setPriority(int priority)方法为线程设置优先级。 优先级参数必须在Thread.MIN_PRIORITY到Thread.MAX_PRIORITY之间的设置范围内。 查看官方线程文档以获取有关线程优先级的更多信息。

锁定界面与同步关键字

1.无法在同步块或方法中具有超时。 这可能会在应用程序似乎挂起,死锁等情况下结束。同步块必须仅包含在单个方法中。

  1. Lock接口的实例可以使用单独的方法调用lock()和unlock()。 此外,锁也可以具有超时。 与synced关键字相比,这是两个很大的好处。
Java

1
class CustomLock {
2
3
 ?private boolean isLocked = false;
4
5
 ?public synchronized void lock()
6
 ?          throws InterruptedException {
7
 ? ?
8
 ? ?isLocked = true;
9
 ? ?while(isLocked) {
10
 ? ? ?// calling thread releases the lock it holds on the monitor
11
 ? ? ?// object. Multiple threads can call wait() as the monitor is released.
12
 ? ? ?wait();
13
 ?  }
14
  }
15
16
 ?public synchronized void unlock() {
17
 ? ?isLocked = false;
18
 ? ?notify();
19
 ? ?// only after the lock is released in this block, the wait() block
20
 ? ?// above can re-acquire the lock on this object‘s monitor.
21
  }
22
}

线程执行

我们可以通过两种方式在Java中执行线程。 他们是:

1.扩展Thread类并调用start()方法。 (这不是从Thread子类化类的首选方式,因为它减少了添加该类更多功能的范围。)

2.实现Runnable或Callable接口 这两个接口都是功能性接口,这意味着它们都只定义了一个抽象方法。 (将来也可以通过实现其他接口来扩展作为类的首选方法。)

可运行的界面

这是用于通过线程执行特定任务的基本接口。 此接口仅描述一种方法,称为带有无效返回类型的run()。 如果必须在线程中执行任何功能,但不期望返回类型,请实现此接口。 基本上,在失败的情况下,无法检索线程的结果或任何异常或错误。

通话界面

这是一个接口,除了获得执行结果之外,还用于通过线程执行特定任务。 该接口遵循泛型。 对于实现此接口的类,它仅描述了一种称为call()的方法,并描述了返回类型。 如果必须在线程中执行任何功能并且必须捕获执行结果,请实现此接口。
同步技术

如上所述,可以使用同步关键字或使用Lock实例来同步线程。 Lock接口的基本实现是ReentrantLock类。 同样,用于读/写操作的Lock接口也有所不同。

当线程试图读取或写入资源时,这有助于应用程序实现更高的并发性。 此实现称为ReentrantReadWriteLock。 这两个类之间的主要区别如下所示:

Class ReentrantLock ReentrantReadWriteLock类

只允许访问1个线程以进行读取或写入,但不能同时访问两者。 如果操作正在读取资源,则一次允许访问多个/所有线程。

如果该操作是写操作,则一次只能访问一个线程。

锁定读写操作的资源,使操作互斥。 锁定读写操作的资源,使操作互斥。

由于资源被锁定(甚至用于读取操作),因此降低了性能。 在性能方面更好,因为它为要执行读取操作的所有线程提供并发访问权限。

请参阅下面的ReentrantReadWriteLock示例,以了解如何在仅允许一个线程更新资源的同时实现对资源的并发读取。

注意:资源可以是应用程序中各种线程尝试同时访问的任何数据。

Java

1
public class ConcurrentReadWriteResourceExample {
2

3
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
4
    private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
5
    private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
6

7
    private void readResource() {
8
        readLock.lock();
9
        // read the resource from a file, cache, database or from memory
10
 ? ? ? ?// this block can be accessed by ‘N‘ threads concurrently for reading
11
        readLock.unlock();
12
    }
13

14
    private void writeResource(String value) {
15
        writeLock.lock();
16
        // write or update value to either a file, cache, database or from memory
17
 ? ? ? ?// this block can be accessed by at-most ‘1‘ thread at a time for writing
18
        writeLock.unlock();
19
    }
20
}

创建上述类的一个实例,并将其传递给多个线程; 将处理以下内容:

??‘N‘个线程使用readLock或最多一个线程使用writeLock。
??读或写都不会同时发生。

Java内存模型和CPU

有关Java和CPU内存模型的说明将帮助我们更好地了解对象和变量在Java堆/线程堆栈中的存储方式以及实际CPU内存的存储方式。 现代的CPU由寄存器组成,这些寄存器充当处理器本身的直接存储器,高速缓存存储器-每个处理器都有一个高速缓存层来存储数据,最后是存在应用程序数据的RAM或主存储器。

JVM与CPU内存模型

在硬件或CPU上,线程堆栈和堆都位于主内存中。 线程堆栈和堆的某些部分有时可能会出现在CPU缓存和内部寄存器中。 以下是由于上述体系结构而可能发生的问题:

1,并非所有访问变量的线程都立即看到对共享变量的线程更新(写)的可见性。
2.读取,检查和更新共享变量的数据时的种族条件。

volatile关键字

volatile关键字是Java 5中引入的,在实现线程安全方面有重要的用途。 此关键字可用于基元和对象。 在变量上使用volatile关键字可确保在更新后直接从主存储器读取给定变量并将其写回主存储器。

ThreadLocal类别

线程同步的最后一个主题是Lock是Java类ThreadLocal之后的主题。 此类可创建只能由同一线程读取/写入的变量。 这为我们提供了一种通过定义线程局部变量来实现线程安全的简单方法。 ThreadLocal在线程池或ExecutorService中有重要用途,因此每个线程都使用自己的某些资源或对象的实例。

例如,对于每个线程,都需要一个单独的数据库连接,或者一个单独的计数器。 在这种情况下,ThreadLocal会有所帮助。 这在Spring Boot应用程序中也使用,其中为每个传入呼叫设置了用户上下文(Spring Security),并且将通过各种实例在线程流之间共享用户上下文。 在以下情况下使用ThreadLocal

?线程限制。
?每个线程的数据性能。
?每个线程上下文。

Java

1
/**
2
 * This is a demo class only. The ThreadLocal snippet can be applied
3
 * to any number of threads and you can see that each thread gets it‘s
4
 * own instance of the ThreadLocal. This achieves thread safety.
5
*/
6
public class ThreadLocalDemo {
7

8
    public static void main(String...args) {
9
        ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
10
            protected String initialValue() {
11
                return "Hello World!";
12
            }
13
        };
14

15
        // below line prints "Hello World!"
16
        System.out.println(threadLocal.get());
17

18
        // below line sets new data into ThreadLocal instance
19
        threadLocal.set("Good bye!!!");
20

21
        // below line prints "Good bye!!!"
22
        System.out.println(threadLocal.get());
23

24
        // below line removes the previously set message
25
        threadLocal.remove();
26

27
        // below line prints "Hello World!" as the initial value will be
28
        // applied again
29
        System.out.println(threadLocal.get());
30
    }
31
}

线程同步和相关概念就是这样。 并发将在本文的第2部分中介绍。

喜欢这篇文章的可以点个赞,欢迎大家留言评论,记得关注我,每天持续更新技术干货、职场趣事、海量面试资料等等
?> 如果你对java技术很感兴趣也可以交流学习,共同学习进步。?
不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代

文章写道这里,欢迎完善交流。最后奉上近期整理出来的一套完整的java架构思维导图,分享给大家对照知识点参考学习。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java干货

原文地址:https://blog.51cto.com/14634606/2473709

时间: 2024-10-01 03:57:52

Java线程同步和并发第1部分的相关文章

Java 线程同步

Java 线程同步根本上是要符合一个逻辑:加锁------>修改------>释放锁 1.同步代码块 示例如下: public class SyncBlock { static class DataWrap { int i; } static class SyncBlockThread extends Thread { private DataWrap date; public SyncBlockThread(DataWrap dataWrap) { this.date = dataWrap;

Java线程测试高并发

package com.expai.utils; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Execu

java线程 同步临界区:thinking in java4 21.3.5

thinking in java 4免费下载:http://download.csdn.net/detail/liangrui1988/7580155 package org.rui.thread.critical; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.uti

Java 线程同步执行(顺序执行)

关于线程,有两种实现方法, 一种是通过继承Runnable接口,另外一种通过扩展Thread类,两者的具体差别,可参考我找的这篇文章 http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html .本主主要是讲 线程的同步执行问题.. 如果程序是通过扩展Thread类的,网上的资料说可以通过 join()函数实现,但本人亲测,此法无法通过.程序如下: public class test1 extends Thread { pu

Java线程同步(synchronized)——卖票问题

卖票问题通常被用来举例说明线程同步问题,在Java中,采用关键字synchronized关键字来解决线程同步的问题. Java任意类型的对象都有一个标志位,该标志位具有0,1两种状态,其开始状态为1,当某个线程执行了synchronized(object)语句后,object对象的标志位变为0状态,直到执行完整个synchronized语句中的代码块后,该对象的标志位又回到1状态. 当一个线程执行到synchronized(object)语句的时候,先检查object对象的标志位,如果为0状态,

(转) Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()

为了解决对共享存储区的访问冲突,Java 引入了同步机制.但显然不够,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个. 为解决访问控制问题,Java 引入阻塞机制.阻塞指的是暂停一个Java线程同步的执行以等待某个条件发生(如某资源就绪). sleep():允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时 间,指定的时间一过,线程重新进入可执行状态.典型地,sleep() 被用在等待某个资源就绪的情

Java线程同步模型-生产者与消费者

Java生产者与消费者模型是经典Java线程同步模型,涉及使用同步锁控制生产者线程和消费者线程同步运行问题.同步对象是仓库资源,生产者线程生产向仓库中生产商品,消费者线程从仓库中消费商品,当生产者线程生产的商品达到仓库的90%时,生产者线程停止生产并通知消费者线程开始消费,当消费者线程消耗到仓库的10%时,消费者线程停止消费并通知生产者线程恢复生产,如此循环往复过程. 如下图解: T1时刻分析 T2时刻 T3时刻 T4时刻 上图的分析,T3同T1时刻相同场景,T2同T2时刻相同场景,程序如此循环

【java并发】(2) Java线程同步:synchronized锁住的是代码还是对象

在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行.synchronized既可以加在一段代码上,也可以加在方法上. 关键是,不要认为给方法或者代码段加上synchronized就万事大吉,看下面一段代码: class Sync { public synchronized void test() { System.out.println("test开始.."); try { Thread.sle

Java线程同步类容器和并发容器(四)

同步类容器都是线程安全的,在某些场景下,需要枷锁保护符合操作,最经典ConcurrentModifiicationException,原因是当容器迭代的过程中,被并发的修改了内容. for (Iterator iterator = tickets.iterator(); iterator.hasNext();) { String string = (String) iterator.next(); tickets.remove(20); } //多线程使用Vector或者HashTable的示例