Code Monkey home page Code Monkey logo

concurrent's Introduction

文章地址

本地运行

先安装gitbook

npm install gitbook-cli -g

然后安装gitbook插件

gitbook install

运行服务

gitbook serve .

然后就可以在本地 http://localhost:4000访问了。

本书简介

笔者在读完市面上关于Java并发编程的资料后,感觉有些知识点不是很清晰,于是在RedSpider社区内展开了对Java并发编程原理的讨论。鉴于开源精神,我们决定将我们讨论之后的Java并发编程原理整理成书籍,分享给大家。

站在巨人的肩上,我们可以看得更远。本书内容的主要来源有博客、书籍、论文,对于一些已经叙述得很清晰的知识点我们直接引用在本书中;对于一些没有讲解清楚的知识点,我们加以画图或者编写Demo进行加工;而对于一些模棱两可的知识点,本书在查阅了大量资料的情况下,给出最合理的解释。

写本书的过程也是对自己研究和掌握的技术点进行整理的过程,希望本书能帮助读者快速掌握并发编程技术。

如果您或者您的单位愿意赞助本书或本社区,请发送邮件到RedSpider社区邮件组[email protected]或加微信redspider-worker进行洽谈。

勘误和支持

由于笔者的水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果你有更多的宝贵意见,可以在我们的github上新建issue,笔者会尽快解答,期待能够得到你的真挚反馈。github地址:https://github.com/RedSpider1/concurrent

公众号

欢迎关注微信公众号“编了个程”,每周会更新一篇Java方面的原创技术文章

公众号

concurrent's People

Contributors

ahbicj avatar air-s avatar akanemurakawa avatar braydenwong avatar chenxiao19920206 avatar chris-wing avatar chunyizanchi avatar colawoo avatar fawks96 avatar ghost-unison avatar hf-hf avatar hiwja avatar hogantry avatar initial-y avatar jasonzhangz avatar lucyvictor avatar neal2018 avatar runningbun avatar sgzman avatar technologyk avatar xhfron avatar xianliu18 avatar yasinshaw avatar zhangchengk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

concurrent's Issues

第八章 volatile 部分小错误

8.2.1 禁止重排序小节的图片

  • "禁止下面所有的操作“ 应该是”操作“

image

8.3 volatile的用途

  • 末尾段写了volatile在单例模式中的应用,原文为”比如线程A在第10行执行了步骤1和步骤3,但是步骤2还没有执行完。这个时候线程A执行到了第7行,它会判定instance不为空,然后直接返回了一个未初始化完成的instance!“,应该是”这个时候B线程执行到了第七行“,原文的意思应该是想说另外一个线程执行到了第七行,判断instance不为空,获得了一个没有初始化完成的instance

关于Timed_waiting的解释有误

第26页关于TIMED_WAITING的解释:
"这时你还是线程t1,你改bug的同事是线程t2。t2让t1等待了指定时间,t1先主动释放了锁。此时t1等待期间就属于TIMED_WAITING状态。"
正确的说法应该是当线程调用sleep(long millis),wait(long millis),join(long millis)这些方法之后会转入TIME_WATING状态,但是持有的锁并不会释放

章节"8.2.1 禁止重排序"中volatile与普通变量的重排序规则第三条疑问

原文如下:
"再介绍一下volatile与普通变量的重排序规则:

如果第一个操作是volatile读,那无论第二个操作是什么,都不能重排序;

如果第二个操作是volatile写,那无论第一个操作是什么,都不能重排序;

如果第一个操作是volatile写,第二个操作是volatile读,那不能重排序。"

原文第三条是"如果第一个操作是volatile写,第二个操作是volatile读,那不能重排序。"按照Doug Lea大师的一篇文章《The JSR-133 Cookbook for Compiler Writers》,如果第一个操作是volatile写,第二个操作是volatile读或者volatile写,那不能重排序。

"2.2.2 Future接口"章节描述疑问

2.2.2 Future接口 章节的最后一段有这样的描述"所以有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable。",这里是不是使用Future来替代Runnable?

病句

article/03/15.md 并发Queue 第一段最后一句

很难搜索的时候如何避免锁住整个list。

可改为:
很难想到搜索的时候如何避免锁住整个list。

acquire流程图的问题

acquire流程

如果没有有线程中断这一句理解不了,应该是不管是否中断都需要自旋直到获取到锁,之后再返回,根据interrupted 判断是否调用selfInterrupt方法。

4.2.2 关于线程 RUNNABLE 状态的疑惑

Java 中的注释写到:
/**

  • Thread state for a runnable thread. A thread in the runnable
  • state is executing in the Java virtual machine but it may
  • be waiting for other resources from the operating system
  • such as processor.
    */
    注释中描述的是 “也许是在等待其他操作系统的资源” 。
    您在书中的描述是 “Java线程的RUNNABLE状态其实是包括了传统操作系统线程的 ready 和 running 两个状态的。"

在等待系统资源的时候,为何不是处于 waiting 状态?那这里应该是 waiting 和 running状态吗? 这是我的一个疑惑点,希望您可以帮助理解一下。

打错字了

1.1 进程产生的背景

  • 对操作系统的要求进一步提高
    的上一段中的黑体字

进程让_操作体统_的并发成为了可能

章节"11.4.1 获取资源"最后总结的流程图疑问

image

原图中acquire方法调用的tryAquire()方法,如果结果为true,自己中断线程;而查看AQS源码

public final void acquire(int arg) {
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
	selfInterrupt();
}

可以看到如果tryAcquire()结果为true,那么!tryAcquire()的结果为false,不会执行 selfInterrupt();而是执行获取到锁后的代码

第15章,图缺失

第15章,图缺失:

有些方法需要跨段,比如size()、isEmpty()、containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。如下图:

第7页,有些内容读不通

总之,进程和线程的提出极⼤的提⾼了操作提供的性能。进程让操作系统的并发性 成为了可能,⽽线程让进程的内部并发成为了可能。

"操作提供"的性能应该是"操作系统"的性能?

部分文字错误

当前仅发现:article/02/9.md

develop分支。

  1. 154行
    所以,如果应用程序里所有的锁通常【出于】(处于)竞争状态,

  2. 209行
    【果】(如果|若)线程获得锁后调用Object.wait方法,

  3. 235行
    | 追求吞吐量。同步块执行【速度】(时间)较长。 |

用例代码问题

2.1.2 实现Runnable接口
Runnable的用例:
new MyThread().start();这样不对吧
是不是应该new Thread(new MyThread()).start();

类名拼写错误

第十三章 最后一段“无论是使用Exectors类中已经提供的线程池“中Exectors应该是Executors

章节5.3中示例代码有线程安全的问题

章节5.3中用volatile关键字来说明信号量的实例代码有线程安全的问题:

public class Signal {
    private static volatile int signal = 0;

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 0) {
                    System.out.println("threadA: " + signal);
                    synchronized (this) {
                        signal++;
                    }
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    synchronized (this) {
                        signal = signal + 1;
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}

// 输出:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
threadA: 4

下面这三行的代码整体需要保证原子性“ while (signal < 5) 、if (signal % 2 == 0) 、signal++;”,否则会有线程安全的问题。
假设此时signal=4,ThreadA和ThreadB都刚执行完while (signal < 5)这一行,进入while的循环体内。此时ThreadB没有分到CPU时间片,ThreadA继续执行,跑完接下来的逻辑后,signal变成了5,此时ThreadB才刚开始从“ if (signal % 2 == 1) ”这一行继续往下执行,最终signal=6
因为示例代码中,signal必须要小于5,所以运行实例代码时,难以出现线程安全的场景,但是如果将signal的范围从signal<5,改成signal < 50,运行多次实例代码,会出现下面两种运行结果:
结果1:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
······
······
threadB: 47
threadA: 48
threadB: 49

结果2:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
······
······
threadB: 47
threadA: 48
threadB: 49
threadA: 50

当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

9.2.4章节中,有下面一段话:
如果线程获得锁后调用Object.wait方法,则会将线程加入到WaitSet中,当被Object.notify唤醒后,会将线程从WaitSet移动到Contention List或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify方法时,如当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁
对于这段话中加粗斜体的这句话,只讲了现象,具体原因请问是什么呢?

2.2.1Callable接⼝例程没有抛出异常

public static void main(String args[]){
// 使⽤
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
?uture result = executor.submit(task);
// 注意调⽤get⽅法会阻塞当前线程,直到得到结果。
// 所以实际编码中建议使⽤可以设置超时时间的重载get⽅法。
System.out.println(result.get());
}

其中,result.get()需要try,或者main方法抛出

文章修改提议

1.文字错误。原文:虽然线程A在临界区做了重排序,但是因为监视器锁的特性,线程B无法观察到线程A在临界区的重排序。这种重排序既提高了执行效率,【有】没有改变程序的执行结果。应该是【又】

不同锁保护同一个资源的问题

article/01/5.md
5.3-信号量

public class Signal {
    private static volatile int signal = 0;

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 0) {
                    System.out.println("threadA: " + signal);
                    synchronized (this) {
                        signal++;
                    }
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    synchronized (this) {
                        signal = signal + 1;
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}

// 输出:
threadA: 0
threadB: 1
threadA: 2
threadB: 3
threadA: 4

我们可以看到,使用了一个volatile变量signal来实现了“信号量”的模型。这里需要注意的是,volatile变量需要进行原子操作。signal++并不是一个原子操作,所以我们需要使用synchronized给它“上锁”。


这两个地方对 this 进行 synchronized 好像不对吧,两个 this 不是同一个锁呢,可以改成 synchronized(Signal.class)

1.1进程产生的背景错别字

后来有了批处理操作体统,把一系列需要操作的指令写下来,形成一个清单,一次性交给计算机。用户将多个需要执行的程序写在磁带上,然后交由计算机去读取并逐个执行这些程序,并将输出结果写在另一个磁带上。 --> "操作体统"应该为"操作系统"

总之,进程和线程的提出极大的提高了操作提供的性能。进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能。 --> “操作提供”应该为“操作系统”

11.1 AQS简介——错别字

原文:当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器,只要之类实现它的几个protected方法就可以了,在下文会有详细的介绍。

错误:...只要之类实现....,应为子类而不是之类

正文第十章的标题是否错了?

从目录上看第十章的标题应该是:CAS与原子操作,但正文的第十章标题是:乐观锁和悲观锁,请问是不是正文的标题错了?

【必看】提交PR必看

感谢大家帮忙完善这本书,有任何问题可以提Issue
如果是错别字等简单的问题,更欢迎提交Pull Request哟。
提交PR请注明是在第几章,方便我们快速定位。
提交PR前请查看github版本是否已经修改过这个问题。PDF和网页版本可能会有一点更新滞后。
谢谢~

17.1.2 Semaphore.release()是否应该在finally中被调用

17.1.2中Semaphore的示例代码中,Semaphore释放资源自用的release方法是不是放在finally中调用更好?实例中的代码,万一抛出异常的话,Semaphore的资源就一直释放不了了,有内存泄露的风险?
`public class SemaphoreDemo {
static class MyThread implements Runnable {

    private int value;
    private Semaphore semaphore;

    public MyThread(int value, Semaphore semaphore) {
        this.value = value;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire(); // 获取permit
            System.out.println(String.format("当前线程是%d, 还剩%d个资源,还有%d个线程在等待",
                    value, semaphore.availablePermits(), semaphore.getQueueLength()));
            // 睡眠随机时间,打乱释放顺序
            Random random =new Random();
            Thread.sleep(random.nextInt(1000));
            semaphore.release(); // 释放permit
            System.out.println(String.format("线程%d释放了资源", value));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(3);
    for (int i = 0; i < 10; i++) {
        new Thread(new MyThread(i, semaphore)).start();
    }
}

}`

章节 ”13.3.4 PriorityBlockingQueue“ 内容有误

以下是 章节 13.3.4中对PriorityBlockingQueue的描述

“基于优先级的无界阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),内部控制线程同步的锁采用的是公平锁。”

上述描述中加粗字体说反了,实际上PriorityBlockingQueue中使用的锁都是非公平锁

    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

第二篇 7 重排序与happens-before

原文:
重排序有两类,JMM对这两类重排序有不同的策略:
会改变程序执行结果的重排序,比如 A -> C,JMM要求编译器和处理器都不许禁止这种重排序。
不会改变程序执行结果的重排序,比如 A -> B,JMM对编译器和处理器不做要求,允许这种重排序。

JMM要求编译器和处理器都不许禁止这种重排序
是否应改为:JMM要求编译器和处理器禁止这种重排序

2.2.3FutureTask类例程,没有抛出异常

// 自定义Callable,与上面一样
class Task implements Callable{
@OverRide
public Integer call() throws Exception {
// 模拟计算需要一秒
Thread.sleep(1000);
return 2;
}
public static void main(String args[]){
// 使用
ExecutorService executor = Executors.newCachedThreadPool();
FutureTask futureTask = new FutureTask<>(new Task());
executor.submit(futureTask);
System.out.println(futureTask.get());
}
}

futureTask.get()需要抛出异常

关于ConcurrentHashMap类的解释有点混乱

第三篇:JDK工具篇 - 15 并发集合容器简介
书里说ConcurrentHashMap采用分段锁机制。
“ConcurrentHashMap提供了一种粒度更细的加锁机制来实现在多线程下更高的性能,这种机制叫分段锁(Lock Striping)。”
同时又说HashEntry会在长度达到8的时候转化为红黑树。
“一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构(同HashMap一样,它也会在长度达到8的时候转化为红黑树)的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。”
那么问题来了:
ConcurrentHashMap在JDK1.8的时候取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
而文中描述ConcurrentHashMap时,同时使用了分段锁和红黑树这两个概念,将两个版本的特性混合在了一起(JDK1.7、JDK1.8)
个人感觉此处应该将JDK1.7和JDK1.8的不同特性分开标注说明。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.