《实战Java高并发程序设计》读书笔记 1~3

第一章 走入并行世界

1、你必须知道的几个概念

(1)同步(Synchronized)和异步(Asynchronous)
同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中“真实”地执行。整个过程,不会阻碍调用者的工作。
(2)并发(Concurrency)和并行(Parallelism)
并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交的,一会儿运行任务A一会儿执千对于务B,系统会不停地在两者间切换。
(3)临界区
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
(4)阻塞和非阻塞
阻塞和非阻塞通常用来形容多线程间的相互影响。
(5)死锁(Deadlock )、饥饿(Starvation)和活锁(Livelock)
死锁:多个线程之间资源互相被占用,导致全都处于等待状态。
饥饿:饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。
活锁:多个线程之间进行资源的相互“谦让”,导致资源在线程之间来回跳动。

2、并发的级别

由于临界区的存在,多线程之间的并发必须受到控制。根据控制并发的策略,我们可以把并发的级别进行分类,大致上可以分为阻塞、无饥饿、无障碍、无锁、无等待。

3、回到Java:JMM

JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。因此,我们首先必须了解这些概念。
(1)原子性(Atomicity)
原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
(2)可见性(Visibility)
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
(3)有序性(Ordering)
指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。所以在多线程中,可能会因为指令重排导致有序性被破坏,运行出现错误。指令重排的好处在于提高了CPU 的处理性能。
但是有一些指令不能重排:Happen-Before规则
在这里插入图片描述

第二章 Java并行程序基础

1、线程与进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程就是轻量级进程,是程序执行的最小单位。使用多线程而不
是用多进程去进行并发程序的设计,是因为线程间的切换和调度的成本远远小于进程。
线程的所有状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED

2、线程的基本操作

(1)新建线程

Thread t1 = new Thread();
t1.start();

那线程start()后,会干什么呢?这才是问题的关键。线程Thread,有一个run()方法,start()方法就会新建一个线程并让这个线程执行run()方法。
也可以通过继承Runnable接口来创建新线程。

(2)等待(wait)和通知(notify)
当一个对象调用wait方法以后,当前线程就会进入等待队列,并释放锁,一直等到其他线程调用了notify()为止,注意:当其他对象调用notify()时,会随机唤醒等待队列中的一个线程,这个选择是不公平的。notifyAll()可以将等待队列中的所有的线程唤醒。
(3)等待线程结束(join)和谦让(yield)
join()的调用会使得当前线程进入等待,直至加入的线程执行结束才能接着执行。join()的本质是让调用线程wait()在当前线程对象实例上。
yield()方法会让出CPU的执行权,但是还会进行CPU资源的争夺。

3、volatile与Java内存模型(JMM )

当你用volatile去申明一个变量时,就等于告诉虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。
但是volatile并不能保证操作的原子性。

4、分门别类的管理:线程组

示例:

public class Num02 implements  Runnable{
    public static void main(String[] args) {
        ThreadGroup print = new ThreadGroup("Print");
        Thread t1 = new Thread(print, new Num02(), "T1");
        Thread t2 = new Thread(print, new Num02(), "T2");

        t1.start();
        t2.start();
        System.out.println(print.activeCount());
        print.list();
    }

    @Override
    public void run() {

    }
}

上述代码建立了一个名字为print的线程组,将t1和t2加入中,个组中。其中,展示了线程组的两个重要的功能,activeCount()可以获得活动线程的总数,但由于线程是动态的,因此这个值只是一个估计值,无法确定精确,list()方法可以打印这个线程组中所有的线程信息,对调试有一定帮助。

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

守护线程:系统的守护者,当一个Java应用内,只有 守护线程时,Java虚拟机就会自然退出。

6、线程优先级

从1到10表示线程的优先级。用setPripority()来进行线程优先级的设置。

7、线程安全的概念与synchronized

关键字synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。
关键字synchronized可以有多种用法。这里做一个简单的整理。
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
除了用于线程同步、确保线程安全外,synchronized还可以保证线程间的可见性和有序性。从可见性的角度上讲,synchronized可以完全替代volatile的功能,只是使用上没有那么方便。

8、隐蔽错误

(1)并发下的ArrayList
ArrayList是线程不安全的。改进的方法可以使用Vector代替。
HashMap可以使用ConcurrentHashMap代替。
不可以将Integer这样的包装类作为锁对象,因为每次Integer改变都会创建一个新的Integer对象。

第三章 JDK并发包

1、多线程的团队协作:同步控制

(1)synchronized的功能扩展:重入锁

重入锁使用java.util.concurrent.locks. ReentrantLock类来实现。

class ReenterLock implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLock t1 = new ReenterLock();
        Thread t2 = new Thread(t1);
        Thread t3 = new Thread(t1);
        t2.start();
        t3.start();
        t2.join();
        t3.join();
        System.out.println(i);
    }
}

开发人员必须手动 的释放锁资源。
lock锁是允许重入的。
中断响应:
对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得这
把锁继续执行,要么它就保持等待。而使用重入锁,则提供另外一种可能,那就是线程可以被
中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。
锁申请等待限时:
在这里插入图片描述
方法描述
在这里,***tryLock()***方法接收两个参数,一个表示等待时长,另外一个表示计时单位。这里的单位设置为秒,时长为5,表示线程在这个锁请求中,最多等待5秒。如果超过5秒还没有得到锁,就会返回false。如果成功获得锁,则返回true。
公平锁
公平锁不会产生饥饿现象。
构造函数为:

public ReentrantLock(boolean fair)

当参数为true时,为公平锁,默认为非公平锁。
在这里插入图片描述
重入锁的实现包含以下三个要素:
(1)原子状态,使用CAS操作。
(2)等待队列。
(3)阻塞原语park和unpark。

(2) 重入锁的好搭档:Condition条件

await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似,signal()也会释放锁。
使用方法:

public static ReentrantLock lock = new ReentrantLock();
public static Condition con = lock.newCondition();
public void run() {
    for (int j = 0; j < 10000000; j++) {
        lock.lock();
        try {
            con.await();
            i++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            con.signal();
            lock.unlock();
        }
    }
}

(3)允许多个线程同时访问:信号量(Semaphore )

信号量可以指定多个线程,同时访问某一个资源。信号量提以下的构造函数:

public Semaphore(int permits)
public Semaphore(int permits,boolean fair)

在构造信号量对象时,必须要指定信号量的准入数,即同时能申请多少个许可。
主要的方法:
在这里插入图片描述
acquire()方法尝试获得一个准入的许可。若无法获得,则线程会等待,直到有线程释放一个许可或者当前线程被中断。acquireUninterruptibly()方法和acquire()方法类似,但是不响应中断。tryAcquire()尝试获得一个许可,如果成功返回hue,失败则返回false,它不会进行等待,立即返回。release()用于在线程访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问。
在这里插入图片描述
意味着可以同时有5个线程同时进入第7-9行,**务必使用release()释放信号量。**否则会发生信号泄露。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值