Java并发编程学习笔记(二)线程安全性 2

简介:

内置锁

    Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。

1
2
3
4
5
6
/*
  *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349
  */
synchronzied (lock){
     //访问或修改由锁保护的共享状态
}

    每个Java独享都可以用作一个实现同步的锁,这些锁被称为内置锁(Itrinsic Lock)或者监事锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁。Java的内置锁相当于一种互斥体,这意味着最多只有一个线程能持有这种锁。当线程1尝试获取一个由线程2持有的锁时,线程1必须等待或者阻塞,知道线程2释放这个锁。由于这个锁保护的同步代码块会以原子方式执行(一组语句作为一个不可分割的单元被执行),多个线程在执行改代码块时互不干扰。我们再来改善下上篇博客 Java并发编程学习笔记(一)线程安全性 1 最后那段Servlet处理因数分解的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
  *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349
  */
  @ThreadSafe             
//并非好的代码          
public  class  SynchronizedFactorizer  implements  Servlet {
     @GuardedBy ( "this" private  BigInteger lastNumber;
     @GuardedBy ( "this" private  BigInteger[] lastFactors;
     
     public  synchronized  void  service(ServletRequest req,ServletResponse resp) {
         BigInteger i = extractFromRequest(req);
         if (i.equals(lastNumber))
             encodeIntoResponse(resp,lastNumber.get());
         else {
             BigInteger[] factors = factor(i);
             lastNumber = i;
             lastFactors = factors;
             encodeIntoResponse(resp,factors);
         }
     }
  }

    这个Servlet能正确地缓存最新的计算结果,但并发性却非常糟糕,服务的响应性非常低,无法令人接受。这是一个性能问题,并不是线程安全的问题。


可重入   

线程安全:被多个并发的线程反复调用时,他会产生正确的结果。可重入:当被多个线程调用的时候,不会引用任何共享数据。

    当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。不过,内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。可重入的一种实现方法,是为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1.如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数值会相应地递减。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
  *原文出处:http://liuxp0827.blog.51cto.com/5013343/1414349
  */
public  class  Widget {
     public  synchronized  void  doSomething() {
       ...
     }
}
 
public  class  LoggingWidget  extends  Widget {
     public  synchronized  void  doSomething() {
       System.out.println(toString() +  ": calling doSomething" );
       super .doSomething();
     }
}

    上面代码中,子类改写了父类的synchronized方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码将死锁。每个doSomething方法在执行前都会获取Widget上的锁,如果这个内置锁不是可重入的,那么在调用super.doSomething时将无法获得Widget上的锁。重入则避免了这样的情况。可重入函数一定是线程安全的;线程安全的函数可能是重入的,也可能是不重入的;线程不安全的函数一定是不可重入的。


用锁来保护状态

    由于锁能使其保护的代码路径以串行形式(多个线程依次以独占的方式访问对象,而不是并发的访问)来访问,因此可以通过锁来构造一些协议已实现对共享状态的独占访问。

    访问共享状态的复合操作,例如上篇博客中提到的命中计数器的递增操作或者延迟初始化,都必须是原子操作。但仅仅将复合操作封装到同步代码块是不够的。如果用同步来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要使用同步,而且,使用锁来协调对某个变量的访问时,在访问变量的所有位置上都要使用同一个锁。对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,称状态变量是由这个锁保护的。

    上述代码SynchronizedFactorizer中,lastNumber和lastFactors都是有Servlet的内置锁保护的,对象的内置锁与其状态之间没有内在的关联。虽然大多数类都将内置锁用作一种有效的加锁机制,但对象的域并不一定要通过内置所来保护。当线程获取与对象关联的锁时,并不能阻止其他线程访问对象,只能阻止其他线程获得同一个锁。

    一种常见的加锁约定,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问。但是,如果在添加新的方法或者代码路径忘记了同步,那么这种加锁协议会很容易被破坏。

    当某个变量由锁保护时,每次访问这个变量时都需要首先获得锁,这样就保证了同一时刻只有一个线程可以访问这个变量。当类的不变形条件涉及到多个状态变量的时候,每个变量都必须由同一个锁来保护。因此可以在单个原子操作中访问这些变量。










本文转自 ponpon_ 51CTO博客,原文链接:http://blog.51cto.com/liuxp0827/1414349,如需转载请自行联系原作者
目录
相关文章
|
1天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
2天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
2天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
2天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
3天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
10 1
|
3月前
|
Oracle Java 关系型数据库
Java 编程指南:入门,语法与学习方法
Java 是一种流行的编程语言,诞生于 1995 年。由 Oracle 公司拥有,运行在超过 30 亿台设备上。Java 可以用于: 移动应用程序(尤其是 Android 应用) 桌面应用程序 网络应用程序 网络服务器和应用程序服务器 游戏 数据库连接 等等!
36 1
|
8月前
|
存储 算法 Java
吐血整理Java编程基础入门技术教程,免费送
吐血整理Java编程基础入门技术教程,免费送
33 0
|
开发框架 Java C语言
Java学习路线-1:编程入门
Java学习路线-1:编程入门
71 0
|
小程序 安全 前端开发
【Java编程进阶】Java语言基础入门篇
整个Java全栈编程知识体系十分庞大,包括JavaSE知识,Web前端,Web后端,数据库相关的知识等,初学者应该系统踏实的学习,一步一个脚印。Java语言是一种完全面向对象的跨平台语言。有很多突出的优点,例如简单易学,面向对象,分布式,安全可靠,解释型语言,跨平台运行,可移植高性能多线程,可实现网络编程等。
138 0
【Java编程进阶】Java语言基础入门篇
|
Java
真的,Java并发编程基础入门看这个就够了
Java并发编程学习之02Java并发编程入门指南 1. Java天生多线程 2. Java启动多线程实现方式 2.1 实现代码 2.2 Thread和Runnable的区别 2.3 start和run方法的区别 3. Java如何停止线程呢 3.1 已弃用方法 3.2 推荐使用 4. 守护线程 5. 优先级 6. 线程生命周期 代码仓
132 0
真的,Java并发编程基础入门看这个就够了