高并发一

1、多线程的实现

多线程的实现方案一:继承Thread类,重写run()方法
java是单继承,也就是说继承本身是很宝贵。
多线程的实现方案二:实现Runnable接口
多线程程序实现方案三:实现Callable接口

2、synchronized关键字

https://segmentfault.com/a/1190000003810166

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadTest implements Runnable{
    public static Integer num = 0;
   @Override
    public synchronized void run(){
       for(int i = 0;i<100;i++)
           System.out.println("hello" + num++);
   }
   public static void main(String[] args) throws{
       ExecutorService pool = Executors.newFixedThreadPool(2);
       pool.submit(new ThreadTest());
       pool.submit(new ThreadTest());
       pool.shutdown();
   }
}

上面使用了同步方法,可以控制线程独占执行体对象,这样在执行的过程中就可以使得线程将执行体上的任务一次性执行完后退出锁定状态,JVM再调度另一个线程进来一次性运行执行体内的任务。实际执行时可以发现,没有synchronized关键字运行出来的数据会少很多。

3、yield关键字

https://blog.csdn.net/dabing69221/article/details/17426953
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
程序示例:

public class ThreadTest extends Thread{
    public static Integer num = 0;
    @Override
    public void run(){
       for(int i = 0;i<50;i++) {
           System.out.println("hello" + i);
           if(i==30){
               this.yield();
           }
       }
   }
   public static void main(String[] args) {
       ThreadTest t1 = new ThreadTest();
       ThreadTest t2 = new ThreadTest();
       t1.start();
       t2.start();
   }
}

需要注意的是yield是 Thread的方法

4、wait()和notify()方法

https://www.jianshu.com/p/f4454164c017
https://www.jianshu.com/p/f7d4819b7b24
下面是一个简单的示例程序,说明了wait和notify的基本用法,

package com.company;

import java.util.concurrent.TimeUnit;

public class WaitNotifyCase {
    public static void main(String[] args){
        final Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread a is waiting to get lock");
                synchronized (lock){
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        lock.wait();
                        System.out.println("wait end");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread b is waiting to get lock");
                synchronized (lock){
                    try {
                        System.out.println("thread b get lock");
                        TimeUnit.SECONDS.sleep(5);
                        System.out.println("wait end");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    lock.notify();
                    System.out.println("notify method");
                }
            }
        }).start();

    }
}

注意的是需要调用同一个对象的wait和notify,线程执行到wait时会被挂起,直到notify通知继续运行。
上面两个线程都有synchronized关键字,为什么需要?还得从wait和notify本质出发,线程执行lock.wait()方法时,必须持有该lock对象的monitor,如果wait方法在synchronized代码中执行,该线程很显然已经持有了monitor。在执行wait时需要获得monitor,但是执行完之后就会释放,进入等待状态。从这个角度看,这也是为什么这边synchronized 代码没有执行完而那边却可以进入notify函数,notify在synchronized代码中执行,先获取到对象monitor,然后通知,但wait并不会立刻就执行,因为还需要获得monitor,所以只有synchronized 代码块执行完毕之后wait才会继续执行。
使用wait、notify有一个需要特别注意,那就是不能让notify先执行,这样wait就会一直阻塞。需要严格控制两者执行顺序。

5、错误加锁

public class ThreadTest implements Runnable{
   public static Integer i = 0;
   static ThreadTest instace = new ThreadTest();
   @Override
    public void run(){
       for(int j=0;j<100000;j++){
           synchronized (i) { i++;}
       }
   }
   public static void main(String[] args) throws InterruptedException{
       Thread t1 = new Thread(instace);
       Thread t2 = new Thread(instace);
       t1.start();
       t2.start();
       t1.join();
       t2.join();
       System.out.println(i);
   }
}

上面程序看起来没有问题,但是打印出来的i确不是200000,得到了一个小很多的数字。涉及到一个自动装包和拆包的问题。
执行i++时,实际上是 i = Integer.valueOf(i.intValue()+1)
Integer.valueOf()实际上是一个工厂方法,会倾向于返回一个代表指定数值的Integer实例,创建一个新的对象(大于127),因此两个线程每次加锁可能加在不同的对象实例上。

6、volatile关键字

http://www.infoq.com/cn/articles/java-multi-thread-volatile
http://www.techug.com/post/java-volatile-keyword.html
被volatile修饰的共享变量,就具有了以下两点特性:

  1. 保证了不同线程对该变量操作的内存可见性;
  2. 禁止指令重排序

7、ReentrantLock

参考:https://my.oschina.net/hosee/blog/607677
ReentrantLock可以看作是synchronized的加强版,之前版本,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使用ReentrantLock。
相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。
简单示例:

import java.util.concurrent.locks.ReentrantLock;

public class Test 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
    {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }

}

ReentrantLock是重入锁,可以反复得到相同的一把锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。如下:

lock.lock();
lock.lock();
try
{
    i++;
            
}           
finally
{
    lock.unlock();
    lock.unlock();
}

如果只是简单的互斥锁,上面这种情况就会发生死锁,因为锁只允许依次进入。
同样,synchronize也是可重入锁。

public class Child extends Father implements Runnable{
    final static Child child = new Child();//为了保证锁唯一
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(child).start();
        }
    }
 
    public synchronized void doSomething() {
        System.out.println("1child.doSomething()");
        doAnotherThing(); // 调用自己类中其他的synchronized方法
    }
 
    private synchronized void doAnotherThing() {
        super.doSomething(); // 调用父类的synchronized方法
        System.out.println("3child.doAnotherThing()");
    }
 
    @Override
    public void run() {
        child.doSomething();
    }
}
class Father {
    public synchronized void doSomething() {
        System.out.println("2father.doSomething()");
    }
}

可以看到一个线程进入不同的 synchronized方法,是不会释放之前得到的锁的。
ReentrantLock还支持可中断,定时和公平方式,功能比较丰富。

8、条件变量

package com.company;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest implements Runnable{
   public static ReentrantLock lock = new ReentrantLock();
   public static Condition condition = lock.newCondition();

   @Override
    public void run(){
       try{
           lock.lock();
           System.out.println("locked");
           condition.await();
           System.out.println("thread is going");
       }catch (InterruptedException e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }
   public static void main(String[] args) throws InterruptedException{
       ThreadTest t1 = new ThreadTest();
       Thread th = new Thread(t1);
       th.start();
       Thread.sleep(2000);
       System.out.println("after sleep");
       lock.lock();
       condition.signal();
       Thread.sleep(1000);
       System.out.println("before unlock");
       lock.unlock();
   }
}

9、信号量

对于锁来说,它是互斥的排他的。意思就是,只要我获得了锁,没人能再获得了。
而对于Semaphore来说,它允许多个线程同时进入临界区。可以认为它是一个共享锁,但是共享的额度是有限制的,额度用完了,其他没有拿到额度的线程还是要阻塞在临界区外。当额度为1时,就相等于lock。

package com.company;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class semaphoreTest implements Runnable{
    final Semaphore semp = new Semaphore(5);
    @Override
    public void run(){
        try{
            semp.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId()+":done");
            semp.release();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void main(String[] args){
        ExecutorService exec = Executors.newFixedThreadPool(20);
        final semaphoreTest demo = new semaphoreTest();
        for(int i=0;i<20;i++){
            exec.submit(demo);
        }
    }
}

11、读写锁

ReadWriteLock是区分功能的锁。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。适用于读频繁的场景。
这样的设计是并发量提高了,又保证了数据安全。

private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(); 
private static Lock readLock = readWriteLock.readLock(); 
private static Lock writeLock = readWriteLock.writeLock();

12、CountDownLatch和CyclicBarrier

倒数计数器CountDownLatch

表示执行某任务前,其他指定任务必须完成。典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程,等待所有检查线程全部完工后,再执行。

static final CountDownLatch end = new CountDownLatch(10);
end.countDown(); 
end.await();

示例

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test implements Runnable
{
    static final CountDownLatch countDownLatch = new CountDownLatch(10);
    static final Test t = new Test();
    @Override
    public void run()
    {
        try
        {
            Thread.sleep(2000);
            System.out.println("complete");
            countDownLatch.countDown();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws InterruptedException
    {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++)
        {
            executorService.execute(t);
        }
        countDownLatch.await();
        System.out.println("end");
        executorService.shutdown();
    }

}
CyclicBarrier

https://www.jianshu.com/p/424374d71b67
和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程

public CyclicBarrier(int parties, Runnable barrierAction) 
barrierAction就是当计数器一次计数完成后,系统会执行的动作
await()
import java.util.concurrent.CyclicBarrier;

public class Test implements Runnable
{
    private String soldier;
    private final CyclicBarrier cyclic;

    public Test(String soldier, CyclicBarrier cyclic)
    {
        this.soldier = soldier;
        this.cyclic = cyclic;
    }

    @Override
    public void run()
    {
        try
        {
            //等待所有士兵到齐
            cyclic.await();
            dowork();
            //等待所有士兵完成工作
            cyclic.await();
        }
        catch (Exception e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    private void dowork()
    {
        // TODO Auto-generated method stub
        try
        {
            Thread.sleep(3000);
        }
        catch (Exception e)
        {
            // TODO: handle exception
        }
        System.out.println(soldier + ": done");
    }

    public static class BarrierRun implements Runnable
    {

        boolean flag;
        int n;

        public BarrierRun(boolean flag, int n)
        {
            super();
            this.flag = flag;
            this.n = n;
        }

        @Override
        public void run()
        {
            if (flag)
            {
                System.out.println(n + "个任务完成");
            }
            else
            {
                System.out.println(n + "个集合完成");
                flag = true;
            }

        }

    }

    public static void main(String[] args)
    {
        final int n = 10;
        Thread[] threads = new Thread[n];
        boolean flag = false;
        CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
        System.out.println("集合");
        for (int i = 0; i < n; i++)
        {
            System.out.println(i + "报道");
            threads[i] = new Thread(new Test("士兵" + i, barrier));
            threads[i].start();
        }
    }

}

上面每个线程有两个await,线程遇到await会阻塞,当指定数量的线程就绪,所有线程继续运行。同时会触发 CyclicBarrier(n, new BarrierRun(flag, n))中第二个参数指定的线程。
CyclicBarrier比CountDownLatch更复杂,功能更强大。

13、LockSupport

LockSupport 提供线程阻塞原语
用法如下:

LockSupport.park(); 
LockSupport.unpark(t1);

回顾Thread中suspend,resume,stop方法,
suspend,使线程暂停,但是不会释放类似锁这样的资源。
resume,使线程恢复,如果之前没有使用suspend暂停线程,则不起作用。
stop,停止当前线程。不会保证释放当前线程占有的资源。
suspend和resume也能提供暂停和继续的,但是如果resume发生在suspend之前就会发生暂停线程得不到继续,而这种情况在多线程环境下很容易发生。
但LockSupport下的park和unpark就不会发生这样的情况。

import java.util.concurrent.locks.LockSupport;
 
public class Test
{
    static Object u = new Object();
    static TestSuspendThread t1 = new TestSuspendThread("t1");
    static TestSuspendThread t2 = new TestSuspendThread("t2");
 
    public static class TestSuspendThread extends Thread
    {
        public TestSuspendThread(String name)
        {
            setName(name);
        }
 
        @Override
        public void run()
        {
            synchronized (u)
            {
                System.out.println("in " + getName());
                //Thread.currentThread().suspend();
                LockSupport.park();
            }
        }
    }
 
    public static void main(String[] args) throws InterruptedException
    {
        t1.start();
        Thread.sleep(100);
        t2.start();
//        t1.resume();
//        t2.resume();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容