温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2020-03-04 00:58:27 来源:网络 阅读:148 作者:wx5deb0084464f6 栏目:编程语言

通过优锐课核心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干货

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

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI