并发编程

java 锁

synchronized 修饰 实例方法

/**
* synchronized 修饰实例方法
*/
public synchronized void increase(){
i++;
}

这里我们还需要意识到,当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法,当然如果是一个线程 A 需要访问实例对象 obj1 的 synchronized 方法 f1(当前对象锁是obj1),另一个线程 B 需要访问实例对象 obj2 的 synchronized 方法 f2(当前对象锁是obj2),这样是允许的,因为两个实例对象锁并不同相同,此时如果两个线程操作数据并非共享的,线程安全是有保障的,遗憾的是如果两个线程操作的是共享数据,那么线程安全就有可能无法保证了,

synchronized 修饰 静态方法

/**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i++;
    }

synchronized 修饰 同步代码块

 @Override
    public void run() {
        //省略其他耗时操作....
        //使用同步代码块对变量i进行同步操作,锁对象为instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    

https://fangjian0423.github.io/2016/03/12/java-Object-method/
Object notify, notifyAll, wait
Object 关于线程 同步锁的这几个方法, 应该是在 api内部使用的, 一般外部写代码根本用不到吧

wait, notify, notifyAll 和锁有关,, 主要是获取锁 和 释放锁

而锁就是个 并发 相关的概念

wait方法会让当前线程等待直到另外一个线程调用对象的notify或notifyAll方法,或者超过参数设置的timeout超时时间。

wait是不是 去放弃 cpu的争夺权 , cpu调度器 在分配 cpu资源使用权的时候, 不会考虑这个线程了

一般情况下,wait方法和notify方法会一起使用的,wait方法阻塞当前线程,notify方法唤醒当前线程,一个使用wait和notify方法的生产者消费者例子代码如下:

多个线程公用一把锁

同一个对象, 只有一把锁, 并且 一把锁在同一时间内只能服务于一个线程

wait阻塞线程, 放弃锁对象,,,

notifyAll, 唤醒用同一把锁 被wait阻塞的线程,, 重新加入 cpu的争夺战

比如这个博客的 生产者和消费者demo
https://fangjian0423.github.io/2016/03/12/java-Object-method/

Concurrency 并发

Runnbale比Thread常用的原因

Java 中实现多线程有两种方法:继承 Thread 类、实现 Runnable 接口,在程序开发中只要是多线程,肯定永远以实现 Runnable 接口为主,因为实现 Runnable 接口相比继承 Thread 类有如下优势:
可以避免由于 Java 的单继承特性而带来的局限;(多了个继承父类的机会)

继承Thread

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
    }
}

实现Runnable
public class MyRunnable implements Runnable {
    @Override
    public void run() {

    }
}

增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;

适合多个相同程序代码的线程区处理同一资源的情况。

这里补充下 yield 和 join 方法的使用。

线程已经已经已经 夺取了CPU执行权
联合
join 方法用线程对象调用,如果在一个线程 A 中调用另一个线程 B 的 join 方法,线程 A 将会等待线程 B 执行完毕后再执行。 - 线程谦让
// 联合其他线程, 把让出的cpu执行权, 交给指定的线程,, 在线程里面获取指定线程的同步结果
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

线程已经已经已经 夺取CPU执行权
退让
yield 可以直接用 Thread 类调用,yield 让出 CPU 执行权给同等级的线程,如果没有相同级别的线程在等待 CPU 的执行权,则该线程继续执行。 -- 线程谦让
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
向调度程序提示当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。

在run方法里调的, 例如
// 让出CPU的执行权,, cpu调度器可以切换到其他线程,, 也可以选择不切换,, 所以这个方法就是个 hint, 这个方法不可控, 用不到的,,
// System.out.println(Thread.currentThread().getName());
Thread.currentThread().yield();

线程睡觉

// 线程睡着的时候, 有没有机会被抢夺?
// 休眠的话线程会被别的线程给抢走, 等到休眠结束了, 重新加入到cpu执行权的争夺战中
Thread.sleep(2000);

Java 中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

java concurrency

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases.

https://docs.oracle.com/javase/tutorial/essential/concurrency/procthread.html

写这篇博客的时候又刚好想起了当时自己实习的时候遇到的一个问题。1000个爬虫任务使用了多线程的处理方式,比如开5个线程处理这1000个任务,每个线程分200个任务,然后各个线程处理那200个爬虫任务→_→,太笨了。其实更合理的方法是使用阻塞队列+线程池的方法。

  • 执行Callable线程,Callable线程和Runnable线程的区别就是Callable的线程会有返回值,这个返回值是Future,未来的意思,而且这Future是个接口,提供了几个实用的方法,比如cancel, idDone, isCancelled, get等方法。
@Test
public void test02() throws Exception {
    ExecutorService es = Executors.newFixedThreadPool(2, new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build());
    final LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<String>();
    for(int i = 1; i <= 500; i ++) {
        deque.add(i + "");
    }
    Future<String> result = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            while (!deque.isEmpty()) {
                System.out.println(deque.poll() + "-" + Thread.currentThread().getName());
            }
            return "done";
        }
    });
    System.out.println(result.isDone());
    // get方法会阻塞
    System.out.println(result.get());
    System.out.println("exec next");
}
  • Future的cancel方法的使用
@Test
public void test03() throws Exception {
    ExecutorService es = Executors.newFixedThreadPool(2, new ThreadFactoryBuilder().setNameFormat("thread-call-runner-%d").build());
    final LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<String>();
    for(int i = 1; i <= 5000; i ++) {
        deque.add(i + "");
    }
    Future<String> result = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            while (!deque.isEmpty() && !Thread.currentThread().isInterrupted()) {
                System.out.println(deque.poll() + "-" + Thread.currentThread().getName());
            }
            return "done";
        }
    });

    try {
        System.out.println(result.get(10, TimeUnit.MILLISECONDS));
    } catch (TimeoutException e) {
        System.out.println("cancel result: " + result.cancel(true));
        System.out.println("is cancelled: " + result.isCancelled());
    }
    Thread.sleep(2000l);
}

什么是 阻塞队列?
blockingQueue
内部加锁,实现线程安全
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/BlockingQueue.html
https://blog.csdn.net/chenchaofuck1/article/details/51660119

LinkedBlockingQueue
https://developer.android.com/reference/java/util/concurrent/LinkedBlockingQueue
实现队列 FIFO

线程池的处理流程是这样的:

首先判断线程池的基本大小,如果基本大小还没满,那么直接创建新的线程执行任务,否则进行下一步

判断线程池中的阻塞队列是是否已满,没满的话存到阻塞队列里等待执行,否则执行下一步(所以如果是个无界的阻塞队列,那么这一步永远都成立) - 涉及到队列阻塞

判断线程池最大大小是否已满,没满的话直接创建线程执行任务,否则交给饱和策略处理

锁类型

Java编程语言内置了两种线程同步:

  • 互斥同步
  • 条件同步
    在互斥同步中,在一个时间点只允许一个线程访问代码段。
    条件同步通过条件变量和三个操作来实现:等待,信号和广播。

http://www.cnblogs.com/paddix/p/5374810.html

对于这种组合操作,要保证原子性,最常见的方式是加锁,如Java中的Synchronized或Lock都可以实现,代码段二就是通过Synchronized实现的。除了锁以外,还有一种方式就是CAS(Compare And Swap),即修改数据之前先比较与之前读取到的值是否一致,如果一致,则进行修改,如果不一致则重新执行,这也是乐观锁的实现原理。不过CAS在某些场景下不一定有效,比如另一线程先修改了某个值,然后再改回原来值,这种情况下,CAS是无法判断的。

  • 可见行
    从这个图中我们可以看出,每个线程都有一个自己的工作内存(相当于CPU高级缓冲区,这么做的目的还是在于进一步缩小存储系统与CPU之间速度的差异,提高性能),对于共享变量,线程每次读取的是工作内存中共享变量的副本,写入的时候也直接修改工作内存中副本的值,然后在某个时间点上再将工作内存与主内存中的值进行同步。这样导致的问题是,如果线程1对某个变量进行了修改,线程2却有可能看不到线程1对共享变量所做的修改。

Java 中可通过Synchronized或Volatile来保证可见性,具体细节会在后续的文章中分析。

  • 有序性, 指令重排、

为了提高性能,编译器和处理器可能会对指令做重排序。重排序可以分为三种:

(1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

(2)指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  (3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

Java 中也可通过Synchronized或Volatile来保证顺序性。

synchronized方法

(1)修饰普通方法

(2)修饰静态方法

(3)修饰代码块

执行结果如下,对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。

  • volatile的作用

《Java并发编程:核心理论》一文中,我们已经提到过可见性、有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果对Synchronized原理有了解的话,应该知道Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,所以,如果有其他解决方案,我们通常都避免使用Synchronized来解决问题。而volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。对于原子性,需要强调一点,也是大家容易误解的一点:对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。

1、防止重排序

我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。其源码如下:

package com.paddx.test.concurrent;

public class Singleton {
    public static volatile Singleton singleton;

    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}


现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

(1)分配内存空间。

(2)初始化对象。

(3)将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

(1)分配内存空间。

(2)将内存空间的地址赋值给对应的引用。

(3)初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。

为啥要加双重非空判断呢?
https://blog.csdn.net/sunzhaojie613/article/details/74998902
第一个注意点:使用私有的构造函数,确保正常情况下该类不能被外部初始化(非正常情况比如通过反射初始化,一般使用反射之后单例模式也就失去效果了)。
第二个注意点:getInstance方法中第一个判空条件,逻辑上是可以去除的,去除之后并不影响单例的正确性,但是去除之后效率低。因为去掉之后,不管instance是否已经初始化,都会进行synchronized操作,而synchronized是一个重操作消耗性能。加上之后,如果已经初始化直接返回结果,不会进行synchronized操作。
第三个注意点:加上synchronized是为了防止多个线程同时调用getInstance方法时,各初始化instance一遍的并发问题。
第四个注意点:getInstance方法中的第二个判空条件是不可以去除,如果去除了,并且刚好有两个线程a和b都通过了第一个判空条件。此时假设a先获得锁,进入synchronized的代码块,初始化instance,a释放锁。接着b获得锁,进入synchronized的代码块,也直接初始化instance,instance被初始化多遍不符合单例模式的要求~。加上第二个判空条件之后,b获得锁进入synchronized的代码块,此时instance不为空,不执行初始化操作。
第五个注意点:instance的声明有一个voliate关键字,如果不用该关键字,有可能会出现异常。因为instance = new Test();并不是一个原子操作,会被编译成三条指令,如下所示。
1.给Test的实例分配内存 2.初始化Test的构造器 3.将instance对象指向分配的内存空间(注意 此时instance就不为空)
然后咧,java会指令重排序,JVM根据处理器的特性,充分利用多级缓存,多核等进行适当的指令重排序,使程序在保证业务运行的同时,充分利用CPU的执行特点,最大的发挥机器的性能!简单来说就是jvm执行上面三条指令的时候,不一定是1-2-3这样执行,有可能是1-3-2这样执行。如果jvm是按照1-3-2来执行的话,当1-3执行完2还没执行的时候,如果另外一个线程调用getInstance(),因为3执行了此时instance不为空,直接返回instance。问题是2还没执行,此时instance相当于什么都没有,肯定是有问题的。然后咧,voliate有一个特性就是禁止指令重排序,上面的三条指令是按照1-2-3执行的,这样就没有问题了。

java实现同步有哪几种方式?

大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的ThreadLocal。
http://fangjian0423.github.io/2014/11/22/java_threadlocal/

ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

http://fangjian0423.github.io/2016/04/18/java-synchronize-way/

连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。这项技术能明显提高对数据库操作的性能。

一般来说,Java应用程序访问数据库的过程是:
①装载数据库驱动程序;
②通过jdbc建立数据库连接;
③访问数据库,执行sql语句;
④断开数据库连接。

Java线程状态分析

http://fangjian0423.github.io/2016/06/04/java-thread-state/

Java线程的生命周期中,存在几种状态。在Thread类里有一个枚举类型State,定义了线程的几种状态,分别有:

  1. NEW: 线程创建之后,但是还没有启动(not yet started)。这时候它的状态就是NEW
  2. RUNNABLE: 正在Java虚拟机下跑任务的线程的状态。在RUNNABLE状态下的线程可能会处于等待状态, 因为它正在等待一些系统资源的释放,比如IO
  3. BLOCKED: 阻塞状态,等待锁的释放,比如线程A进入了一个synchronized方法,线程B也想进入这个方法,但是这个方法的锁已经被线程A获取了,这个时候线程B就处于BLOCKED状态
  4. WAITING: 等待状态,处于等待状态的线程是由于执行了3个方法中的任意方法。 1. Object的wait方法,并且没有使用timeout参数; 2. Thread的join方法,没有使用timeout参数 3. LockSupport的park方法。 处于waiting状态的线程会等待另外一个线程处理特殊的行为。 再举个例子,如果一个线程调用了一个对象的wait方法,那么这个线程就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会解除这个状态
  5. TIMED_WAITING: 有等待时间的等待状态,比如调用了以下几个方法中的任意方法,并且指定了等待时间,线程就会处于这个状态。 1. Thread.sleep方法 2. Object的wait方法,带有时间 3. Thread.join方法,带有时间 4. LockSupport的parkNanos方法,带有时间 5. LockSupport的parkUntil方法,带有时间
  6. TERMINATED: 线程中止的状态,这个线程已经完整地执行了它的任务

线程优先级 thread priority

https://www.w3cschool.cn/java/java-thread-priority.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容