美文网首页
高并发一

高并发一

作者: lifesmily | 来源:发表于2018-08-23 22:42 被阅读15次

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();
    }
}

相关文章

网友评论

      本文标题:高并发一

      本文链接:https://www.haomeiwen.com/subject/vwenjftx.html