嘉兴企业网站制作,企业网站改版seo,东莞路桥统缴,为什么网站不见了一#xff0c;基础概念
1.1什么是CPU
中央处理器#xff08;CPU#xff09;#xff0c;是电子计算机的主要设备之一#xff0c;电脑中的核心配件。其功能主要是解释计算机指令以及处理计算机软件中的数据。CPU是计算机中负责读取指令#xff0c;对指令译码并执行指令的…一基础概念
1.1什么是CPU
中央处理器CPU是电子计算机的主要设备之一电脑中的核心配件。其功能主要是解释计算机指令以及处理计算机软件中的数据。CPU是计算机中负责读取指令对指令译码并执行指令的核心部件。中央处理器主要包括两个部分即控制器、运算器其中还包括高速缓冲存储器及实现它们之间联系的数据、控制的总线。电子计算机三大核心部件就是CPU、内部存储器、输入/输出设备。中央处理器的功效主要为处理指令、执行操作、控制时间、处理数据。
1.2CPU核心数和线程数的关系
多核心:也指单芯片多处理器( Chip Multiprocessors,简称CMP),CMP是由美国斯坦福大学提出的,其思想是将大规模并行处理器中的SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个CPU同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理
多线程: Simultaneous Multithreading.简称SMT.SMT可通过复制处理器上的结构状态,让同一个处理器上的多个线程同步执行并共享处理器的执行资源可最大限度地实现宽发射、乱序的超标量处理,提高处理器运算部件的利用率,缓和由于数据相关或 Cache未命中带来的访问内存延时。
核心数、线程数:目前主流CPU有双核、三核和四核,六核也在2010年发布。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1对应关系,也就是说四核CPU一般拥有四个线程。但 Intel引入超线程技术后,使核心数与线程数形成1:2的关系
1.3CPU时间片轮转机制
我们平时在开发的时候感觉并没有受cpu核心数的限制想启动线程就启动线程哪怕是在单核CPU上为什么这是因为操作系统提供了一种CPU时间片轮转机制。
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
我们平时在开发的时候感觉并没有受cpu核心数的限制想启动线程就启动线程哪怕是在单核CPU上为什么这是因为操作系统提供了一种CPU时间片轮转机制。
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾
时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要一定时间的,包括保存和装入寄存器值及内存映像,更新各种表格和队列等。假如进程切换( processwitch),有时称为上下文切换( context switch),需要5ms,再假设时间片设为20ms,则在做完20ms有用的工作之后,CPU将花费5ms来进行进程切换。CPU时间的20%被浪费在了管理开销上了。
为了提高CPU效率,我们可以将时间片设为5000ms。这时浪费的时间只有0.1%。但考虑到在一个分时系统中,如果有10个交互用户几乎同时按下回车键,将发生什么情况?假设所有其他进程都用足它们的时间片的话,最后一个不幸的进程不得不等待5s才获得运行机会。多数用户无法忍受一条简短命令要5才能做出响应,同样的问题在一台支持多道程序的个人计算机上也会发生
结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了CPU效率:而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100ms通常是一个比较合理的折衷。
1.4什么是进程和线程
进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。
线程是CPU调度的最小单位必须依赖于进程而存在
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
1.5并行和并发
并发:指应用能够交替执行不同的任务,比如单CPU核心下执行多线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不断去切换这两个任务,已达到同时执行效果,其实并不是的,只是计算机的速度太快,我们无法察觉到而已.
并行:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行
两者区别:一个是交替执行,一个是同时执行.
1.6高并发编程的意义
由于多核多线程的CPU的诞生,多线程、高并发的编程越来越受重视和关注。多线程可以给程序带来如下好处。
(1)充分利用CPU的资源
从上面的CPU的介绍,可以看的出来,现在市面上没有CPU的内核不使用多线程并发机制的,特别是服务器还不止一个CPU,如果还是使用单线程的技术做思路,明显就out了。因为程序的基本调度单元是线程,并且一个线程也只能在一个CPU的一个核的一个线程跑,如果你是个i3的CPU的话,最差也是双核心4线程的运算能力:如果是一个线程的程序的话,那是要浪费3/4的CPU性能:如果设计一个多线程的程序的话,那它就可以同时在多个CPU的多个核的多个线程上跑,可以充分地利用CPU,减少CPU的空闲时间,发挥它的运算能力,提高并发量。
就像我们平时坐地铁一样,很多人坐长线地铁的时候都在认真看书,而不是为了坐地铁而坐地铁,到家了再去看书,这样你的时间就相当于有了两倍。这就是为什么有些人时间很充裕,而有些人老是说没时间的一个原因,工作也是这样,有的时候可以并发地去做几件事情,充分利用我们的时间,CPU也是一样,也要充分利用。
(2)加快响应用户的时间
比如我们经常用的迅雷下载,都喜欢多开几个线程去下载,谁都不愿意用一个线程去下载,为什么呢?答案很简单,就是多个线程下载快啊。
我们在做程序开发的时候更应该如此,特别是我们做互联网项目,网页的响应时间若提升1s,如果流量大的话,就能增加不少转换量。做过高性能web前端调优的都知道,要将静态资源地址用两三个子域名去加载,为什么?因为每多一个子域名,浏览器在加载你的页面的时候就会多开几个线程去加载你的页面资源,提升网站的响应速度。多线程,高并发真的是无处不在。
(3)可以使你的代码模块化,异步化,简单化
例如我们在做 Android程序开发的时候,主线程的UI展示部分是一块主代码程序部分,但是UI上的按钮用相应事件的处理程序就可以做个单独的模块程序拿出来。这样既增加了异步的操,又使程序模块化,清晰化和简单化。
时下最流行的异步程序处理机制,正是多线程、并发程序最好的应用例子。
多线程应用开发的好处还有很多,大家在日后的代码编写过程中可以慢慢体会它的魅力。
二线程的启动方式
Java中的线程有两种启动方式
1类 extends Thread 类.start
public class YuanThread extends Thread{Overridepublic void run() {super.run();System.out.println(YuanThread run..);}
}
public class Test {public static void main(String[] args) {YuanThread yuanThread new YuanThread();yuanThread.start();}
}
输出YuanThread run..
2类 implements Runnable
public class YuanRunnable implements Runnable{Overridepublic void run() {System.out.println(YuanRunnable run..);}
}
public class Test {public static void main(String[] args) {YuanRunnable yuanRunnable new YuanRunnable();Thread thread new Thread(yuanRunnable);thread.start();}
}
输出YuanRunnable run..
有人会说还有一种启动方式
public class YuanCallable implements CallableString {Overridepublic String call() throws Exception {return YuanCallable;}
}
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {YuanCallable callable new YuanCallable();FutureTaskString futureTask new FutureTask(callable);Thread thread1 new Thread(futureTask);thread1.start();System.out.println(YuanCallablefutureTask.get());}
}
输出YuanCallableYuanCallable
那为什么说这不算是一种新的启动方式呢因为它和第二种方式其本质上是一样的。
我们看FutureTask的源码可以发现 它实现了RunnableFuture接口而RunnableFuture又实现了Runnable接口所以最终还是
new Thread(Runnable)的方式。 只不过Callable方式有返回值在获取结果时
Future.get()方法会阻塞直到返回结果。
三结束线程的方方式
第一种方式stop()方式 暴力停止线程在终结一个线程时不会保证线程的资源正常释放通常是没有给予线程完成资源释放工作的机会因此会导致程序可能工作在不确定状态下。
public class YuanThread extends Thread{Overridepublic void run() {super.run();while (true){System.out.println(YuanThread run..);}}
}
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {YuanThread yuanThread new YuanThread();yuanThread.start();Thread.sleep(3000);yuanThread.stop();}
}
输出YuanThread run.. 3s后停止
第二种方式interrupt() isInterrupt() 停止线程这种是最常用的方式根据自己的业务通过标志位结束线程
public class YuanThread extends Thread{Overridepublic void run() {super.run();while (!isInterrupted()){System.out.println(YuanThread run..);}}
}
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {YuanThread yuanThread new YuanThread();yuanThread.start();Thread.sleep(3000);yuanThread.interrupt();}
}
注意 当一个任务在sleep时去interrupt会包异常ava.lang.InterruptedException: sleep interrupted
public class YuanThread extends Thread{Overridepublic void run() {super.run();while (!isInterrupted()){try {sleep(6000);System.out.println(YuanThread run..);} catch (InterruptedException e) {System.out.println(YuanThread InterruptedException);throw new RuntimeException(e);}}}
}
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {YuanThread yuanThread new YuanThread();yuanThread.start();sleep(2000);yuanThread.interrupt();}
} 四join wait notify notifyAll
join放弃当前线程的执行并返回对应的线程
如果我想让三个线程 T1 ,T2,T3顺序执行怎么办
public class T1 extends Thread{Overridepublic void run() {super.run();for (int i 0; i 10; i) {System.out.println(T1:i);}}
}
public class T2 extends Thread{Overridepublic void run() {super.run();for (int i 0; i 10; i) {System.out.println(T2:i);}}
}
public class T3 extends Thread{Overridepublic void run() {super.run();for (int i 0; i 10; i) {System.out.println(T3:i);}}
} public class Test {public static void main(String[] args) {T1 t1 new T1();T2 t2 new T2();T3 t3 new T3();t1.start();t2.start();t3.start();}
} 输出
T2:0 T3:0 T1:0 T3:1 T2:1 T3:2 T1:1 T3:3 T2:2 T3:4 T1:2 T3:5 T2:3 T3:6 T1:3 T3:7 T2:4 T3:8 T1:4 T3:9 T2:5 T1:5 T2:6 T1:6 T2:7 T1:7 T2:8 T1:8 T2:9 T1:9
杂乱无章那如果使用join呢
public class Test {public static void main(String[] args) throws InterruptedException {T1 t1 new T1();T2 t2 new T2();T3 t3 new T3();t1.start();/**程序在main线程中调用t1线程的join方法则main线程放弃cpu控制权并返回t1线程继续执行直到线程t1执行完毕所以结果是t1线程执行完后才到主线程执行相当于在main线程中同步t1线程t1执行完了main线程才有执行的机会*/t1.join();t2.start();t2.join();t3.start();}
}
输出结果
T1:0 T1:1 T1:2 T1:3 T1:4 T1:5 T1:6 T1:7 T1:8 T1:9 T2:0 T2:1 T2:2 T2:3 T2:4 T2:5 T2:6 T2:7 T2:8 T2:9 T3:0 T3:1 T3:2 T3:3 T3:4 T3:5 T3:6 T3:7 T3:8 T3:9
wait():
1.使当前的线程进行等待 2.释放当前的锁 3.被唤醒时重新尝试获取这个锁
wait()结束等待的条件
1.其他线程也可以不是线程调用了该对象的notify或notifyAll方法 2.其他线程也可以不是线程调用该等待线程的interrupted方法 3.等待时间超时wait有参
wait是Object方法 有参数时wait500表示Time_waiting状态 无参数wait或wait0都表示无限等待:waiting状态
wait的用法
1.必须配合synchronized使用 2.且使用的必须为同一个对象synchronized (A)配合A.wait()使用 3.当线程执行到object.wait()时此线程会同时释放锁synchronized (object)当它结束了wait后此线程又会重新去争抢锁synchronized (object)。
public class YuanThread extends Thread{private Object object;YuanThread(Object o){object o;}Overridepublic void run() {synchronized (object){System.out.println(YuanThread wait before);try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(YuanThread wait after);}}
}
public class Test {public static void main(String[] args) {Object object new Object();YuanThread yuanThreadnew YuanThread(object);yuanThread.start();}
}
输出 YuanThread wait before
会一直等待 直到重新唤醒
notify 必须配合synchronized使用且在notify后当前线程不会马上释放锁要等到当前线程被synchronized修饰的代码执行完才会释放锁。
使用notify的对象为A不能唤醒B.wait的线程
notify会随机唤醒一个wait的线程
notifyAll 唤醒所有wait的线程让这些被唤醒的线程去争抢按争抢顺序依次执行
public class T1 extends Thread{private Object object;T1(Object o){object o;}Overridepublic void run() {synchronized (object){object.notify();}}
}
public class Test {public static void main(String[] args) throws InterruptedException {Object object new Object();YuanThread yuanThreadnew YuanThread(object);yuanThread.start();T1 t1 new T1(object);t1.start();}
} 输出
YuanThread wait before YuanThread wait after
注意1调用等待线程的interrupted方法会抛出异常然后继续执行wait后的代码
2当时间超时会自动恢复执行会重新去获得锁然后从wait的下一行开始执行
五线程的状态切换
线程的状态切换主要是下面这个图所示 当线程start的时候它并不会立即执行而是会进入一个就绪状态等待CPU时间片轮转到该任务时才会运行。就绪和运行中我们统称为运行态。
六锁
6.1对象锁与类锁
对象锁synchronized 修饰非静态的方法和synchronized(this)都是使用的对象锁一个系统可以有多个对象实例所以使用对象锁不是线程安全的除非保证一个系统该类型的对象只会创建一个通常使用单例模式才能保证线程安全;
public class YuanThread extends Thread{private Object object;YuanThread(Object o){object o;}Overridepublic void run() {synchronized (object){test1(object);}}public void test1(Object object){synchronized (object){System.out.println(dui xiang suo);}}
}
public class Test {public static void main(String[] args) throws InterruptedException {Object object new Object();YuanThread yuanThreadnew YuanThread(object);yuanThread.start();}
}
类锁锁是加持在类上的用synchronized static 或者synchronized(class)方法使用的锁都是类锁因为class和静态方法在系统中只会产生一份所以在单系统环境中使用类锁是线程安全的
public class YuanThread extends Thread{private Object object;YuanThread(Object o){object o;}Overridepublic void run() {test1();test2();}public static synchronized void test1(){System.out.println(lei suo 1);}public static synchronized void test2(){synchronized (YuanThread.class){System.out.println(lei suo 2);}}
}
6.2显示锁
前面提到的synchronized是内置锁是Java语言提供的一种基本锁机制当代码块使用synchronized关键字进行同步时会自动获取对象的内置锁执行完该代码块后会自动释放锁。
显示锁也称为可重入锁是Java语言提供的一种高级锁机制。与内置锁不同的是显示锁需要程序员手动获取和释放锁。
显示锁都是由java.util.concurrent.locks.Lock派生出来的我们看一下这个类的方法 再看一下他的实现类 以ReentrantLock为例我们看一下显示锁的使用
public class YuanThread extends Thread{private Lock lock;YuanThread(Lock lock){this.lock lock;}Overridepublic void run() {lock.lock();try {System.out.println(run lock);}finally {lock.unlock();}}}
public class Test {public static void main(String[] args) throws InterruptedException {Lock lock new ReentrantLock();YuanThread yuanThreadnew YuanThread(lock);yuanThread.start();}
}
上面在使用中如果我们忘记了在finally里面释放掉锁的话就很有可能会导致一些死锁的情况。
trylock就是尝试获取锁如果锁已经被其他线程占用那么立即返回false如果没有那么应该占用它并返回true表示拿到锁啦。
trylock方法里带了参数的这个参数的作用是指定一个时间表示在这个时间内一直尝试去获得锁如果到时间还没有拿到就放弃。
因为trylock对锁并不是一直阻塞等待的所以可以更多的规避死锁的发生。
lockInterruptibly是在线程获取锁时优先响应中断如果检测到中断抛出中断异常由上层代码去处理。这种情况下就为一种轮循的锁提供了退出机制。
再来看一下另外一个显示锁ReentrantReadWriteLock可重入的读-写锁
什么是读写锁呢比如一波数据大部分时候都是提供读取的而只有比较少量的写操作那么如果用互斥锁的话就会导致线程间的锁竞争。如果对于读取的时候大家都可以读一旦要写入的时候就再将某个资源锁住。这样的变化就很好的解决了这个问题使的读操作可以提高读的性能又不会影响写的操作。
下面来看一下使用
databean 数据类
public class DataBean implements Serializable {private String data;public DataBean(String data) {this.data data;}public String getData() {return data;}public void setData(String data) {this.data data;}
}
读写锁
public class YuanLock {private ReentrantReadWriteLock lock new ReentrantReadWriteLock();private Lock readLock lock.readLock();//读取锁private Lock writeLock lock.writeLock();//写入锁private DataBean bean;public YuanLock(DataBean bean) {this.bean bean;}public DataBean getData(){readLock.lock();try {System.out.println(-----readlock bean.getData());return bean;}finally {readLock.unlock();}}public void setData(String data){writeLock.lock();try {System.out.println(-----writelock :data);bean.setData(bean.getData()data);}finally {writeLock.unlock();}}}
读取线程
public class T1 extends Thread{private YuanLock lock;T1(YuanLock o){lock o;}Overridepublic void run() {for (int i 0; i 10; i) {//读取十次for (int j 0; j 5; j) {System.out.println(lock.getData());}}}
}
写入线程
public class T2 extends Thread{private YuanLock lock;T2(YuanLock o){lock o;}Overridepublic void run() {super.run();for (int i 0; i 5; i) {//写入五次lock.setData(i);}}
}
具体应用:
public class Test {public static void main(String[] args) throws InterruptedException {Lock lock new ReentrantLock();YuanThread yuanThreadnew YuanThread(lock);yuanThread.start();DataBean bean new DataBean(-);YuanLock yuanLock new YuanLock(bean);for (int i 0; i 2; i) {for (int j 0; j 5; j) {T1 t1 new T1(yuanLock);t1.start();}T2 t2 new T2(yuanLock);t2.start();}}
}
6.3可重入锁
什么是可重入锁前面我们说的synchronized 和 ReentrantLock 都是可重入锁。
可重入锁就是可以重复进入的锁也叫递归锁。前提是同一把锁如同一个类、同一个实例、同一个代码块。可重入锁指的是以线程为单位当一个线程获取对象锁之后这个线程可以再次获取本对象上的锁而其他的线程是不可以的。
可重入锁的意义之一在于防止死锁。
实现原理实现是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时认为锁是未被占有的线程请求一个未被占有的锁时JVM将记录锁的占有者并且将请求计数器置为1 。 如果同一个线程再次请求这个锁计数器将递增 每次占用线程退出同步块计数器值将递减。直到计数器为0,锁被释放。
synchronized 使用 public class T3 extends Thread{private Object object;public T3(Object object) {this.object object;}Overridepublic void run() {synchronized (object){System.out.println(first);synchronized (object){System.out.println(second);}}}
} public class Test {public static void main(String[] args) throws InterruptedException {T3 t3 new T3(new Object());t3.start();}
} lock使用 public class YuanThread extends Thread{private Lock lock;YuanThread(Lock lock){this.lock lock;}Overridepublic void run() {lock.lock();try {System.out.println(first);lock.lock();try {System.out.println(second);}finally {lock.unlock();}}finally {lock.unlock();}}
} public class Test {public static void main(String[] args) throws InterruptedException {Lock lock new ReentrantLock();YuanThread yuanThreadnew YuanThread(lock);yuanThread.start();}
}
6.4死锁
死锁就是两个或两个以上的线程持有不同系统资源的锁线程彼此都等待获取对方的锁来完成自己的任务但是没有让出自己持有的锁线程就会无休止等待下去。线程竞争的资源可以是锁、网络连接、通知事件磁盘、带宽以及一切可以被称作“资源”的东西
class DieLockThread extends Thread {private boolean flag;public DieLockThread(boolean flag) {this.flag flag;}Overridepublic void run() {int i 0;int j 0;if (flag) {while (true) {synchronized (Lock.LOCK1) // 使用第一把锁{synchronized (Lock.LOCK2) // 使用第二把锁{System.out.println(一一一一一一一一一一一一 i);}}}} else {while(true) {synchronized (Lock.LOCK2) // 使用第二把锁{synchronized (Lock.LOCK1) // 使用第一把锁{System.out.println(二二二二二二二二二二二二 j);}}}}}
}
class Lock {public final static Object LOCK1 new Object();public final static Object LOCK2 new Object();
}
public class DieLockDemo {public static void main(String[] args) {new DieLockThread(true).start();new DieLockThread(false).start();}}
七ThreadLocal
ThreadLocal 是线程本地变量。ThreadLocal可以让每个线程拥有一个属于自己的变量的副本不会和其他线程的变量副本冲突实现了线程的数据隔离。
下面看下使用
public class UseThreadLocal {static ThreadLocalInteger threadLocal new ThreadLocalInteger() {Overrideprotected Integer initialValue() {return 1;}};/*** 运行3个线程*/public void StartThreadArray() {Thread[] runs new Thread[3];for (int i 0; i runs.length; i) {runs[i] new Thread(new TestThread(i));}for (int i 0; i runs.length; i) {runs[i].start();}}public static void main(String[] args) {UseThreadLocal test new UseThreadLocal();test.StartThreadArray();}
}
public static class TestThread implements Runnable {int id;public TestThread(int id) {this.id id;}public void run() {System.out.println(Thread.currentThread().getName() :start);// 如果使用了 ThreadLocal 会单独Copy一份 到 当前线程 例如 Thread-0Integer s threadLocal.get();s s id;threadLocal.set(s); System.out.println(Thread.currentThread().getName() : threadLocal.get());}
}
八CAS
什么是CAS呢CAS是compare and swap的缩写中文翻译成比较并交换。
CAS 操作包含三个操作数 —— 内存位置V、预期原值A和新值(B)。 如果内存位置的值与预期原值相匹配那么处理器会自动将该位置值更新为新值 。否则处理器不做任何操作。
CAS的基本思路就是如果这个地址上的值和期望的值相等则给其赋予新值否则不做任何事儿但是要返回原值是多少。循环CAS就是在一个循环里不断的做cas操作直到成功为止。
下面这个图可以看出什么是CAS操作 CAS都是进行的原子操作那什么是原子操作呢 假定有两个操作A和B(A和B可能都很复杂)如果从执行A的线程来看当另一个线程执行B时要么将B全部执行完要么完全不执行B那么A和B对彼此来说是原子的。
实现原子操作可以使用锁锁机制满足基本的需求是没有问题的了但是有的时候我们的需求并非这么简单我们需要更有效更加灵活的机制synchronized关键字是基于阻塞的锁机制也就是说当一个线程拥有锁的时候访问同一资源的其它线程需要等待直到该线程释放锁这里会有些问题首先如果被阻塞的线程优先级很高很重要怎么办其次如果获得锁的线程一直不释放锁怎么办这种情况是非常糟糕的。还有一种情况如果有大量的线程来竞争资源那CPU将会花费大量的时间和资源来处理这些竞争同时还有可能出现一些例如死锁之类的情况最后其实锁机制是一种比较粗糙粒度比较大的机制相对于像计数器这样的需求有点儿过于笨重。
CAS实现原子操作的三大问题
1ABA问题
因为CAS需要在操作值的时候检查值有没有发生变化如果没有发生变化则更新但是如果一个值原来是A变成了B又变成了A那么使用CAS进行检查时会发现它的值没有发生变化但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加上版本号每次变量更新的时候把版本号加1那么A→B→A就会变成1A→2B→3A。举个通俗点的例子你倒了一杯水放桌子上干了点别的事然后同事把你水喝了又给你重新倒了一杯水你回来看水还在拿起来就喝如果你不管水中间被人喝过只关心水还在这就是ABA问题。
2循环时间长开销大
自旋CAS如果长时间不成功会给CPU带来非常大的执行开销。
3只能保证一个共享变量的原子操作
当对一个共享变量执行操作时我们可以使用循环CAS的方式来保证原子操作但是对多个共享变量操作时循环CAS就无法保证操作的原子性这个时候就可以用锁。
还有一个取巧的办法就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i2ja合并一下ij2a然后用CAS来操作ij。从Java 1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性就可以把多个变量放在一个对象里来进行CAS操作。
九原子操作类
9.1AtomicInteger
int addAndGetint delta以原子方式将输入的数值与实例中的值AtomicInteger里的value相加并返回结果。
boolean compareAndSetint expectint update如果输入的数值等于预期值则以原子方式将该值设置为输入的值。
int getAndIncrement()以原子方式将当前值加1注意这里返回的是自增前的值。
int getAndSetint newValue以原子方式设置为newValue的值并返回旧值。
9.2AtomicIntegerArray
主要是提供原子的方式更新数组里的整型其常用方法如下。
int addAndGetint iint delta以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSetint iint expectint update如果当前值等于预期值则以原子方式将数组位置i的元素设置成update值。
需要注意的是数组value通过构造方法传递进去然后AtomicIntegerArray会将当前数组复制一份所以当AtomicIntegerArray对内部的数组元素进行修改时不会影响传入的数组。
9.3AtomicReference
原子更新引用类型。
9.4AtomicStampedReference
利用版本戳的形式记录了每次改变以后的版本号这样的话就不会存在ABA问题了。这就是AtomicStampedReference的解决方案。AtomicMarkableReference跟AtomicStampedReference差不多 AtomicStampedReference是使用pair的int stamp作为计数器使用AtomicMarkableReference的pair使用的是boolean mark。 还是那个水的例子AtomicStampedReference可能关心的是动过几次AtomicMarkableReference关心的是有没有被人动过方法都比较简单。
9.5AtomicMarkableReference
原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReferenceV initialRefbooleaninitialMark)。
十阻塞队列
队列是一种特殊的线性表特殊之处在于它只允许在表的前端front进行删除操作而在表的后端rear进行插入操作和栈一样队列是一种操作受限制的线性表。进行插入操作的端称为队尾进行删除操作的端称为队头。
在队列中插入一个队列元素称为入队从队列中删除一个队列元素称为出队。因为队列只允许在一端插入在另一端删除所以只有最早进入队列的元素才能最先从队列中删除故队列又称为先进先出FIFO—first in first out线性表。
什么是阻塞队列
1支持阻塞的插入方法意思是当队列满时队列会阻塞插入元素的线程直到队列不满。
2支持阻塞的移除方法意思是在队列为空时获取元素的线程会等待队列变为非空。
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。
在线程世界里生产者就是生产数据的线程消费者就是消费数据的线程。在多线程开发中如果生产者处理速度很快而消费者处理速度很慢那么生产者就必须等待消费者处理完才能继续生产数据。同样的道理如果消费者的处理能力大于生产者那么消费者就必须等待生产者。
为了解决这种生产消费能力不均衡的问题便有了生产者和消费者模式。生产者和消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通信而是通过阻塞队列来进行通信所以生产者生产完数据之后不用等待消费者处理直接扔给阻塞队列消费者不找生产者要数据而是直接从阻塞队列里取阻塞队列就相当于一个缓冲区平衡了生产者和消费者的处理能力。
阻塞队列常用于生产者和消费者的场景生产者是向队列里添加元素的线程消费者是从队列里取元素的线程。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。
抛出异常当队列满时如果再往队列里插入元素会抛出IllegalStateExceptionQueuefull异常。当队列空时从队列里获取元素会抛出NoSuchElementException异常。
返回特殊值当往队列插入元素时会返回元素是否插入成功成功返回true。如果是移除方法则是从队列里取出一个元素如果没有则返回null。
一直阻塞当阻塞队列满时如果生产者线程往队列里put元素队列会一直阻塞生产者线程直到队列可用或者响应中断退出。当队列空时如果消费者线程从队列里take元素队列会阻塞住消费者线程直到队列不为空。
超时退出当阻塞队列满时如果生产者线程往队列里插入元素队列会阻塞生产者线程一段时间如果超过了指定的时间生产者线程就会退出。
常用的阻塞队列
①ArrayBlockingQueue一个由数组结构组成的有界阻塞队列。此队列按照先进先出FIFO的原则对元素进行排序。默认情况下不保证线程公平的访问队列所谓公平访问队列是指阻塞的线程可以按照阻塞的先后顺序访问队列即先阻塞线程先访问队列。非公平性是对先等待的线程是非公平的当队列可用时阻塞的线程都可以争夺访问队列的资格有可能先阻塞的线程最后才访问队列。初始化时有参数可以设置
②LinkedBlockingQueue一个由链表结构组成的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。
③PriorityBlockingQueue一个支持优先级排序的无界阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则或者初始化PriorityBlockingQueue时指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
④DelayQueue一个使用优先级队列实现的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
DelayQueue非常有用可以将DelayQueue运用在以下应用场景。
缓存系统的设计可以用DelayQueue保存缓存元素的有效期使用一个线程循环查询DelayQueue一旦能从DelayQueue中获取元素时表示缓存有效期到了。
⑤SynchronousQueue一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作否则不能继续添加元素。SynchronousQueue可以看成是一个传球手负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素非常适合传递性场景。
⑥LinkedTransferQueue一个由链表结构组成的无界阻塞队列。
多了tryTransfer和transfer方法
1transfer方法
如果当前有消费者正在等待接收元素消费者使用take()方法或带时间限制的poll()方法时transfer方法可以把生产者传入的元素立刻transfer传输给消费者。如果没有消费者在等待接收元素transfer方法会将元素存放在队列的tail节点并等到该元素被消费者消费了才返回。
2tryTransfer方法
tryTransfer方法是用来试探生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收方法立即返回而transfer方法是必须等到消费者消费了才返回。
⑦LinkedBlockingDeque一个由链表结构组成的双向阻塞队列。
所谓双向队列指的是可以从队列的两端插入和移出元素。双向队列因为多了一个操作队列的入口在多线程同时入队时也就减少了一半的竞争。
多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法以First单词结尾的方法表示插入、获取peek或移除双端队列的第一个元素。以Last单词结尾的方法表示插入、获取或移除双端队列的最后一个元素。另外插入方法add等同于addLast移除方法remove等效于removeFirst。但是take方法却等同于takeFirst不知道是不是JDK的bug使用时还是用带有First和Last后缀的方法更清楚。在初始化LinkedBlockingDeque时可以设置容量防止其过度膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中。
以上的阻塞队列都实现了BlockingQueue接口也都是线程安全的。