一、 线程与进程
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
二、 并发和并行
并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行
三、创建线程的方式
1.继承Thread类,重写run方法。
public class MyThread extends Thread {
public MyThread() {
}
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
public static void main(String[] args) {
MyThread mThread1=new MyThread();
MyThread mThread2=new MyThread();
MyThread myThread3=new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
2.实现Runnable接口,重写run方法。
public class MyThread implements Runnable{
public static int count=20;
public void run() {
while(count>0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
}
}
public static void main(String[] args) {
MyThread Thread1=new MyThread();
Thread mThread1=new Thread(Thread1,"线程1");
Thread mThread2=new Thread(Thread1,"线程2");
Thread mThread3=new Thread(Thread1,"线程3");
mThread1.start();
mThread2.start();
myThread3.start();
}
}
3.实现Callable接口,重写带返回值的call方法,需使用Future。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<String> {
private int count = 20;
@Override
public String call() throws Exception {
for (int i = count; i > 0; i--) {
// Thread.yield();
System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
}
return "sale out";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> callable =new MyThread();
FutureTask <String>futureTask=new FutureTask<>(callable);
Thread mThread=new Thread(futureTask);
Thread mThread2=new Thread(futureTask);
Thread mThread3=new Thread(futureTask);
// mThread.setName("hhh");
mThread.start();
mThread2.start();
mThread3.start();
System.out.println(futureTask.get());
}
}
4.线程池创建线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService ex=Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
ex.shutdown();
}
}
四、 Thread类
1.Thread类的实例方法
start():启动一个线程,使其变为准备就绪状态。
run():线程获得CPU资源,即变为运行状态,执行run方法中的逻辑。
isAlive():判断线程是否存活。
getId():返回线程的标识符,线程ID是正值,线程ID在生命周期内不会变化,当线程终止了,线程ID可能会被重用。
getName():返回线程名称。
getPriority()和setPriority(int):返回优先级和设置优先级,优先级越高的线程获取CPU时间片的概率越高。
isDaemon()和setDaemon(boolean):isDaemon方法判断是否是守护线程;setDaemon设置守护线程;在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
我们自定义的线程和main线程都是用户线程,我们熟知的GC(垃圾回收器)就是守护线程。守护线程是用户线程的“奴仆”,当用户线程执行完毕,守护线程就会终止,因为它没有存在的必要了。
interrupt():中断标志,并不会进入中断状态。
isInterrupted():判断线程是否被中断。
join():等待这个线程死亡,举例说明:线程A执行join方法,会阻塞线程B,线程A join方法执行完毕,才能执行线程B。
2.Thread类的静态方法
currentThread():返回当前正在执行线程的引用。
sleep(long):让当前线程沉睡若干毫秒,不释放锁对象。与Object中的wait方法区别在于wait方法释放锁对象。
yield():当前线程放弃CPU的使用权,这里的放弃是指当前线程少用CPU资源,最后线程还是会执行完成。
interrupted():测试当前线程是否已经中断。线程的中断状态 由该方法清除。
五、 线程的生命周期
线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建:就是刚使用new方法,new出来的线程;
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源。
六、 java内存模型
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
七、 volatile关键字
1.不保证原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
2.内存可见性
指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
3.禁止指令重排
重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。
重排序需要遵守一定规则:
a.重排序操作不会对存在数据依赖关系的操作进行重排序。
比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
b.重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。
比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=2)由于不存在数据依赖关系, 所以可能会发生重排序,但是c=a+b这个操作是不会被重排序的,因为需要保证最终的结果一定是c=a+b=3。
使用volatile关键字修饰共享变量便可以禁止这种重排序。若用volatile修饰共享变量,在编译时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序,volatile禁止指令重排序也有一些规则:
a.当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
b.在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
即执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。
volatile原理
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
I. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
II. 它会强制将对缓存的修改操作立即写入主存;
III. 如果是写操作,它会导致其他CPU中对应的缓存行无效。
八、 happens-before规则
字面意思先行发生,前一个操作结果对后一个操作可见,解决编译器指令优化产生可见性问题,是编译器优化的约束。
八大规则:
程序次序规则:在一个线程内一段代码的执行结果是有序的。就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!
管程锁定规则:就是无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)
volatile变量规则:就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
线程终止规则:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
传递规则:这个简单的,就是happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C。
对象终结规则:这个也简单的,就是一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。
九、 原子类
我们知道volatile不保证原子性,如何保证原子性呢,我们可以使用JUC包下的原子类,或者使用synchronized和ReentrantLock。下面我们重点讲解原子类。
基本类型的原子类
AtomicInteger:整型原子类
AtomicLong:长整型原子类
AtomicBoolean :布尔型原子类
基于Unsafe类,采用CAS算法,以及自旋实现。
CAS
对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS比较与交换的伪代码可以表示为:
do{
备份旧数据;
基于旧数据构造新数据;
}while(!CAS( 内存地址,备份的旧数据,新数据 ))
引用类型的原子类
基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类。
AtomicReference:引用类型原子类
AtomicStampedReference:原子更新引用类型里的字段原子类
AtomicMarkableReference :原子更新带有标记位的引用类型
AtomicStampedReference、AtomicMarkableReference 带“标志”控制,解决ABA问题。
十、 synchronized关键字
synchronized关键字用于修饰方法和同步代码块。加锁机制,只允许一个线程执行方法或代码块中的逻辑,其它未获取锁的线程只能阻塞或者自旋。
实现原理:
synchronized 关键字底层原理属于 JVM 层面。
① synchronized 同步语句块的情况
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
② synchronized 修饰方法的的情况
public class SynchronizedDemo2 {
public synchronized void method() {
System.out.println("synchronized 方法");
}
}
synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
线程八锁
转载至:线程八锁
package com.java.juc;
/**
* 题目:判断打印 "one" or "two"
*
* 1.两个普通同步方法,两个线程 ,标准打印,打印?// one two
* 2.新增Thread.sleep(3000) 给getOne() 打印? // 3s 后打印 one two
* 3.新增普通方法 getThreee 打印?// 先打印three 三秒后打印 one two
* 4.两个普通同步方法,两个number对象,打印? // two 3s后打印 one
* 5.修改getOne()为静态同步方法,一个number对象,打印? // two 3s后打印 one
* 6.修改两个方法均为静态同步方法,一个number对象,打印?// 3s 后打印 one two
* 7.修改getOne()为静态同步方法,getTwo()为非静态同步方法 ,两个number,一个调用one,一个调用two //two 3s后打印 one
* 8.两个都改为静态同步方法,两个number 一个调用getOne(),一个调用getTwo() //3s 后打印 one two
* @author Administrator
*
*/
public class TestThread8Monitor {
public static void main(String[] args) {
final Number number = new Number();
final Number number2 = new Number();
new Thread(new Runnable(){
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
number2.getTwo();
}
}).start();
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo(){
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
某一时刻只有一个对象持有锁,不管有多少个方法,其他线程都无法持有锁
线程八锁的关键:
1.非静态方法的锁默认为this,静态方法的锁为对应的Class 实例(类的字节码)。
2.某一时刻内,只能有一个线程持有锁,无论几个方法。
十一、JUC
在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等;
1.ReentrantLock
基于AQS和CAS实现。是一种可重入锁,支持公平锁、非公平锁,可响应中断,支持多条件。
以下是ReentrantLock体系的类图:
AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,如果当前线程竞争锁失败,那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
AQS队列内部维护的是一个FIFO的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个Node其实是由线程封装,当线程争抢锁失败后会封装成Node加入到ASQ队列中去。
2.ReentrantLock与synchronized的区别
1.API层面上:ReentrantLock是一个类;synchronized是一个关键字,用于修饰代码、同步代码块。
2.实现:synchronized实现基于JVM层面的,同步代码块采用monitor控制指令执行,同步方法采用ACC_SYNCHRONIZED标识;ReentrantLock基于AQS和CAS实现。
3.synchronized和ReentrantLock都是可重入锁。
4.synchronized是非公平锁,ReentrantLock支持公平和非公平锁,默认是非公平锁。
5.线程中断响应:ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过Lock#lockInterruptibly申请锁,等待锁期间可被其他线程使用Thread#interrupt中断当前线程。synchronized不可中断响应。
6.线程分组唤醒:有些场景下,我们可能不希望唤醒所有的线程,而是唤醒部分线程。这种方式在synchronized下是无法实现的。但是,ReentrantLock通过提供一个Contition类,可以同时绑定多个对象,以此,来实现线程的分组唤醒。
7.效率:人们很容易被大众化的观点所误导,认为synchronized的效率会比ReentrantLock差很多。但是事实上,synchronized在JDK的发展过程中,经过了不断优化,比如引入了偏向锁,轻量级锁,锁升级机制等,目前,已经和ReentrantLock的效率相差不多了。如果没有特殊的场景,推荐使用synchronized,因为它使用起来比较简单,且不会造成死锁。
3.ReentrantLock使用
简单的买票程序:
package thread;
import java.util.concurrent.locks.ReentrantLock;
/**
* ClassName:TestLock
* Package:thread
* Description:
*
* @date:2020/3/15 17:59
* @auther:zh
*/
class SaleTicket implements Runnable {
ReentrantLock lock = new ReentrantLock();
int tickets = 100;
public void run() {
while (tickets > 0) {
sale();
}
}
public void sale() {
lock.lock();
try {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖第"
+ (100 - tickets + 1) + "张票"); //打印第几个线程正在执行
tickets--;
}
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class TestSaleTicket {
public static void main(String[] args) {
SaleTicket st = new SaleTicket();
Thread t1=new Thread(st, "一号窗口");
Thread t2=new Thread(st, "二号窗口");
Thread t3=new Thread(st, "三号窗口");
Thread t4=new Thread(st, "四号窗口");
Thread t5=new Thread(st, "五号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
4.生产者消费者不同实现
1.synchronized和Object类中的notify/notifyAll与wait实现。
package thread;
/**
* ClassName:Pro_Com
* Package:thread
* Description:
*
* @date:2020/3/15 18:54
* @auther:zh
*/
public class Pro_Com {
public static void main(String[] args) {
Resource resource = new Resource();
Thread p1 = new Thread(new Productor(resource),"生产者1");
Thread p2 = new Thread(new Productor(resource),"生产者2");
Thread p3 = new Thread(new Productor(resource),"生产者3");
Thread c1 = new Thread(new Customer(resource),"消费者1");
Thread c2 = new Thread(new Customer(resource),"消费者2");
Thread c3 = new Thread(new Customer(resource),"消费者3");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
class Resource{
private int num = 0;
private int max = 10;
public synchronized void product(){
if(num<max){
System.out.println(Thread.currentThread().getName()+"生产第"+(++num)+"个物品");
notifyAll();
}else {
try {
System.out.println("满了");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void custom(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"消费第"+(num--)+"个物品");
notifyAll();
}else {
try {
System.out.println("没了");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor implements Runnable{
private Resource resource;
public Productor(Resource resource){
this.resource = resource;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.product();
}
}
}
class Customer implements Runnable{
private Resource resource;
public Customer(Resource resource){
this.resource = resource;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.custom();
}
}
}
2.ReentrantLock和Condition实现
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* ClassName:Pro_Com
* Package:thread
* Description:
*
* @date:2020/3/15 18:54
* @auther:zh
*/
public class Pro_Com {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition cusCondition = lock.newCondition();
Condition proCondition = lock.newCondition();
Resource resource = new Resource(lock,cusCondition,proCondition);
Thread p1 = new Thread(new Productor(resource),"生产者1");
Thread p2 = new Thread(new Productor(resource),"生产者2");
Thread p3 = new Thread(new Productor(resource),"生产者3");
Thread c1 = new Thread(new Customer(resource),"消费者1");
Thread c2 = new Thread(new Customer(resource),"消费者2");
Thread c3 = new Thread(new Customer(resource),"消费者3");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
class Resource{
private int num = 0;
private int max = 10;
private ReentrantLock lock;
private Condition cusCondition;
private Condition proCondition;
public Resource(ReentrantLock lock,Condition cusCondition,Condition proCondition){
this.lock = lock;
this.cusCondition = cusCondition;
this.proCondition = proCondition;
}
public void product(){
lock.lock();
try{
if(num<max){
System.out.println(Thread.currentThread().getName()+"生产第"+(++num)+"个物品");
cusCondition.signalAll();
}else {
System.out.println("满了");
proCondition.await();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized void customer(){
lock.lock();
try{
if(num>0){
System.out.println(Thread.currentThread().getName()+"消费第"+(num--)+"个物品");
proCondition.signalAll();
}else {
System.out.println("没了");
cusCondition.await();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class Productor implements Runnable{
private Resource resource;
public Productor(Resource resource){
this.resource = resource;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.product();
}
}
}
class Customer implements Runnable{
private Resource resource;
public Customer(Resource resource){
this.resource = resource;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.customer();
}
}
}
阻塞队列实现:
package thread;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
/**
* ClassName:Pro_Com_Queue
* Package:thread
* Description:
*
* @date:2020/3/15 20:30
* @auther:zh
*/
public class Pro_Com_Queue {
public static void main(String[] args) {
BlockingDeque blockingDeque = new LinkedBlockingDeque(10);
Resource1 resource1 = new Resource1(blockingDeque);
new Thread(new Productor1(resource1),"生产者1").start();
// new Thread(new Productor1(resource1),"生产者2").start();
// new Thread(new Productor1(resource1),"生产者3").start();
new Thread(new Customer1(resource1),"消费者1").start();
new Thread(new Customer1(resource1),"消费者2").start();
new Thread(new Customer1(resource1),"消费者3").start();
}
}
class Resource1{
private BlockingDeque blockingDeque;
public Resource1(BlockingDeque blockingDeque){
this.blockingDeque = blockingDeque;
}
public void put(){
try {
blockingDeque.put(1);
System.out.println(Thread.currentThread().getName()+"生产一个资源,现在有"+blockingDeque.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void remove(){
try {
blockingDeque.take();
System.out.println(Thread.currentThread().getName()+"消费了一个资源,现在还有"+blockingDeque.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Productor1 implements Runnable{
private Resource1 resource1;
public Productor1(Resource1 resource1){
this.resource1 = resource1;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource1.put();
}
}
}
class Customer1 implements Runnable{
private Resource1 resource1;
public Customer1(Resource1 resource1){
this.resource1 = resource1;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource1.remove();
}
}
}
5.线程按续交替打印ABC
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* ClassName:OrderABC
* Package:thread
* Description:
*
* @date:2020/3/16 12:13
* @auther:zh
*/
public class OrderABC {
private static Lock lock = new ReentrantLock();
private static Condition A = lock.newCondition();
private static Condition B = lock.newCondition();
private static Condition C = lock.newCondition();
private static int count = 0;
static class ThreadA implements Runnable{
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
while (count%3!=0)
A.await();
System.out.print("A");
count++;
B.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
static class ThreadB implements Runnable{
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
while (count%3!=1)
B.await();
System.out.print("B");
count++;
C.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
static class ThreadC implements Runnable{
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
while (count%3!=2)
C.await();
System.out.print("C");
count++;
A.signal();
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
new Thread(new ThreadC()).start();
}
}
6.ConcurrentHashMap
jdk1.7:采用分段锁,维护了一个个的长度为16的segment,每一个segment基于数组和链表实现,并发修改时为每一个segment上锁,可以分段读写,提高了效率。
jdk1.8:摒弃了分段锁,数据结构采用HashMap的数组、链表、红黑树,采用CAS和synchronized实现。不是链表类型,CAS插入,链表类型,用链表的头节点作为锁对象,减小了锁粒度。
7.闭锁(CountDownLatch)
计数递减,一个线程需要等待其他线程执行完即计数为零时,才能继续向下执行,否则一直阻塞。
package thread;
import java.util.concurrent.CountDownLatch;
/**
* ClassName:CountDownLatchTest
* Package:thread
* Description:
*
* @date:2020/3/16 15:23
* @auther:zh
*/
public class CountDownLatchTest {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程A");
}
latch.countDown();
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程B");
}
latch.countDown();
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程C");
}
latch.countDown();
}).start();
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程结束");
}
}
8.CyclicBarrier
一个线程组的线程需要等待所有线程完成任务后再继续执行下一次任务。
public class CyclicBarrierDemo {
static class TaskThread extends Thread {
CyclicBarrier barrier;
public TaskThread(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(getName() + " 到达栅栏 A");
barrier.await();
System.out.println(getName() + " 冲破栅栏 A");
Thread.sleep(2000);
System.out.println(getName() + " 到达栅栏 B");
barrier.await();
System.out.println(getName() + " 冲破栅栏 B");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int threadNum = 5;
CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 完成最后任务");
}
});
for(int i = 0; i < threadNum; i++) {
new TaskThread(barrier).start();
}
}
}
所有线程会等待全部线程到达栅栏之后才会继续执行,并且最后到达的线程会完成 Runnable 的任务。
9.信号量( Semaphore)
semaphore 这个类是用作访问并发控制,可以设置资源最大同时访问的个数。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore = new Semaphore(3);//资源最多可被3个线程并发访问
for(int i = 0;i < 20;i++){
final int threadnum = i;
executorService.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("current thread"+Thread.currentThread().getName());
semaphore.acquire(1);//获取许可
test(threadnum);
semaphore.release(1);//释放许可
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
executorService.shutdown();//如果不shutdown工程不会结束
}
private static void test(int threadNum) throws Exception {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss");
System.out.println(simpleDateFormat.format(new Date())+" method run "+Thread.currentThread().getName());
Thread.sleep(1000);
}
10.实现 Callable 接口
上述已经讲到了,不再重复
11.读写锁(ReentrantReadWriteLock )
读操作的锁叫共享锁,写操作的锁叫排他锁。就是遇见写锁就需互斥。那么以此可得出读读共享,写写互斥,读写互斥,写读互斥。
12.写时复制(CopyOnWriteArrayList)
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
13.Fork/Join
fork/join大体的执行过程,先把一个大任务分解(fork)成许多个独立的小任务,然后起多线程并行去处理这些小任务。处理完得到结果后再进行合并(join)就得到我们的最终结果。显而易见的这个框架是借助了现代计算机多核的优势并行去处理数据。这看起来好像没有什么特别之处,这个套路很多人都会,并且工作中也会经常运用~。其实fork/join的最特别之处在于它还运用了一种叫work-stealing(工作窃取)的算法,这种算法的设计思路在于把分解出来的小任务放在多个双端队列中,而线程在队列的头和尾部都可获取任务。当有线程把当前负责队列的任务处理完之后,它还可以从那些还没有处理完的队列的尾部窃取任务来处理,这连线程的空余时间也充分利用了!