实战Java高并发程序设计-读书笔记

实战Java高并发程序设计-读书笔记

第一章

死锁、饥饿、活锁的概念。

并发级别:阻塞、饥饿、无障碍、无锁、无等待。

无障碍:是一种最弱的非阻塞调度。两个线程如果是无障碍的执行,那么他们不会因为临界区的问题导致一方被挂起。但是一旦检测到冲突,就应该进行回滚。

无锁:无锁的并行都是无障碍的,在无锁的情况下,所有的线程都能尝试对临界区进行访问,但是不同的是,无锁的迸发保证必然有一个线程能够在有限步内完成操作离开临界区。

while(!atomicVar.compareAndSet(localVar,localVar+1)){
    localVar = atomicVar.get();
}

无等待:要求所有的线程都必须在有限步内完成,这样就不会引起饥饿问题。一种典型的无等待结构就是RCU(Read-Copy-Update)。它的基本思想是,对数据的读可以不加控制。因此,所有的读线程都是无等待的。但是在写数据的时候,先取得原始数据的副本,接着值修改副本数据,修改完成后,在合适的时机回写数据。

Amdah1定律

加速比定义: 加速比=优化前系统耗时/优化后系统耗时
加速比计算方式

Java的内存模型(JMM)

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。

  • 原子性

原子性是指一个操作是不可中断的。即使在多个线程一起执行的时候。一个操作一旦开始,就不会被其他线程干扰。
比如对于32位系统来说,对long型数据的读写不是原子性的。对int型数据读写是原子性的。

  • 可见性

    可见性是指当一个线程修改了某一个共享变量的值,其他线程能否立即知道这个修改。可见性问题是一个综合性问题。除了缓存优化或者硬件优化(有些内存读写可能会立即出发,而会先进入一个硬件队列等待)会导致可见性问题外,指令重排以及编辑器的优化,都有可能导致一个线程的修改避讳立即被其他线程察觉。
    可见性说明

  • 有序性
    在并发时,程序的执行可能会出现乱序。给人直观的感觉是:写在前面的代码,会在后面执行。原因是程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
    指令重排说明

假设线程A首先执行write()方法,接着线程B执行reader()方法,如果发生指令重排,那么线程B在代码第10行时,不一定看到a已经被赋值为1了。
指令重排是有一个基本的前提的,就是保证串行语义的一致性。指令重排不会使串行的语义逻辑发生问题。

那些指令不能重排:Happen-Before规则

虽然Java虚拟机和执行系统会对指令进行一定的重排,但是指令重排是有原则的,并非所有的指令都可以随便改变位置,一下是一些基本原则。

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
  • 锁规则:解锁(unlock)必然发生于随后的加锁(lock)前
  • 传递性:A先与B,B先于C,那么A必然先于C
  • 线程的start()方法先于它的每个动作
  • 线程的所有操作先于线程的终结(Thread.join())
  • 线程的中断(interrupt())先于被中断线程的代码
  • 对象的构造函数执行、结束先于finalize()方法

Java并行程序基础

继承Thread,继承Runable,继Callable,FutureTask等方式

线程状态图

线程状态图

2.2.1新建线程
2.2.2终止线程

线程的stop方法是被废弃而不推荐使用的,因为stop()方法太过暴力,强行把执行到一半的线程终止,可能会引发一些数据不一致的问题。

2.2.3线程中断
public void Thread.interrupt()  //中断线程
public boolean Thread.isInterrupted()  //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态

Thread interrupt代码
Thread interrupt说明

2.2.4等待(wait)和通知(notify)

wait notify


package com.alibaba.dubbo.consumer;

/**
 * {@code notify()} {@code wait()} 方法测试
 * @author voidme
 */
public class NotifyTest {
    public final  static Object object=new Object();
    public  static class T1 extends  Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T1 start");
                System.out.println(System.currentTimeMillis()+": T1 wait for object");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+": T1 end");
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T2 start notify object");
                object.notify();
                System.out.println(System.currentTimeMillis()+":T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1=new T1();
        Thread t2=new T2();
        t2.start();
        t1.start();
    }
}

转自知乎用户 世界这么大,多走走挺好~的回答:
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

相关衔接 锁池 等待池

然后再来说notify和notifyAll的区别

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。

有了这些理论基础,后面的notify可能会导致死锁,而notifyAll则不会的例子也就好解释了

2.2.5挂起(suspend)和继续执行(resume)线程

不推荐使用suspend方法的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何锁资源。此时,其他任何线程想要访问被它暂用的锁时,都会被牵连,导致无法正常继续运行。直到对应的线程上进行了resume()操作,被挂起的线程才能继续,从而其他所有阻塞在相关锁上的线程也可以继续执行。但是,如果resume()操作意外的在suspend()前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且,更严重的是:它所占用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上来看,居然还是Runnable,这也会严重影响我们队当前系统状态的判断。

2.2.6等待线程结束(join)和谦让(yied)

join的本质是让调用线程wait()在当前线程对象实例上。

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
while(isAlive()){
    wait(0);
}

它让调用线程在当前线程对象上进行等待。当线程执行完成后,被等待的线程会退出前调用notifyAll()通知所有等待线程继续执行。因此,不要在Thread对象实例上使用类似wait()或者notify()等方法。

public static native void yied();

这是一个静态方法,一旦执行,它会使当前线程让出CPU。让出CPU并不表示当前线程不执行了。当前线程还是会进行CPU资源的争夺。但是能否再次被分配到,就不一定了。如果一个线程不是那么重要,或者优先级非常低,而且有害怕它会占用太多的CPU资源,那么可以在适当的时候调用Thread.yield(),给与其他重要线程更多的工作机会。

2.3volatile与Java内存模型(JMM)

volate能保证写入操作的原子性。如

public volatile static long t=0;

但是volatile并不能替代锁,它无法保证一些复合操作的原子性。比如i++.

此外,volatile也能保证数据的可见性和有序性.

2.4 分门别类的管理:线程组

线程组
activeCount()可以获得活动线程总数,但是线程是动态的,这个值是一个估计值。list()方法可以打印这个线程组中所有的线程信息。

关于ThreadGroup使用注意两点:
1. 不要使用stop(),他会停止线程组中的所有线程。
2. 在创建线程组合线程的时候,给他们去一个有意义的名字。比如HttpHander、FTPService。

驻守后台:守护线程(Daemon)

守护线程是一种特殊的线程,就和它的名字一样,是系统的守护者,在后台完成一些系统的服务。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
天猫是中国最大的电商平台之一,其背后支撑的是一套高并发、分布式的架构设计。在天猫的架构设计中,Java作为主要的开发语言之一,扮演了重要的角色。 首先,天猫的架构设计采用了分布式的思想。在天猫的系统中,不同的功能模块被拆分为独立的服务,每个服务都运行在不同的服务器上,实现了内容的分布与并发处理。这种架构设计可以实现高可用性和横向扩展的特性,使得系统在面对亿级高并发时能够保持稳定。 其次,天猫的架构设计注重性能优化。在天猫的系统中,通过使用缓存技术和负载均衡技术,优化了系统的响应时间和吞吐量。同时,利用集群和分布式数据库等技术,实现了数据的高可靠性和快速读写。通过这些手段,天猫在高并发情况下能够有效地提供服务,保证用户的购物体验。 另外,天猫的架构设计还考虑了安全性和可扩展性。在安全方面,天猫采用了多层次的安全机制,包括数据加密、防火墙等,确保用户的信息和交易数据的安全。在可扩展性方面,天猫的系统可以随着用户数量的增加进行水平扩展,通过增加服务器数量和服务节点,提高系统的并发处理能力。 总的来说,天猫的架构设计在Java的支持下,通过分布式、性能优化、安全性和可扩展性等方面的设计,实现了对亿级高并发的支持。这个设计可以为电商行业提供参考,帮助其他电商平台构建具有高并发能力的系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值