并发控制学习记录

线程这一块是前段时间面试过程中遇到很多的问题,学校里面接触有限,吃了一些亏,抽空最近在看并发编程编程的挑战这本书并作一些简单记录。

第一章 简述

 并发的目的是为了让程序更快的执行,但是有一点必须明确的是,并不是多线程就一定比单线程运行的快!我记得这个问题是当初面试群硕时面试官提的问题,逐渐向后挖掘这个问题并不局限于软件问题了。这里注意即使时单核CPU也是支持多线程的,通过给各个线程分配时间片完成,所以就涉及到每个线程切换之前需要记录该线程的任务状态(为了下次切回这个线程继续执行),这样的任务从保存到再加载就是一个上下文的切换过程,这是需要时间的。所以多线的运行效率受限于上下文的切换(切换次数和时长,可以用Lmbench和vmstat查看)、死锁、硬、软件资源等多种问题的影响。

第二章 并发实现原理

2.1 几个关键字

1.volatile
 可以理解为轻量级锁,保证该关键字修饰的变量在内存中的改变对于其他线程而言是即时可见的。使用它修饰,Java线程内存模型可以确保所有线程看到这个变量的一致性。

2.synchronized
 相对于volatile而言可以理解为重量级的锁,主要用于同步方法和同步代码块。

2.2.CAS

1.CAS简介
 CAS是Compare And Swap的简写,即比较交换,对应于Java中的compareAndSet(expect,update)方法,返回一个boolean值,底层调用compareAndSwapInt(this, valueOffset, expect, update)方法,第一个参数表示变量的内存位置,它完成的作用是在set时先比较expect值和valueOffset内存中的值是否相等,如果相等就将valueOffset内存中的值更新为值update,否则不执行任何操作,从而保证了原子性操作,java.util.concurrent.atomic包中的很大一部分的类都是使用了CAS操作,可以翻一下源码,比如AtomicInteger类的getAndUpdate方法底层的实现代码如下
这里写图片描述
2.CAS存在的问题
 CAS虽然解决了原子性操作问题,但是CAS仍然存在以下三个问题:

  1. ABA问题
     循环时间长,开销大,只能保证一个共享变量的原子操作。CAS操作时,会首先检查该内存上的值有没有发生变化,如果没有变化则更新,否则不更新,但是值的更新存在一个问题:比如原来的值为A,一个人改成了B,然后一混蛋偷偷又改成了A,这时候CAS在检查时发现该内存中值没有改变,这就混淆视听了,要防止这种问题的发生,可以在变量前面追加版本号,每次更新版本号加1,那么上述ABA的流程就变成了1A–>2B–>3A。当然,JDK1.5以后提供了AtomicStampedReference类解决ABA问题,它的compareAndSet方法可以首先检查当前引用是否等于预期标志,只有全部相等才会以院子方式蒋该引用和该标志的值设置为给定的更新值。源码如下:
    这里写图片描述

  2. 循环时间长开销大
     自旋CAS如果长时间不成功,将会给CPU带来非常大的执行开销。

  3. 只能保证一个共享变量的原子操作
     通过循环CAS的方法保证原子操作,但对于多个共享变量将无法保证它们的原子操作,只能加锁,当然还可以将多个共享变量合并为一个共享变量来操作,比如两个共享变量a=1,b=2,那么可合并为ab=12,然后直接对ab进行CAS操作,由于1.5以后有了AtomicStampedReference,可以将多个变量放到一个对象里进行操作。

第三章 Java中的锁

1. Lock接口

 在多线程中,通常的理解是利用锁来限制同一时刻只有一个线程可以对共享资源进行操作,但是有些锁是允许多线程对多线程同时访问共享资源的(仅仅是访问,也就是读操作,也能理解,同时读也没啥毛病),比如提的比较多的读写锁。在jdk1.5以后提供了除synchronized关键字以外的Lock接口来实现锁,这个接口提供了与synchronized类似的功能,但是它需要显示的获取锁和释放锁,虽然没有像同步方法或者同步代码块那样隐式的获取锁、释放锁方便,但是拥有获取、释放锁的多种灵活性。对于synchronized提供的方式,锁的操作流程是被固化的,必定是先获取再释放。主要使用方法如下:

//创建锁
Lock lock = new ReentrantLock();
//获取锁,重要:不要将获取锁的步骤写在try块中,防止在获取锁的过程中抛出异常,锁无故被释放
lock.lock();
try {
   // ....
} finally {
    lock.unlock();
}

Lock接口相对于synchronized关键字主要比较鲜明的特性有:

1. 尝试非阻塞地获取锁
 当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功获取并持有锁,对应于其中的lock.tryLock()方法,调用后立刻返回,如果获取则返回true,否则返回false,不会在那傻等,通常使用如下的方式:

if(lock.tryLock()) {
    try {
        //....
    }catch (Exception e) {
        
    } finally {
        lock.unlock();
    }
} else {
    //....
}

2. 可以中断地获取锁
 获取锁的线程可以相应中断(被自己或者其他线程中断线程的等待状态),当获取到锁的线程被中断时,中断异常抛出,同时锁被释放,对应于lock.lockInterruptibly()方法,比如两个线程同时通过此方式准备获取锁,当一个线程获取锁成功后那么另一个想要获取锁的线程只能傻等了,这种情况是我们不希望发生的,那么此时可以对傻等线程调用interrupt()方法可以使傻等线程的傻等状态中断。主要使用方法如下:

//此处的异常可以向上抛出
lock.lockInterruptibly();

try {
    //...
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

3. 超时获取锁
 可以在指定时间内获取锁,如果指定时间内无法获取锁,那么则返回,对应于lock.tryLock(time, TimeUnit)方法,类似与短时间内傻等状态,可以相应中断,使用方式与上述不带参数的tryLock类似。

2. 队列同步器(AbstractQueuedSynchronizer)

 队列同步器(AbstractQueuedSynchronizer),主要用于构建锁或者其他同步组件的基础框架,使用一个int类型的成员变量表示同步,通过内置的FIFO队列完成资源获取线程的排队工作。同步器的使用方式是子类通过继承队列同步器并实现它的抽象方法来管理同步状(注意:推荐定义为自定义同步组件的静态内部类),在抽象方法的实现过程中对同步状态的更改可以使用以下的三个方法:

//1.获取当前同步状态,返回int类型
int state = getState();

//2.设置当前同步状态
setState(int  newState);

//3.使用CAS设置当前状态,该方法能保证状态设置的原子性
boolean resu = compareAndSetState(int expect, int update);

通过继承同步器可以重写的方法和内容主要如下:

/*
 * 独占式获取同步状态,实现该方法需要查询当前状态并判断是否符合预期,然后再进行
 * CAS操作进行同步状态的设置
 */
@Override
protected boolean tryAcquire(int arg) {
    return super.tryAcquire(arg);
}

/*
独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
*/
@Override
protected boolean tryRelease(int arg) {
    return super.tryRelease(arg);
}

/*
共享式获取同步状态,返回大于等于0的值,表示获取成功,反之获取失败
*/
@Override
protected int tryAcquireShared(int arg) {
    return super.tryAcquireShared(arg);
}

/*
共享式释放同步状态
*/
@Override
protected boolean tryReleaseShared(int arg) {
    return super.tryReleaseShared(arg);
}

/*
当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所占
*/
@Override
protected boolean isHeldExclusively() {
    return super.isHeldExclusively();
}

【注】
 “独占式”和“共享式”的概念:独占式表示同一时刻只有一个线程可以占有,其他的只能在同步队列中等我完事儿后在来抢,共享式则是与之相反。

在自定义同步组件时,将会调用同步器提供的方法,主要有如下几种:

  • acquire(int arg) :独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法;
  • acquireInterruptibly(int arg):与acquire(int arg)相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前被中断,则该方法会抛出InterruptedException并返回;
  • tryAcquireNanos(int arg,long nanos):在acquireInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,获取到了返回true
  • release(int arg):独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;
  • acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待。与独占式的不同是同一时刻可以有多个线程获取到同步状态;
  • acquireSharedInterruptibly(int arg):与acquire(int arg) 相同,但是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,如果当前被中断,则该方法会抛出InterruptedException并返回;
  • tryAcquireSharedNanos(int arg,long nanos):在acquireSharedInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么将会返回false,获取到了返回true
  • releaseShared(int arg):共享式释放同步状态;
  • getQueuedThreads():获取等待在同步队列上的线程集合;

 好的,下面来看个独占式锁的栗子,Mutex在同一时刻只允许一个线程占有锁,主要看下面静态内部类的同步器都是重写的独占式抽象方法,所以肯定是独占式的,对吧,如果要搞成多线程可以共享的应该怎么办,对嘛,直接复写抽象队列同步器里的共享式方法就好啦,好了,看代码:

package com.hhu.threads;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 测试独占锁
 * 在自定义的队列同步器中主要是独占式的父类方法进行重写
 * 然后在锁Mutex中通过层性向上调用自定义的同步器的独占式方法实现
 * 单线程限制的独占式的控制,以后调用锁的时候直接用即可
 * created by WeiguoLiu on 2017/12/26
 */
public class Mutex implements Lock {
    //利用静态内部类自定义同步器,则是推荐的方式
    private static class Sync extends AbstractQueuedSynchronizer {
        //重写判断同步器是否处于独占式占用状态,0表示未被占用,1表示被占用
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //重写同步器获取锁的方式,当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            //CAS操作,如果该内存位置上的值为0(即没有被占用),那么将它更新为1,
            // 表示被调用的线程占用,否则不更新
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //重写独占式释放同步状态的方法,将状态置为0
        protected boolean tryRelease(int releases) {
            if (getState() == 0)
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //返回一个Condition,每个condition都包含一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    //将需要的操作代理到Sync上,定义一个同步器
    private final Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean siLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

}

3.几种锁机制

1.重入锁

 重入锁ReentrantLock,这种锁支持重进入的锁,表示能支持一个线程对资源的重复加锁,此外,重入锁还支持获取锁的公平和非公平。比如上述我们定义的独占锁Mutex就不是一个重入锁,从它的tryLock()方法可以看出,在进行第二次tryLock()获取锁的时候,CAS操作时将得不到预期的0的同步状态,所以不能进行二次进入获取,所以他不是重入锁。但是syqnchronized关键字是隐式的支持重新进入的,通常在用synchronized修饰递归方法时,线程在开始的时就获取的了锁,随着递归深度的增加,仍然可以连续的不断获取锁,所以它是隐式地支持重入行为的。在重入锁中,已经获取锁的线程还是可以继续调用lock()方法获取锁而不会出现被自己阻塞的情况,注意这里重入的定义是仅仅局限于同一个线程而言,不要和独占这个词搞混了。另外获取锁的公平性是指的先到先得的原则,通常对于一个独占式的锁总会需要进行等待,换而言之,可以理解为等待获取锁的时间越长的线程应该越优先获取锁。公平锁机制能够减少“饥饿”的发生,但是实际中,公平锁机制往往没有非公平加锁机制高效。

2.读写锁

 读写锁允许同一时刻多个线程对访问共享资源,但是在一个线程在进行写操作的时候,所有的读线程和其他的写线程将全被阻塞(只有写线程释放锁后才能允许读,这样有效的避免了数据的脏读)。读写锁维护的是一对锁,即读锁和写锁,将读锁和写锁进行分离,使得线程的并发性得到了较大的提高。在Java并发包中提供的读写锁的实现是ReentrantReadWriteLock,除此之外,还有一点是需要注意的,就是一个或者多个线程获取读锁之后,其他线程是无法获取写锁的,就上上面所讲的那样,这样的线程读写策略有效的避免了数据的脏读,由于读锁是共享的支持重入行为的,每次获取和释放锁的时候,增加或者减少的都是读的状态。下面是在缓存中使用读写锁的案例:

package com.hhu.threads;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 读写锁在缓存中的使用
 * created by WeiguoLiu on 2017/12/28
 */
public class Cache {
    static Map<String, Object> map = new HashMap<String, Object>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    
    //获取一个key对应的value
    public static final Object get(String key) {
        //首先获取锁
        r.lock();
        try{
            return map.get(key);
        } finally {
            //最后资源的释放
            r.unlock();
        }
    }
    
    //设置key对应的value,并返回旧值的value
    public static final Object put(String key, Object value) {
        w.lock();
        try {
            return map.put(key, value);
        } finally {
            w.unlock();
        }
    }
    
    //清空缓存内容
    public static final void clear() {
        //清空是一种写操作,获取写锁,阻塞索所有线程的读操作和其他线程的写操作
        w.lock();
        try {
            map.clear();
        } finally {
            w.unlock();
        }
    }
}

3.锁降级

 锁降级指的是写锁降级为读锁。如果当前线程拥有写锁,然后将其释放再获取读锁,我们的锁降级可不是指的这种逗逼情况。详细点讲,锁降级是指当前线程仍然把握着当前拥有的写锁,然后再获取读锁,然后将写锁释放,这才是上述锁降级的过程(一定是把握写锁的同时去获取读锁!)。首先锁降级的这一操作流程是必不可少,主要是为了当前线程可以感知变量的修改即时可见,具体场景如下:

有两个线程A和B,它们都是同时获取的是写锁,而A线程主要干的事儿是获取写锁而非获取读锁,而B线程也是获取的写锁并进行数据的更新操作,那么由于A线程持有的是数据的写锁,A线程是无法感知B线程对变量的修改(所以A线程读取的数据是无用的),但是如果A线程获取的是读锁,那么B线程将会被阻塞(注意读锁获取后,其他线程不能获取写锁,对应的其他线程获取写锁,那么此时不能获取读锁,防止读取脏数据),直到A线程使用数据并释放读锁后,线程B才能获取写锁进行数据的更新。当然ReentrantReadWriteLock不支持锁升级的,原因也是为了数据的可见性,如果读锁已经被多个线程获取,任意一个线程成功获取写锁并更新了数据,那此时这种更新对其他获取读锁的线程是不可见的。

4.LockSupport工具类

 在需要阻塞或者唤醒一个线程的时候,都会使用LockSupport工具类完成相应的工作,它定义了一组公共的静态方法来完成上述工作。来看一下jdk8中源码吧:

	//唤醒处于阻塞的线程,但是最起码线程已经启动了吧
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
    
    //阻塞当前线程,blocker是负责该线程阻塞的同步器
    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }

	//阻塞当前线程,最长不超过nanos纳秒,前者的blocker就是同步器
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }

	//阻塞当前线程直到deadline时间
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

/*
* 下面是上述方法的重载,上面多出来的同步器,可以从线程dump结果得到阻塞对象的更多信息,
* 方便定位,上面的方法主要问题排查和系统监控
* /
	//阻塞当前线程
	public static void park() {
        UNSAFE.park(false, 0L);
    }
	
	//阻塞当前线程,最长不超过nanos纳秒
    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }
	
	//阻塞当前线程直到deadline时间
	public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

5.Condition接口

 任何Java对象都有一组监视器的方法,通常是定义子在java.lang.Object上,包含了wait()wait(long timeout)notify()notifyAll()方法,将这些方法和synchronized关键字结合可以实现等待/通知模式,Contidition接口中同样提供了类似于Object的监视器方法,和Lock配合可以实现等待/通知的模式。Condition定义了等待、通知两种类型的方法,在此场景下,需要注意的是在线程在调用这些方法时,需要提前获取Condition对象关联的锁,它的锁是由Lock对象创建(调用Lock对象的newCondition()),所以其实可以这样讲,Condition是依赖于Lock对象存在的,好了,看一个Condition的使用案列:

 通常情况下,会将Condition对象作为成员变量,在调用await()后,当前线程会释放锁并且在此等待,而其他线程调用signal()后,通知当前线程后,当前线程才会从await()返回,终止等待的状态,并且在返回时就已经获取了锁。

package com.hhu.threads;

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

/**
 * Condition示例
 * created by WeiguoLiu on 2017/12/29
 */
public class ConditionUseCase {
    Lock lock = new ReentrantLock();
    //可以看到Condition是依赖于Lock对象的
    Condition condition = lock.newCondition();
    
    public void conditionWait() throws InterruptedException {
        try {
	        //使用前先获取锁
            lock.lock();
        } finally {
            lock.unlock();
        }
    }
    
    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }
}

下面是有个利用Condition实现的有界队列,比上面的靴微地好理解点:

package com.hhu.threads;

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

/**
 * Contion的案例:有界队列的实现
 * created by WeiguoLiu on 2017/12/29
 */
public class BoundedQueue<T> {
    private Object[] items;
    //增加、移除、数组中的数据量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();

    public BoundedQueue(int size) {
        //在构造方法中进行数组的初始化工作
        items = new Object[size];
    }

    public int getSize() {
        return count;
    }

    //往里面添加一个元素
    public void add(T t) throws InterruptedException {
        //首先获取锁
        lock.lock();
        try {
            //如果队列已满,那么添加数据的线程将会被阻塞,直到队列有空位
            while(count==items.length) {
                notFull.await();
            }
            items[addIndex] = t;
            if(++addIndex==items.length) {
                addIndex = 0;
            }
            ++count;
            //通知正在await的线程
            notEmpty.signal();
        } finally {
            //最后释放锁
            lock.unlock();
        }
    }

    public T remove() throws InterruptedException {
        lock.lock();
        try {
            while(count==0) {
                notEmpty.await();
            }
            Object x = items[removeIndex];
            if(++removeIndex==items.length) {
                removeIndex = 0;
            }
            --count;
            notFull.signal();
            return (T)x;
        } finally {
            lock.unlock();
        }
    }
}

第四章 Java并发容器和框架

1.ConcurrentHashMap

 关于HashMapHashtable这俩儿货一直是面试过程中属于阴魂不散的,最本质的区别就是前者不是线程安全的,而后者是线程安全的,这里通过jdk的源码可以看到在Hashtable的提供的大部分方法都加上了synchronized关键字,所以明白它为啥是线程安全的吧,但是一直说synchronized是不好的,因为它重啊,阻塞式的保证同一时刻只有一个线程可以获取唯一的一把锁,所以可想而知在并分量达到一定数值时,Hashtable的效率实在让人失望,老版本中的HashMap还存在并发环境下执行put操作出现死循环的问题(不过我用jdk8中并没有测试出来这个问题,不知道是不是源码修改了,后面再研究),处于这样的尴尬情况,所以ConcurrentHashMap出现了(线程安全并且效率比Hashtable高),他主要通过分段锁的技术提高了并发访问的效率,分段锁就是ConcurrentHashMap这个容器中不像Hashtable只有一把锁抢起来太费劲了,它以数据分段为单位,为每一段数据提供一把锁,在获取不同数据段锁的时候,就避免了竞争情况的发生,当A线程访问1号数据段的时候,那么B线程可以访问2号数据段呀,完全不要去竞争,多和谐!
 在ConcurrentHashMap中,包含了一个Segment数据结构和一个HashEntry数组。Segment是一种重入锁(ReentrantLock),而HashEntry是用于存储数据的键值对,哦对了,Segment也是一种键值对的数据形式,在其中扮演着锁的角色,包含着HashEntry数组,而HashEntry又是一种链表结构,每个Segment都将守护着HashEntry数组,当每次要对HashEntry里面的数据进行更改时必须先获取守护它的Segment锁。我们可以形象的将ConcurrentHashMap的结构描述为(鬼画符啊):
这里写图片描述
ConcurrentHashMap中的get操作高效在于get操作不需要加锁,除非读到的值为空才会加锁重读,而Hashtable中的get操作是需要加锁的,在ConcurrentHashMap的get方法中将要使用的共享变量都定义为volatile类型(比如用于统计当前Segment大小的count字段和用于存储值的HashEntryvalue);在ConcurrentHashMapput操作中,在操作共享变量时,为了线程安全,必须加锁,put方法首先定位到Segment,然后在Segment里面进行插入操作,插入操作主要经过两个步骤:

  1. 判断是否需要对Segment里面的HashEntry数组进行扩容(HashMap中扩容时是先插入元素再判断是否达到容量值,而Segment的扩容是在插入前先判断再插入,这样就避免了扩容后没有元素插入的尴尬情况,HashMap扩容时是将其容量阔成为原来的2倍,而在ConcurrentHashMap中,为了高效,并不会对整个ConcurrentHashMap进行扩容,而是仅对某个Segment进行扩容);
  2. 其次定位添加元素的位置,然后将其放在HashEntry数组中。

2.队列

 在线程安全队列中,有阻塞算法和非阻塞算法,阻塞算法可以使用一个锁(入队和出队用同一把锁)也可以使用两把锁(出队和入队使用不同的锁);非阻塞算法则是使用循环CAS方式实现。
1.ConcurrentLinkedQueue
ConcurrentLinkedQueue是基于链接节点的无界线程安全队列,采用先进先出的规则对节点进行排序,新增元素添加队列尾部,获取元素时,返回的时队列头部的元素,采用“wait-free”算法(CAS算法)。几种常用的操作:
1.入队
入队就是将入队的节点添加到队列的尾部。
2.出队
出队就是从队列中返回一个节点元素,并清空该节点对元素的引用

2.阻塞队列
阻塞队列是一个支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移除的方法。首先阻塞的插入方法指:当队列满时,队列会阻塞插入元素的线程,直到线程不满;支持阻塞的移除方法是指:在队列为空时,获取元素的线程会等待队列变为非空。在阻塞队列不可用时,上述的两种操作提供了如下的4种处理方式(都是针对是不同线程对该队列进行取、放操作):
1.抛出异常:当队列满时再有线程执行插入操作,会抛出IllegalStateException异常,当队列为空时再获取元素会抛出NoSuchElementException异常;
2.返回特殊值:当线程插入成功时返回true,移除则是从队列中取出一个元素,如果没有则返回null;
3.一只阻塞:当阻塞队列满时再插入(put)时,队列会一直被阻塞,当队列为空时,线程获取元素将会,那么此线程将会被一直阻塞。
4.超时退出:当阻塞队列满时,当有线程向里面插入时,队列将会阻塞它一段时间,如果超出了指定时间将会使该线程退出。

在JDK7中提供了7中阻塞队列,主要如下:

  1. ArrayBlockingQueue
    ArrayBlockingQueue是用数组实现的有界阻塞队列,按照先进先出的原则进排序,默认不保证线程公平的访问队列(即不保证阻塞的先后顺序访问队),阻塞的线程可以任意争夺这个资源,可能最先被阻塞的线程一直到最后才访问到队列资源。通常情况下,为了保证公平性会降低吞吐量
  2. LinkedBlockingQueue
    LinkedBlockingQueue是利用链表实现的有界阻塞队列,默认和最大的长度为Integer.MAX_VALUE,按照先进先出的原则进行排序。
  3. PriorityBlockingQueue
    PriorityBlockingQueue是一个支持优先级的无界阻塞队列,默认情况下元素采取自然顺序升序排序,也可以自定义类实现compareTo()方法指定排序规则,或者初始化PriorityBlockingQueue时,制定构造参数Comparator对元素进行排序,但是不能保证同优先级别的顺序
  4. DelayQueue
    DelayQueue支持延时获取元素的无界阻塞队列,队列使用PriorityQueue实现,但是队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从队列从提取元素。这玩意儿很有用,可以运用在缓存系统的设计和定时任务调度的场景下
  5. SynchronousQueue
    SynchronousQueue是一个不存储元素阻塞队列,每个put操作必须等待一个take操作,否则不能继续添加元素,它支持公平访问队列,默认是采用非公平的性策略访问,使用如下的构造方法进行初始化的时候会进行相应的设置
public synchronousQueue(boolean fair) {
	transferer = fair ? new TransferQueue():new TransferStack();
}

SynchronusQueue队列本身并不存储任何元素,只是将线程处理过的
6. LinkedTransferQueue
7. LinkedBlockingDeque

第五章 Java中的原子操作类

多线程除了利用同步监视器和同步代码块来保证线程之间的共享资源的安全外,在JDK5后的版本中提供了原子操作类来更为简单且性能高效、线程安全的变量方式,这个问题也是在之前面试过程中被问懵逼的一个技术点,由于在学校对于Java的接触面一直比较狭隘,仅仅知道有个线程安全的概念,所以根本不知到有这么个东西,Java提供的原子操作都是在

java.util.concurrent.atomic

这个包中,总共13个原子操作类,每个类都有基本都是实用Unsafe实现的包装类,主要分为4种类型:原子更新基本类型、原子更新数组、原子更新引用和原子更新属性。

1.原子更新基本类型类

原子更新基本类型的类,Atomic主要提供下面的3个类:
AtomicInteger:原子更新整型数
AtomicLong:原子更新长整型数
AtomicBoolean:原子更新布尔类型数
下面是关于AtomicInteger的一Demo:

package com.hhu.Atomic;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @author WeiguoLiu
 * @date 2018/1/14 9:34
 */
public class AtomicIntegerTest {
    //定义原子整型数
    static AtomicInteger ai = new AtomicInteger(1);

    public static void main(String[] args) {
        //AtomicInteger中的getAndIncrement方法是将当前值加1并返回自增操作前的旧值
        System.out.println(ai.getAndIncrement());
        //获取当前值
        System.out.println(ai.get());
        //进行加和操作并返回加和之前的值
        System.out.println(ai.getAndAdd(2));
        System.out.println(ai.get());
        //进行加和操作并返回结果,注意和上面的方法的区别,其实就是顺序,上面是先获取再加和
        System.out.println(ai.addAndGet(2));
        //CAS操作返回原值和预期值是否相同,相同就更新并返回true,否者不更新返回false
        System.out.println(ai.compareAndSet(5, 8));
        System.out.println(ai.get());
        //返回旧值并设置新值
        System.out.println(ai.getAndSet(5));
        System.out.println(ai.get());
    }
}

2.原子更新数组

对数组的更新类似于上述的操作,Atomic提供下面的4个类:
1.AtomicIntegerArray:原子更新整型数组里面的元素;
2.AtomicLongArray:原子更新长整型数组元素;
3.AtomicReferenceArray:原子更新引用类型数组里面的元素;
下面的是原子更新整型数组的Demo:

package com.hhu.Atomic;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @author WeiguoLiu
 * @date 2018/1/14 9:34
 */
public class AtomicIntegerArrayTest {
    //定义原子整型数组
    static int[] value= new int[]{1, 2};
    //这里将数组以构造方法的方式传进去,Atomic在操作时,会把这个数组在内部复制一份在
    //然后在内部对它复制的数组进行操作而不会去影响原来的数组
    static AtomicIntegerArray aa = new AtomicIntegerArray(value);
    public static void main(String[] args) {
        //获取数组中的第一个数,并设置新值为3
        aa.getAndSet(0, 3);
        //对内部有影响
        System.out.println(aa.get(0));
        //这里对原有的数组不会影响
        System.out.println(value[0]);
        //将对应索引号的元素进行加和操作
        System.out.println(aa.addAndGet(0, 3));
        //数组中的CAS操作和单个的Atomic基本类型操作类似,但是必须提供数组的索引号
        System.out.println(aa.compareAndSet(0, 5, 9));
        System.out.println(aa.get(0));
    }
}

3.原子更新引用类型

原子更新多个变量,可以使用原子更新引用类型的类,不像原子更新的基本类型,Atomic包中提供了下面的3个类:
1.AtomicReference:原子更新引用类型;
2.AtomicReferenceFieldUpdater:原子更新引用类型里面的字段
3.AtomicMarkableReference:原子更新带有标记位的引用类型,可以更新布尔类型的标记位和引用类型,构造方法为AtomicMarkableReference(V initialRef, boolean initialMark)
下面是AtomicReference的一个Demo:

package com.hhu.Atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 测试原子更新引用类型
 * 由于AtomicInteger只能对单个变量进行更新,对多个变量的更新
 * 就需要使用这个原子更新的引用类,这是对一个引用的整体更新,而非局部的字段更新
 *
 * AtomicReference:原子更新引用类型
 * AtomicReferenceFieldUpdater:原子更新引用类型里面的字段
 * AtomicMarkableReference:原子更新带有标记位的引用类型,可以原子更新布尔类型的标记位和引用类型
 * 构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)
 *
 * @author WeiguoLiu
 * @date 2018/1/14 10:20
 */
public class AtomicReferenceTest {
    public static AtomicReference<User> af = new AtomicReference<User>();

    public static void main(String[] args) {
        User user = new User("jack", 15);
        af.set(user);
        User updateUser = new User("air", 18);
        //调用CAS进行更新,先判断原变量是否还是user,是的话就更新为新值,否则不更新
        af.compareAndSet(user, updateUser);
        System.out.println(af.get().getName());
        System.out.println(af.get().getAge());
    }

    static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

4.原子更新字段

注意和上面原子更新引用类型的区别
原子更新对某个类中的某个字段,Atomic包中主要提供了下面的3个类:
1.AtomicIntegerFieldUpdater:原子更新整型数的更新器
2.AtomicLongFieldUpdater:原子更行长整型的更新器
3.AtomicStampedReference:原子更新带有版本号的引用类型,这个类将整数值与引用关联,用于原子的更新数据和版本号,这就解决了CAS操作中可能出现的ABA问题。

原子更新字段的主要分为两个步骤:
1.因为原子更新字段类都是抽象类,所以每次使用的时候必须使用静态方法new Updater()来创建一个更新器,并且指定其中需要更新的类和该类实例的某个字段属性名;
2.更新类的某个属性字段必须需要设置为public volatile才能对此字段进行更新;
上述的更新过程就是不断对更新类的中待更新的字段的不断的创建的更新器,并且将其中更新的字段名设置进去即可,下面是更新器的一个Demo:

package com.hhu.Atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * 原子更新某个类的某个字段,在Atomic包中主要提供的3个类:
 * AtomicIntegerFieldUpdater:原子更新整型的字段更新器
 * AtomicLongFieldUpdater:原子更新长整形字段的更新器
 * AtomicStampedReference:原子更新带有本版号的引用类型,可以解决CAS操作的ABA问题
 *
 * 原子更新字段类的步骤如下:
 * 1.因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器
 * 并设置想要更新的类和属性
 *
 * 2.更新类的字段必须使用public volatile修饰
 *
 * @author WeiguoLiu
 * @date 2018/1/14 10:44
 */
public class AtomicIntegerFieldUpdaterTest {
    //首先创建原子更新器,并设置需要更新的对象和对象的字段
    private static AtomicIntegerFieldUpdater<User> afu =  AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

    public static void main(String[] args) {
        User user = new User("conan", 10);
        //获取更字段更新器对User的年龄进行更新(自增1,获取自增前的值)
        System.out.println(afu.getAndIncrement(user));
        //获取更更行器中类的字段的属性值
        System.out.println(afu.get(user));
    }

    public static class User {
        private String name;
        //必须将更新的字段设置为public volatile
        public volatile int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

第六章 Java并发工具类

jdk并发包提供了一些并发工具类:CountDownLatch、CyclicBarrier和Semaphore工具类提供了并发流程控制的手段,Exchanger提供了在线程间交换数据的一种手段。

1.等待多线程完成的CountDownLatch

场景:多线程解析EXCEL,每个线程负责一个sheet,当所有线程解析完成后给出提示,所以这个提示必须是在所有线程完成之后才可以,下面是利用join方法完成的案例,直接将每个sheet分配给各个线程,然后在主线程中调用各个线程的join方法来阻塞主线程,在主线程中给出提示即可,Demo如下:

package com.hhu.threadutil;

/**
 * 场景:多线程解析EXCEL,每个线程负责一个sheet,当所有线程解析完成后给出提示
 * @author WeiguoLiu
 * @date 2018/1/15 8:52
 */
public class JoinCountDownLatchTest {
    public static void main(String[] args) {
        Thread parser1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("parser1 has finshed task1!");
            }
        });

        Thread parser2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("parser2 has finshed task2!");
            }
        });

        parser1.start();
        parser2.start();
        try {
            //这里注意join的线程必须在启动后才能生效,其原理是不停地检查
            //join线程是否存活,如果存活则让当前线程永远等待,也可以指定等待的时间,
            //在join线程终止后,线程的this.notifyAll()会被调用(这个方法在JVM中,需查看JVM源码),
            parser1.join();
            parser2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Excel parse has finshed!");
    }
}

当然,下面利用JDK提供的CountDownLatch并发工具类完成上述的工作,就是等地,代码如下:

package com.hhu.threadutil;

import java.util.concurrent.CountDownLatch;

/**
 * 利用JDK中提供的CountDownLatch实现
 * @author WeiguoLiu
 * @date 2018/1/15 9:18
 */
public class CountDownLatchTest {
    //这里传入参数作为计数器,等待N个点完成就传入N即可,这里将线程的数量传入,必须大于0
    static CountDownLatch c = new CountDownLatch(2);
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Task1 has complated!");
              
                /*
                每次调用CountDownLatch的countDown方法,计数器就会执行减1操作
                这个方法可以在任意地方执行,所以上面计数器的N可以是线程的数量,也可以
                也可以是1个线程里的N个步骤
                 */
                c.countDown();
                System.out.println("Task2 has complated!");
                c.countDown();
            }
        }).start();
        /*CountDownLatch的await的方法会阻塞当前线程,直到CountDownLatch的计数器变为0
        如果有某个线程执行速度比较慢,也可以指定等地的时间await(long timeout, TimeUnit unit)
         */
        c.await();
        System.out.println("Task has all complated!");
    }
}

2.同步屏障CyclicBarrier

CyclicBarrier用于线程屏障的阻塞,当线程到达屏障点后调用CyclicBarrier的await方法告诉CyclicBarrier该线程已经到达屏障点并且被阻塞,等待其他线程全部到达屏障点后在全部运行,至于各个线程是哪个先运行哪个后运行,全凭自己去抢,优势多线程之战了,下面是一个简单的Demo:

package com.hhu.threadutil;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 可循环屏障的案例
 * CyclicBarrier的默认构造方法需要传入一个整型数,一般指线程数
 * 每个线程达到屏障点的时候会被阻塞,直到最后一个线程达到屏障时,屏障才会开门
 * 这时所有的线程才会继续运行
 * @author WeiguoLiu
 * @date 2018/1/15 9:40
 */
public class CyclicBarrierTest {
    //这里设置的有几个屏障点就必须有几个线程去调用CyclicBarrier的await的方法
    //否则suo
    static CyclicBarrier c = new CyclicBarrier(2);

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //每个线程调用await()告诉CyclicBarrier我已经到达屏障点了然后被阻塞,
                    //这里是随意放了一个位置作为阻塞屏障点
                    c.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("Task1 has complated!");
            }
        }).start();

        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("Task has complated!");
    }
}

【注】这里到底是先输出哪个字符串是不一定的,而且设置的CyclicBarrier设置的屏障点的数量一定要和后面调用CyclicBarrier调用其await的次数一致,否则其他的线程到达屏障点后会一直被阻塞在屏障点,直到await的调用次数达到设置的值。
当然CyclicBarrier也提供高级的构造方法CyclicBarrier(int parties, Runnable barrierAction),在所有线程到达屏障点后优先执行barrierAction,案例如下:

package com.hhu.threadutil;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * CyclicBarrier还提供了比较高级的构造函数CyclicBarrier(),
 * 用于达到屏障时优先执行,方便复杂业务场景
 * @author WeiguoLiu
 * @date 2018/1/15 10:00
 */
public class CyclicBarrierTest2 {
    /*CyclicBarrier提供的更为高级的构造方法CyclicBarrier(int parties, Runnable barrierAction),
    在各个线程到达屏障点后,会优先执行barrierAction
     */
    static CyclicBarrier c = new CyclicBarrier(2, new A());

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    /*这里放一个屏障点,线程执行到这个地方时会告诉CyclicBarrier我已经到达屏障点了
                    然后就会被阻塞在这里,等待其他的线程到达各个的屏障点
                     */
                    c.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("Task1 has complated!");
            }
        }).start();
        try {
            c.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        System.out.println("Task2 has complated!");
    }

    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println("Task3 has complated!");
        }
    }

}

同理,上述Demo中的只有Task3是优先执行的,至于Task1和Task2谁先执行,看运气咯。下面是多线程处理银行流水的Demo,处理方式主要和Excel差不太多,各个线程完成各个Sheet的流水处理,然后由优先线程来统计银行总的流水信息,主要代码如下:

package com.hhu.threadutil;

import java.util.Map;
import java.util.concurrent.*;

/**
 * 场景:每隔Sheet保存一个银行账户的近一年的银行流水,现在要统计
 * 用户的日均银行流水,先用多线程处理每个Sheet里面的银行流水,操作
 * 完得到每个Sheet里面的流水,最后用CyclicBarrier里面的barrierAction执行计算结果
 *
 * @author WeiguoLiu
 * @date 2018/1/15 10:45
 */
public class BankWaterService implements Runnable {

    //构造CyclicBarrier,4个屏障点,优先执行本服务线程,即下面重写的run方法
    private CyclicBarrier c = new CyclicBarrier(4, this);

    //构造定长线程池,只启动4个线程,注意线程池的构建方式
    private Executor executor = Executors.newFixedThreadPool(4);

    //用于保存每个线程处理流水的结果,用于后面本服务线程的统计
    private ConcurrentHashMap<String, Integer> sheetBanWaterCount = new ConcurrentHashMap<String, Integer>();

    private void count() {
        //这里循环创建的线程池的4个线程执行任务
        for (int i = 0; i < 4; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    //这里是将线程名和值1放入Map中
                    sheetBanWaterCount.put(Thread.currentThread().getName(), 1);
                    System.out.println(Thread.currentThread().getName() + " has processed task");
                    try {
                        /*到达线程屏障点,线程被阻塞,等待其他线程到达屏障点,这里是一个线程一个屏障点,
                         * 由于是多线程,循环创建4个线程,每个线程一个屏障点,4个线程4个屏障点,完美
                         * 在每个线程完成流水统计后设置屏障点,当前线程的等待
                         */
                        c.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    /*该线程是CyclicBarrier优先执行的线程任务,在所有计算流水线程处理完毕到达屏障点后,优先执行这个综合
    各个线程的结果即可
     */
    @Override
    public void run() {
        int result = 0;
        //便利流水处理结果进行加和处理
        for (Map.Entry<String, Integer> sheet : sheetBanWaterCount.entrySet()) {
            result += sheet.getValue();
        }
        sheetBanWaterCount.put("result", result);
        System.out.println("All threads have complated, result = " + result);
    }

    public static void main(String[] args) {
        BankWaterService bankWaterCount = new BankWaterService();
        bankWaterCount.count();
    }
}

当然在上面的Demo中已经可以看到CountDownLatch和CyclicBarrier两者之间的区别,但是两者最主要的区别在与前者是一次性的操作,它的计数器只能用一次,在下次使用的时候必须重新进行初始化,而 后者的则是可以直接调用它的reset()方法重置,CyclicBarrier还提供更为有用的方法,比如getNumberWaitting来获取正在等待的线程数,还有isBroken()方法来了解阻塞线程是否被中断,下面是一个简单的Demo:

package com.hhu.threadutil;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 测试CountDownlatch和CyclicBarrier的区别,前者只可以应用一次,初始化,在计数器用完就无法再次调用
 * 后者是可以进行重置计数器reset(),可以用于更为复杂的场景,而且提供很多方法
 * @author WeiguoLiu
 * @date 2018/1/15 11:26
 */
public class CyclicBarrierTest3 {
    //创建屏蔽的点的计数器,初始化的屏蔽点为2
    static CyclicBarrier c = new CyclicBarrier(2);
    public static void main(String[] args) {
        //创建处理每个Sheet业务子线程
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //设置屏蔽点,处理该业务和线程阻塞等待
                    c.await();
                } catch (Exception e) {
                    System.out.println("error1 has happend!");
                }
            }
        });
        t.start();
        t.interrupt();
        try {
            c.await();
        } catch (Exception e) {
            //通过这个方法可以了解到阻塞线程是否被中断
            System.out.println(c.isBroken());
        }
    }
}

3.控制并发线程数的Semaphore

Semaphore也就是信号量,还是比较形象的,它负责协调各个线程来保证公共资源的合理使用,可以理解为在设置信号量以后,只有看到信号量的线程才有资格执行,其他线程是没有资格执行的,就好比一电梯中有一个限重的概念,一个电梯只允许有在13个人同时在里面,超过这个数之外的人只能在外面等,只有里面的出来之后,才可以进去等量的人,类比到这里就是,信号量控制着最大并发执行的线程数,超过这个数量的线程只能在外面等待,在Semaphore里面也提供了其他的一些方法,比如:
availablePermits()可以返回信号量中可用的许可证数;
getQueueLength()获取正在等待的许可证数;
s.hasQueuedThreads()是否有现成正在等待获取许可证;
简单的案例如下:

package com.hhu.threadutil;

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

/**
 * 测试Semaphore的案例,信号量的可以理解为红绿灯,在一个场景下,一条马路上只允许一个有100辆车。
 * 那么在信号乱量Semaphore的控制下,只有当前路上的100辆车看的信号量是允许放行的状态,而对于
 * 100辆以外的车来说,这个信号量都是不允许放行的状态,这个说下来邮更有点像某个场地了,有人数限制,
 * 只有场地里面的人离开了才会让外面等量的人进来
 *
 * @author WeiguoLiu
 * @date 2018/1/15 13:35
 */
public class SemaphoreTest {
    private static final int THREAD_COUNT = 30;
    //创建定长线程池
    private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
    //规定一次只能有10个的信号量
    private static Semaphore s = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //获取许可证,只有获取许可证,线程才能正常执行
                        s.acquire();
                        //availablePermits方法获信号量中可用的许可证,getQueueLength是获取正在等待许可证的线程数
                        System.out.println("save date" + s.availablePermits() + " " + s.getQueueLength());

                        //上诉业务不太复杂看不出效果,加睡眠时间
                        Thread.currentThread().sleep(5000);

                        //运行完释放许可证给其他线程使用
                        s.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            //这里无需手动释放,否则报错
//            threadPool.shutdown();
        }
    }
}

4.线程间交换数据的Exchanger

Exchanger主要用于线程间的数据交换,它为线程提供了一个数据交换和同步点,在某个线程到达同步时会调用Exchanger中的exchange()的方法,传入本线程的需要给对方的数据并且返回对方在同步站点传入的数据(在某一个线程到达先达到同步点后会等待另一个线程到达同步点才进行数据的交换),这个工具类可以用于遗传算法(两人进行交配时会交换数据,并使用交叉规则得出交配结果)和数据的校对(两个人同时进行同一个工作,在某个点进行两人数据的比对,提高正确率),下面是简单的数据校对的Demo:

package com.hhu.threadutil;

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

/**
 * Exchanger是线程协同工作的工具类,主要用于线程间的数据交换。它提供了一个同步点,在这个同步点,
 * 两个线程可以进行数据的交换,在某一个线程先到达exchange方法,它会一直等待第二个线程到同步点,
 * 当两个线程都到达同步点的时候,两者可以进行数据的交换,下面是两个线程的数据校对的案案例
 * @author WeiguoLiu
 * @date 2018/1/15 14:32
 */
public class ExchangerTest {
    private static final Exchanger<String> exchanger = new Exchanger<String>();
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                String A = "Bannker A";
                try {
                    //这里将A传给同步点的另一个线程,并且返回对方传入的数据B
                    String re1 = exchanger.exchange(A);
                    Thread.sleep(2000);
                    System.out.println(re1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                String B = "Bannker B";
                String A = null;
                try {
                    //将B传入给同步点的两个线程并且返回对方传入的数据
                    A = exchanger.exchange(B);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("A equals B ? " + A.equals(B) + "\n A=" + A + "; B=" + B);
            }
        });
        threadPool.shutdown();
    }

}

【注】如果两个线程只要有一个没有执行exchange方法,那另一个就会一直等待,为了避免这种等待无限持续,可以设定最大的等待时长,可以使用exchange(V x, long timeout, TimeUnit unit)

第七章 线程池

在实际开发涉及到多线程时,往往会搞一个线程池统一管理,这样做的和数据库连接池那一块的东西有些类似了,有如下的一些好处:

  1. 降低资源的消耗:重复利用已创建的线降低线程创建和销毁的资源消耗

  2. 提高响应速度:当任务到达时不需要再等待线程创建这个过程

  3. 提高线程的可管理性:线程是稀缺资源,反复创建会消耗系统资源,降低系统的稳定性,线程池可以统一分配、调优和监控。

  4. 线程池原理


线程池处理任务的流程主要如下:
提交新的任务后,首先会判断核心线程池是否已满,如果不满则创建线程执行任务,如果满了再判断工作队列是否已满,如果不满则将该任务存储到这个工作队列中,如果工作队列已满则再对线程池进行判断,如果不满创建新的工作线程执行任务,如果已满则把这个任务交给饱和策略进行处理,呐,上面就是线程池处理提交任务的完整流程了,下面对上述的一些名词做一个简单的说明:
1.核心线程池:corePool,在创建线程池后默认里面是没有任何线程的,只有任务来的时候才创建线程执行任务,除非调用prestartAllCoreThreads()或者prestartCoreThread()方法预创建线程,在任务还没有到来时就创建corePoolSize个线程或一个线程,当线程池中线程的数据达到corePoolSize后就会把之后到来的任务放到缓存队列中;
2.工作队列:workQueue是一种阻塞式队列(BlockingQueue),用于存储等待执行的任务,主要有如下的几种阻塞队列:
LinkedBlockingQueue:一种基于链表结构的有界阻塞队列,按FIFO排序,吞吐量一般高于ArrayBlockingQueue,其中定长线程池Executors.newFixedThreadPool()就是使用的这个队列;
SynchronousQueue:一种不存储元素的阻塞队列,每个插入操作必须等到等一个线程调用移除操作,否则会阻塞在那里,吞吐量一般高于LinkedBlockingQueue,可缓存线程池Executors.newCachedThreadPool()就是只用的这种队列;
ArrayBlockingQueue:一种基于数组结构的有界阻塞队列,按FIFO原则排序;
PriorityBlockingQueue:一种具有优先级的无限阻塞队列。
【注】以上四种阻塞队列,前两种LinkedBlockingQueue和SynchronousQueue比较常用,另外两个不常用,另外建议使用有界队列(它能增加系统的稳定性和预警能力)。
3.饱和策略:RejectedExecutionHandler是在队列和线程池都已满的状态时,表示线程池已经处于饱和状态,这个时候任务再来的时候就必须采取一种策略来处理提交的新任务,主要的饱和策略有如下的四种:
ThreadPoolExecutor.AbortPolicy():直接抛出异常;
ThreadPoolExecutor.CallerRunsPolicy():只用调用者所在的线程来执行任务
ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列中最近的一个任务并执行当前任务;
ThreadPoolExecutor.DiscardPolicy():不处理,直接丢弃。

第一步:创建线程池
ThreadPoolExecutor类是Java线程池中最核心的类,继承自AbstractExecutorService,AbstractExecutorService是一个抽象类,它实现了ExecutorService接口(所以在创建线程池的时候可以写成ExecutorService threadpool = new ThreadPoolExecutor(...)的形势),当然除此之外,在JDK8中可以追踪到的最根本的接口是Executor接口,这里简单提一下其他的与ThreadPoolExecutor类并列的几个类:

ThreadPoolExecutor:线程池的核心类,用于执行被提交的任务;
ScheduledThreadPoolExecutor:可以在给定延迟后再运行,或者定期执行命令

ThreadPoolExecutor提供了四种构造方法,主要源码如下:

public class ThreadPoolExecutor extends AbstractExecutorService {
...
	    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    ...

上面的几个构造方法corePoolSize表示核心线程池的大小,maximumPoolSize表示线程池最大线程池,keepAliveTime表示线程最大空闲时间(即线程如果没有执行任何任务会存活多长时间,往往提高这个参数可以提高线程的复用率),unit表示前面keepAliveTime参数的单位,workQueue表示工作队列(阻塞队列),threadFactory表示创建线程的线程工厂,handler表示拒绝任务的策略,通常可以用上述ThreadPoolExecutor的四种初始化方法进行线程池的创建。

第二步:提交任务
呐,线程池创建后提交任务可以用如下的两种方式:

threadPool.execute(new Runnable());
threadPool.submit(new Runnable());

其中execute()方法没有返回值,无法知道提交的任务是否被线程池执行,而submit()方法提交任务会返回Future类型的对象,并且可以通过它的get()方法判断任务是否成功执行,但是这个方法会阻塞当前线程直到任务完成

第三步:关闭线程池
线程池的关闭可以通过调用线程池的shutdownNow()或者shutdown()方法,但是shutdown()是直接interrupt所有线程没有正在执行任务的线程,不再接受新的任务,它会等待所有正在执行的任务执行完毕,一般都是调用这个方法;而shutdownNow()会首先尝试将线程池的状态设置为STOP,然后尝试停止所有正在执行或者暂停的任务,并返回等待执行的任务列表;
【注意】性质不同的任务应当用不同规模的线程池处理,CPU密集的任务应该配置尽可能小的线程,通过Runtime.getRuntime().availableProcessors()可以获取当前设备的CPU个数。

好啦,来看个小例子:
注意这里最最好存在12个任务对不对,池中10个,工作队列中12个

package com.hhu.threads;

import java.util.concurrent.*;

/**
 * 测试线程池
 *
 * @author WeiguoLiu
 * @date 2018/1/15 16:06
 */
public class ThreadPoolTest {

    public static void main(String[] args) {
        //获取当前设备中CPU的个数
        System.out.println("CPU: " + Runtime.getRuntime().availableProcessors());
        //创建线程池,核心池为5个线程,最大线程数为10,线程最大空闲存活时间3秒,工作队列是基于数组的有界队列2个元素,饱和策略选择直接抛弃超出的任务,不予处理
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue(2), new ThreadPoolExecutor.DiscardPolicy());
        //创建线程池的另一种方式,面向接口编程
        ExecutorService threadPool2 = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue(2), new ThreadPoolExecutor.DiscardPolicy());

        //这里提交20个线程做测试
        for (int i = 0; i < 20; i++) {
            System.out.println("需要执行: " + threadPool.getTaskCount() + "; 已完成: " + threadPool.getCompletedTaskCount() + "; 池中存在: " +
            threadPool.getPoolSize());
            threadPool.execute(new A());
            System.out.println();
        }

//        threadPool.shutdown();
    }

    static class A implements Runnable {
        @Override
        public void run() {
            System.out.println("The thread has started: " + Thread.currentThread().getName() );
            try {
                Thread.currentThread().sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

输出的主要内容如下:

"D:\Program Files\Java\jdk1.8.0_101\bin\java" "-javaagent:D:\Program Files (x86)\ideaIU-2017.3.2\lib\idea_rt.jar=8374:D:\Program Files (x86)\ideaIU-2017.3.2\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_101\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar;D:\我的文档\IdeaWorkSpace\JavaProject\target\classes;D:\Mavenrepository\repository\junit\junit\4.12\junit-4.12.jar;D:\Mavenrepository\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\Mavenrepository\repository\org\springframework\spring-core\4.3.7.RELEASE\spring-core-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\Mavenrepository\repository\org\springframework\spring-web\4.3.7.RELEASE\spring-web-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-aop\4.3.7.RELEASE\spring-aop-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-beans\4.3.7.RELEASE\spring-beans-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-context\4.3.7.RELEASE\spring-context-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-webmvc\4.3.7.RELEASE\spring-webmvc-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-expression\4.3.7.RELEASE\spring-expression-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\springframework\spring-websocket\4.3.7.RELEASE\spring-websocket-4.3.7.RELEASE.jar;D:\Mavenrepository\repository\org\apache\poi\poi\3.16\poi-3.16.jar;D:\Mavenrepository\repository\commons-codec\commons-codec\1.10\commons-codec-1.10.jar;D:\Mavenrepository\repository\org\apache\commons\commons-collections4\4.1\commons-collections4-4.1.jar;D:\Mavenrepository\repository\com\baidu\aip\java-sdk\3.4.1\java-sdk-3.4.1.jar;D:\Mavenrepository\repository\org\json\json\20160810\json-20160810.jar;D:\Mavenrepository\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar;D:\Mavenrepository\repository\mysql\mysql-connector-java\5.1.37\mysql-connector-java-5.1.37.jar" com.hhu.threads.ThreadPoolTest
CPU: 4
需要执行: 0; 已完成: 0; 池中存在: 0
需要执行: 1; 已完成: 0; 池中存在: 1
需要执行: 2; 已完成: 0; 池中存在: 2
The thread has started: pool-1-thread-1
需要执行: 3; 已完成: 0; 池中存在: 3
需要执行: 4; 已完成: 0; 池中存在: 4
The thread has started: pool-1-thread-2
需要执行: 5; 已完成: 0; 池中存在: 5
需要执行: 6; 已完成: 0; 池中存在: 5
需要执行: 7; 已完成: 0; 池中存在: 5
需要执行: 8; 已完成: 0; 池中存在: 6
需要执行: 9; 已完成: 0; 池中存在: 7
需要执行: 10; 已完成: 0; 池中存在: 8
需要执行: 11; 已完成: 0; 池中存在: 9
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
需要执行: 12; 已完成: 0; 池中存在: 10
The thread has started: pool-1-thread-3
The thread has started: pool-1-thread-4
The thread has started: pool-1-thread-5
The thread has started: pool-1-thread-6
The thread has started: pool-1-thread-7
The thread has started: pool-1-thread-8
The thread has started: pool-1-thread-9
The thread has started: pool-1-thread-10
The thread has started: pool-1-thread-5
The thread has started: pool-1-thread-4

Process finished with exit code 0

第八章 Executor框架

Executor框架主要包含了:ThreadPoolExecutor、ScheduledThreadPoolExcutor、Future接口、Runnable接口、Callable接口和Executors。

  1. ThreadPoolExecutor
    ThreadPoolExecutor是Executor框架的核心,通常使用工厂类Executors来创建,Executors可以创建三种类型的ThreadPoolExecutor:
    单线程化线程池SingleThreadPool、定长线程池FixedThreadPool、可缓存线程池CachedThreadPool。

  2. ScheduledThreadPoolExcutor
    ScheduledThreadPoolExcutor继承自ThreadPoolExecutor,通常使用工厂类Executors来创建,Executors可以创建2种类型的ScheduledThreadPoolExcutor:
    ScheduledThreadPoolExcutor:包含若干个线程的ScheduledThreadPoolExcutor
    SingleThreadPoolExcutor:只包含一个线程的ScheduledThreadPoolExcutor

  3. Future接口
    Future接口和实现Future接口的FutureTask类(该类也实现了Runnable接口)用来表示异步计算的结果,当向线程池使用submit提交任务时就是返回的一个FutureTask对象

4.Runnable接口和Callable接口
Runnbale接口和Callable接口的实现类都可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行,主要是执行Runnable接口不会返回结果,而执行Callable接口是可以返回结果的,当然除了自己去实现Callable接口外,Executor类也提供了把Runnable封装为Callable:

    public static Callable<Object> callable(Runnable task) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<Object>(task, null);
    }
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
智慧旅游解决方案利用云计算、物联网和移动互联网技术,通过便携终端设备,实现对旅游资源、经济、活动和旅游者信息的智能感知和发布。这种技术的应用旨在提升游客在旅游各个环节的体验,使他们能够轻松获取信息、规划行程、预订票务和安排食宿。智慧旅游平台为旅游管理部门、企业和游客提供服务,包括政策发布、行政管理、景区安全、游客流量统计分析、投诉反馈等。此外,平台还提供广告促销、库存信息、景点介绍、电子门票、社交互动等功能。 智慧旅游的建设规划得到了国家政策的支持,如《国家中长期科技发展规划纲要》和国务院的《关于加快发展旅游业的意见》,这些政策强调了旅游信息服务平台的建设和信息化服务的重要性。随着技术的成熟和政策环境的优化,智慧旅游的时机已经到来。 智慧旅游平台采用SaaS、PaaS和IaaS等云服务模式,提供简化的软件开发、测试和部署环境,实现资源的按需配置和快速部署。这些服务模式支持旅游企业、消费者和管理部门开发高性能、高可扩展的应用服务。平台还整合了旅游信息资源,提供了丰富的旅游产品创意平台和统一的旅游综合信息库。 智慧旅游融合应用面向游客和景区景点主管机构,提供无线城市门户、智能导游、智能门票及优惠券、景区综合安防、车辆及停车场管理等服务。这些应用通过物联网和云计算技术,实现了旅游服务的智能化、个性化和协同化,提高了旅游服务的自由度和信息共享的动态性。 智慧旅游的发展标志着旅游信息化建设的智能化和应用多样化趋势,多种技术和应用交叉渗透至旅游行业的各个方面,预示着全面的智慧旅游时代已经到来。智慧旅游不仅提升了游客的旅游体验,也为旅游管理和服务提供了高效的技术支持。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值