Enda Lin

keep foolish, keep sharp


  • Home

  • Categories

  • About

  • Archives

  • Tags

《JAVA并发编程的艺术》读书笔记②

Posted on 2019-06-01 |
Words count in article 697 | Reading time 2

前言

JAVA代码在编译之后会变成JAVA字节码,字节码被类加载器加载到JVM里面,JVM执行字节码,将字节码装换成汇编指令在CPU上执行。

JAVA中所执行的并发机制依赖JVM的实现和CPU的指令。

volatile

在多线程并发编程中,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性,如果volatile变量修饰符使用恰当,它比synchronized的使用或执行成本更低,因为它不会引起线程上下文的切换和调度。

作用1(保证共享变量的可见性)

  • 将当前处理器缓存行的数据写会到系统内存中。
  • 这个写会内存的操作会使其他CPU里缓存了该内存地址的数据无效。

对作用1的解释:

为了提高处理速度,处理器不直接与内存进行通信,而是将内存中的数据读到内部的缓存里面再进行操作,而为了保持缓存的一致性,在多处理器模式下,就实现缓存一致性的协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否已经过期,当处理器发现自己缓存行对应的内存地址被修改,将会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,就会重新从系统内存中把数据督导处理器缓存中。

image.png

image.png

volatile实现原则

  • Lock前缀指令会引起处理器缓存写回到内存中。
  • 一个处理器的缓存回写到内存中会导致其他处理器的缓存无效。

特性

  • 可见性:对一个Volatile变量的读,总是能够看到任意线程对这个volatile的最后写入。
  • 原子性:对任意单个volatile的变量的读写具有原子性,但是类似于volatile++这种复合操作不具有原子性。

volatile写-读建立的happens-before关系

  • 从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果。

synchronized

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的Class对象。
  • 对于同步方法块,锁是synchronized括号里配置的对象。

原子操作

原子操作意思就是不可被中断的一个或一系列的操作。

处理器保证原子操作的措施

  • 总线锁:举个例子,当CPU获取到共享变量时,会发出LOCK#信号去组织其它处理器对这个变量的请求
  • 缓存锁:

《JAVA并发编程的艺术》读书笔记①

Posted on 2019-06-01 |
Words count in article 544 | Reading time 1

上下文切换

在单核处理器下,支持多线程执行代码,CPU给每一个线程分配CPU时间片来实现这个机制,由于分配的时间片比较短,一般为几十毫秒,所以CPU需要不停地切换线程,在切换之前会通过程序计数器去保存上一个线程的任务状态,以便在下一次执行这个任务时,可以再次加载这个任务的状态。

任务从保存到再加载的过程就是一次上下文切换。

问题:多线程一定快吗

答:不一定,因为多线程有上下文切换的开销。

如何减少上下文切换

答:减少上下文切换的方法有无锁并发编程、CAS算法、使用最小线程和使用协程。

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以使用一些方法来避免使用锁
  • CAS算法
  • 使用最小线程:避免创建不需要的线程,避免造成大量线程处于等待状态
  • 协程:在当线程里面实现多任务的调度,并在单线程里维持多个任务间的切换

如何避免死锁

  • 避免在一个线程同时获取多个锁
  • 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只用一个资源
  • 尝试使用定时锁,使用lock,tryLock(timeout)来替代使用内部锁机制
  • 对于数据库锁,加锁和解锁都必须在同一个数据库连接里,否则会出现解锁失败的情况

资源限制的问题

所谓资源限制,通常所指的是硬件的条件不能满足程序的需求,在这种情况下,多线程可能会延长执行时间,因为存在上下文切换的开销,这个时候,通常可以考虑集群并行执行程序。

在资源受限的情况下,需要根据具体的情况去决定程序的并发度,以达到最优状态。

进程、程序与线程

Posted on 2019-05-31 |
Words count in article 598 | Reading time 2

线程

线程是一个比进程更小的执行单位,一个进程在其执行的过程中可以产生多个线程,在同一个进程内,各个线程共享着同一块内存空间和同一组系统资源,线程的上下文切换也要比进程要小

程序

程序是含有指令和数据的文件,被存储在磁盘或者其它数据存储设备中,程序是静态的代码。

进程

进程是系统运行程序的基本单位,是动态的,是程序的一次执行过程。
系统运行一个程序是一个进程从创建、运行到消亡的过程。简单来说, 一个进程就相当于一个执行中的程序,它在计算机中一个指令接着另一个指令地执行着, 同时每个进程还会占有某些系统资源如CPU时间、内存空间、文件、输入输出设备等等, 换句话说,当程序在执行时,程序会被操作系统载入内存中,。

JAVA线程状态

image.png

Daemon线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持工作,当一个JAVA虚拟机中不存在非Daemon线程的时候,JAVA虚拟机将会退出。

可以使用Thread.setDaemon(true)来设置Daemon线程。

Deamon线程被用作支持性工作,但是在JAVA虚拟机退出时,Daemo线程中的finally块不一定会执行。

对象、监视器、同步队列和执行线程之间的关系

image.png

线程间的通信

等待/通知机制

image.png

有一些细节上的东西需要注意一下:

  • 使用wait、notify、notifyAll时需要对调用对象加锁。
  • 调用wait方法后,线程状态由RUNNING变成WAITING,并将当前线程放置在对象的等待队列中。
  • notify和notifyAll调用后,等到线程依旧不会从wait返回,而是要等本线程释放锁之后,等待线程才有机会返回
  • notify是将等待队列中的一个等待线程从等待队列中移到同步队列中
  • notifyAll是将等待队列中所有的线程全部移到同步队列中,被移动的线程状态由WAITING变为BLOKCED
  • 从wait方法返回后重新执行的前提是获得对象的锁

hashCode和equals

Posted on 2019-05-31 |
Words count in article 630 | Reading time 2

问题缘由:重写equals时必须要重写hashCode方法

简介

hashCode()的作用是获取哈希码, 也称为散列码,它实际上就是一个int整数, 这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode定义在JDK的Object中, 这就意味者所有的类都含有hashCode方法

散列表存储的是键值对, 它的特点是, 能够根据键快速检索出对应的值, 其中就利用到了散列码。

哈希码的作用是获取对象在哈希表中的索引位置。

HashSet的插入机制

HashSet不允许重复的插入, 在每一次插入时, 会根据hashCode来判断对象的插入位置, 同时会与已经存在的hashcode作比较, 如果没有相符的hashCode, 则没有重复的对象, 如果有重复的hashCode, 则会使用equals来判断值是否相等, 若相等则不插入, 若不相等则重新散列到其它位置, 这样可以大大减少equals的次数, 大大提高执行速度。

重写equals时必须要重写hashCode方法

因为在使用equals之前,首先是使用hashcode去判断对象的索引位置, hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import java.util.*;
import java.lang.Comparable;

public class ConflictHashCodeTest2{

public static void main(String[] args) {
// 新建Person对象,
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
Person p3 = new Person("aaa", 200);
Person p4 = new Person("EEE", 100);

// 新建HashSet对象
HashSet set = new HashSet();
set.add(p1);
set.add(p2);
set.add(p3);

// 比较p1 和 p2, 并打印它们的hashCode()
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
// 比较p1 和 p4, 并打印它们的hashCode()
System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
// 打印set
System.out.printf("set:%s\n", set);
}

/**
* @desc Person类。
*/
private static class Person {
int age;
String name;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String toString() {
return name + " - " +age;
}

/**
* @desc重写hashCode
*/
@Override
public int hashCode(){
int nameHash = name.toUpperCase().hashCode();
return nameHash ^ age;
}

/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}

//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}

//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}

Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}

LinkedList源码学习

Posted on 2019-05-31 |
Words count in article 3.8k | Reading time 21

简介

LinkedList 是一个实现了List和Deque接口的双向链表, List接口都是一个常规的增删改查操作, 而Deque是的LinkedList具有队列的特性, LinkList不是线程安全的,如果先是的LinkedList变成线程安全的,可以使用

1
List list=Collections.synchronizedList(new LinkedList(...));

Read more »

String、StringBuilder和StringBuffer

Posted on 2019-05-30 |
Words count in article 352 | Reading time 1

String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new String.CaseInsensitiveComparator();

public String() {
this.value = "".value;
}

public String(String var1) {
this.value = var1.value;
this.hash = var1.hash;
}

public String(char[] var1) {
this.value = Arrays.copyOf(var1, var1.length);
}

从String 的源码中,我们不难发现, String存储的字符串, 对象是final类型的, 不可变, 线程安全。

StringBuffer and StringBuilder

StringBuffer和StringBuilder都继承了AbstractStringBuilder并且使用的基本都是父类的方法,区别在于前程是线程安全的,后者是线程不安全的,前者比后者快。

1
2
3
4
5
6
7
8
9
10
11
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
private static final int MAX_ARRAY_SIZE = 2147483639;

AbstractStringBuilder() {
}

AbstractStringBuilder(int var1) {
this.value = new char[var1];
}

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

总结

  • 操作少量的数据: 适用String
  • 单线程操作字符串缓冲区下操作大量数据: 适用StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据: 适用StringBuffer

利用Python进行数据分析-Pandas入门

Posted on 2019-05-30 |
Words count in article 1.1k | Reading time 4

本文出自<利用Python进行数据分析 第二版> 侵删

Pandas

Pandas是一个非常巨大的库,采用了大量Numpy的编程风格,基于Numpy构建,含有使数据清洗和数据分析工作变得更加简单的工具.

Pandas的数据结构

Pandas两个主要的数据结构:Series和DataFrame

Read more »

利用Python进行数据分析-NumPy基础

Posted on 2019-05-30 |
Words count in article 2.4k | Reading time 11

本文出自<利用Python进行数据分析 第2版>  侵删

NumPy

NumPy 是Python数值计算最重要的基础包,可以高效处理大数组的数据.

NumPy的ndarray:一种多维的数组对象

ndarray是一个快速而又灵活的同构数据多维容器,是一个N维数组对象,其中所有的元素对象必须要是相同的数据类型,每一个对象包含一个元组和一个属性,分别是shape(一个表示各维度大小的元组)和dtype(一个说明数组数据类型的对象)

Read more »

利用Python进行数据分析-数据结构准备(元组、列表、字典、集合、函数、推导式、柯里化、生成器、itertools模块以及文件读写)

Posted on 2019-05-30 |
Words count in article 2.9k | Reading time 13

本文引用《利用Python进行数据分析·第2版》

元组tuple

元组是一个固定长度而且不可以改变的序列对象

定义元组的方法:

(1) 最简单的方法:

1
2
3
4
5
In [23]: top = 1,2,3

In [24]: top

Out[24]: (1, 2, 3)
Read more »

SpringCloud微服务架构实战读书笔记①

Posted on 2019-05-29 |
Words count in article 1.7k | Reading time 6

微服务架构概述

微服务设计原则

  • 单一设计原则: 单一职责原则指的是一个单元(类、方法或者是服务)关注的应该只是整个系统中单独且有界限的一部分。
  • 服务自治原则: 服务自治指的是每个微服务都应具备独立的业务能力、依赖与运行环境, 服务与服务之间需要高度解耦, 每个服务从开发、测试、构建到部署都应该可以独立的运行, 而不需要依赖其它服务。
  • 轻量级通信机制: 微服务之间应该通过轻量级的通信机制进行交互。
  • 微服务粒度: 根据合理的粒度划分微服务, 而并非越小越好。
Read more »
1…678
Enda Lin

Enda Lin

所有的伟大都是从零开始

79 posts
35 tags
GitHub E-Mail
© 2019 Enda Lin
您是博主的第 2937 位小伙伴 总访问量 2970 次