首页 » Latest Post

这些年,我们的 CPU、内存、I/O 设备都在不断迭代,不断朝着更快的方向努力。但是,在这个快速发展的过程中,有一个核心矛盾一直存在,就是这三者的速度差异。为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系机构、操作系统、编译程序都做出了贡献,主要体现为:1.CPU 增加了缓存,以均衡与内存的速度差异;2.操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异 3. 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用

一:缓存导致的可见性问题,同一个cpu的缓存可见,但是不同cpu的缓存不那么可见了。这是硬件程序员给软件程序员挖的坑

二:线程切换带来的原子性问题

  • 我们潜意识里面觉得 count+=1 这个操作是一个不可分割的整体,就像一个原子一样,线程的切换可以发生在 count+=1 之前,也可以发生在 count+=1 之后,但就是不会发生在中间。
  • 我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性
  • CPU 能保证的原子操作是 CPU 指令级别的,而不是高级语言的操作符,这是违背我们直觉的地方。因此,很多时候我们需要在高级语言层面保证操作的原子性。

三:编译优化带来的有序性问题

  • 例如下面的代码:

在 Java 领域一个经典的案例就是利用双重检查创建单例对象,例如下面的代码:在获取实例 getInstance() 的方法中,我们首先判断 instance 是否为空,如果为空,则锁定 Singleton.class 并再次检查 instance 是否为空,如果还为空则创建 Singleton 的一个实例。

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;

  }
}

这看上去一切都很完美,无懈可击,但实际上这个 getInstance() 方法并不完美。问题出在哪里呢?出在 new 操作上,我们以为的 new 操作应该是:

分配一块内存 M;
在内存 M 上初始化 Singleton 对象;
然后 M 的地址赋值给 instance 变量。

但是实际上优化后的执行路径却是这样的:

分配一块内存 M;
将 M 的地址赋值给 instance 变量;
最后在内存 M 上初始化 Singleton 对象。

优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

如果对instance进行volatile语义声明,就可以禁止指令重排序,避免该情况发生。

Java 里 synchronized、wait()/notify() 相关的知识很琐碎,看懂难,会用更难。但实际上 synchronized、wait()、notify() 不过是操作系统领域里管程模型的一种实现而已,Java SDK 并发包里的条件变量 Condition 也是管程里的概念

1.并发编程可以总结为3个核心问题:分工、同步、互斥

  • 分工:指的是如何高效地拆解任务并分配给线程,类似于现实中一个组织完成一个项目,项目经理要拆分任务,安排合适的成员去完成。
  • 例如 Fork/Join 框架就是一种分工模式
  • 著名数学家华罗庚曾用“烧水泡茶”的例子通俗地讲解了统筹方法(一种安排工作进程的数学方法),“烧水泡茶”这么简单的事情都这么多说道,更何况是并发编程里的工程问题呢。
  • Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是一种分工方法。
  • 并发编程领域还总结了一些设计模式,基本上都是和分工方法相关的,例如生产者 - 消费者、Thread-Per-Message、Worker Thread 模式等都是用来指导你如何分工的。
  • 其实这就是生产者 - 消费者模式的一个优点,生产者一个一个地生产数据,而消费者可以批处理,这样就提高了性能。
  • 同步:指的是线程之间如何协作,分好工之后,就是具体执行了。在项目执行过程中,任务之间是有依赖的,一个任务结束后,依赖它的后续任务就可以开工了,后续工作怎么知道可以开工了呢?这个就是靠沟通协作了,这是一项很重要的工作。
  • 在并发编程领域里的同步,主要指的就是线程间的协作,本质上和现实生活中的协作没区别,不过是一个线程执行完了一个任务,如何通知执行后续任务的线程开工而已。
  • 协作一般是和分工相关的。Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是分工方法,但同时也能解决线程协作的问题。例如,用 Future 可以发起一个异步调用,当主线程通过 get() 方法取结果时,主线程就会等待,当异步执行的结果返回时,get() 方法就自动返回了。主线程和异步线程之间的协作,Future 工具类已经帮我们解决了。除此之外,Java SDK 里提供的 CountDownLatch、CyclicBarrier、Phaser、Exchanger 也都是用来解决线程协作问题的。
  • 工作中遇到的线程协作问题,基本上都可以描述为这样的一个问题:当某个条件不满足时,线程需要等待,当某个条件满足时,线程需要被唤醒执行。例如,在生产者 - 消费者模型里,也有类似的描述,“当队列满时,生产者线程等待,当队列不满时,生产者线程需要被唤醒执行;当队列空时,消费者线程等待,当队列不空时,消费者线程需要被唤醒执行。”
  • 在 Java 并发编程领域,解决协作问题的核心技术是管程monitor,上面提到的所有线程协作技术底层都是利用管程解决的。管程是一种解决并发问题的通用模型,除了能解决线程协作问题,还能解决下面我们将要介绍的互斥问题。可以这么说,管程是解决并发问题的万能钥匙。
  • 互斥则是保证同一时刻只允许一个线程访问共享资源,分工、同步主要强调的是性能,但并发程序里还有一部分是关于正确性的,用专业术语叫“线程安全”。
  • 并发程序里,当多个线程同时访问同一个共享变量的时候,结果是不确定的。不确定,则意味着可能正确,也可能错误,事先是不知道的。而导致不确定的主要源头是可见性问题、有序性问题和原子性问题
  • 为了解决这三个问题,Java 语言引入了内存模型,内存模型提供了一系列的规则,利用这些规则,我们可以避免可见性问题、有序性问题,但是还不足以完全解决线程安全问题。解决线程安全问题的核心方案还是互斥
  • 所谓互斥,指的是同一时刻,只允许一个线程访问共享变量。实现互斥的核心技术就是锁,
  • 虽说锁解决了安全性问题,但同时也带来了性能问题
  • Java SDK 里提供的 ReadWriteLock、StampedLock 就可以优化读多写少场景下锁的性能。还可以使用无锁的数据结构,例如 Java SDK 里提供的原子类都是基于无锁技术实现的。
  • 除此之外,还有一些其他的方案,原理是不共享变量或者变量只允许读。这方面,Java 提供了 Thread Local 和 final 关键字,还有一种 Copy-on-write 的模式。
  • 使用锁除了要注意性能问题外,还需要注意死锁问题。
  1. Java SDK 并发包其余的一部分则是并发容器和原子类,这些比较容易理解,属于辅助工具,其他语言里基本都能找到对应的。

1.创建配置文件my.ini

[mysqld]
# 安装目录
basedir=D:\\env\\mysql-5.7.19-winx64
# 数据存放目录
datadir=D:\\env\\data

2.创建初始化密码文件init.txt

ALTER USER 'root'@'localhost' IDENTIFIED BY 'your_pwd';

3.命令行启动

mysqld --defaults-file=D:\env\mysql-5.7.19-winx64\my.ini  --console  --explicit_defaults_for_timestamp --log_syslog=0  --init-file=D:\env\mysql-5.7.19-winx64\init.txt

unrecognized import path "google.golang.org/grpc
mkdir -p $GOPATH/src/google.golang.org/
cd $GOPATH/src/google.golang.org
git clone https://github.com/grpc/grpc-go grpc

or


replace (

 // 如果用latest  则会因为V0.40以后版本出现需要cloud.google.com/go/……  的问题,所以当前使用低版本

cloud.google.com/go => github.com/GoogleCloudPlatform/google-cloud-go v0.34.0     

golang.org/x/crypto => github.com/golang/crypto v0.0.0-20190701094942-4def268fd1a4

golang.org/x/exp => github.com/golang/exp v0.0.0-20190731235908-ec7cb31e5a56

golang.org/x/image => github.com/golang/image v0.0.0-20190802002840-cff245a6509b

golang.org/x/lint => github.com/golang/lint v0.0.0-20190409202823-959b441ac422

golang.org/x/mobile => github.com/golang/mobile v0.0.0-20190814143026-e8b3e6111d02

golang.org/x/mod => github.com/golang/mod v0.1.0

golang.org/x/net => github.com/golang/net v0.0.0-20190813141303-74dc4d7220e7

golang.org/x/oauth2 => github.com/golang/oauth2 v0.0.0-20190604053449-0f29369cfe45

golang.org/x/sync => github.com/golang/sync v0.0.0-20190423024810-112230192c58

golang.org/x/sys => github.com/golang/sys v0.0.0-20190813064441-fde4db37ae7a

golang.org/x/text => github.com/golang/text v0.3.2

golang.org/x/time => github.com/golang/time v0.0.0-20190308202827-9d24e82272b4

golang.org/x/tools => github.com/golang/tools v0.0.0-20190814235402-ea4142463bf3

golang.org/x/xerrors => github.com/golang/xerrors v0.0.0-20190717185122-a985d3407aa7

google.golang.org/api => github.com/googleapis/google-api-go-client v0.8.0

google.golang.org/appengine => github.com/golang/appengine v1.6.1

google.golang.org/genproto => github.com/ilisin/genproto v0.0.0-20181026194446-8b5d7a19e2d9

google.golang.org/grpc => github.com/grpc/grpc v1.22.0

)