Java教程

Java并发编程

本文主要是介绍Java并发编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

并发/高并发

tomcat默认150并发连接(socket)

RT:相应时间

QPS:吞吐量

硬件

cpu,内存,磁盘,网络

软件

最大化使用硬件资源

线程数量、JVM内存大小、网络通信机制(BIO、NIO、AIO)、磁盘IO

线程数量如何提升服务端并发数量

什么是线程

马路车道

线程是cpu执行的最小调度单元

并行线程数量由cpu决定(cpu核心数或者核心数*2)

并发和并行

单核cpu也支持多线程 --> cpu时间片切换

并发:同时发生的请求连接

并行:同时处理执行的请求

多线程的特点

同步

需要阻塞等待其他线程完成处理

异步

不需要阻塞当前的处理

并行

多线程同时执行处理

Java中的线程

  • Runnable 借口
  • Thread 类
  • Callable/Future 带返回值的
public class ThreadDemo extends Thread {
  @Override
	public void run(){
  	//线程执行的指令
	}

  public static void main(String[] args){
    ThreadDemo t = new ThreadDemo();
    t.start();//启动线程
  }
}
public class CallableDemo implements Callable<String> {
  @Override
  public String call(){
    //call()等价于run(),线程执行的指令
    return null;
  }
  
  public static void main(String[] args){
    ExecutorService exe = Executors.newFixedThreadPool(1);
    CallableDemo c = new CallableDemo();
    Future<String> f = exe.submit(c);
    f.get();//get()方法是阻塞的
  }
}

如何使用Java线程

  • 网络请求分发
  • 文件导入
  • 短信发送

线程的基础

线程的生命周期

线程的启动 --> 结束

线程状态

阻塞

  • TIMED_WAITING
  • WAITING
  • BLOCKED
public class Demo {
  public static void main(String[] args){
    //阻塞状态 TIMED_WAITING
  	new Thread(() -> {
    	while(true){
      	try {
        	TimeUnit.SECONDS.sleep(100);
      	} catch (InterruptedException e){
        	e.printStackTrace();
      	}
    	}
  	}).start();
  
    //阻塞状态 WAITING
  	new Thread(() -> {
    	while(true) {
      	synchronized (Demo.class) {
          try {
            Demo.class.wait();
      		} catch (InterruptedException e){
        		e.printStackTrace();
      		}
        }
    	}
  	}).start();
    
    //抢占锁 TIMED_WAITING
    new Thread(new BlockedDemo()).start();
    
    //未抢占锁 BLOCKED
    new Thread(new BlockedDemo()).start();
	}
  
  static class BlockedDemo extends Thread {
    @Override
    public void run() {
      while(true) {
      	synchronized (Demo.class) {
          try {
            TimeUnit.SECONDS.sleep(100);
      		} catch (InterruptedException e){
        		e.printStackTrace();
      		}
        }
    	}
    }
  }
}

jps 查看当前线程的Pid

jstack pid 查看pid的堆栈信息

Java中的线程状态:6种

  • new (线程初始化)

  • runnable(cpu OS调度前为就绪状态)

  • waiting

    sleep(0);

    wait();

    join();

    LockSupport.parkUnitl();

    notify

    notifyAll

    unpark

  • timed_waiting

    sleep(long);

    wait(long);

    join(long);

    LockSupport.parkUnitl(xx);

    notify

    notifyAll

    unpark

  • blocked (锁阻塞状态)

  • 终止 (线程运行结束)

操作系统层面的线程状态:5种

没有new状态

线程的启动

new Thread().start();//启动线程

new Thread().run();//调用实例方法
  1. Thread.start方法调用本地方法start0,启动线程

  2. JVM通过本地方法start0调用不同系统层面的创建线程

  3. OS调用JVM的Thread.run方法(OS通过CPU调度算法调用CPU执行run方法指令)

  4. JVM调用Java中Thread.run

  5. 执行结束之后JVM销毁线程

线程终止

线程什么情况下终止

run方法执行结束

Thread.stop方法强制终止 ,不建议使用

发送终止信号通知终止,interrupt

Thread.currentThread().isInterrupted() 表示中断标记,默认是false

Thread.interrupt(); 设置interrupt = true

Thread.interrupted(); 复位,恢复interrupt = false

interrupt(); --> 通过一个共享变量实现线程之间的通信

设置一个共享变量的值 true 。volatile jint _interrupted;

唤醒处于阻塞状态的线程

线程安全

问题

public static int count = 0;
public static incr() {
  try {
    Thread.sleep(1);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  count ++;
}
public static void main(String[] args) {
  for(int i = 0; i < 1000; i ++) {
    new Thread(APP::incr).start();
  }
  try {
    Thread.sleep(1000);
  } catch(InterruptedException e) {
    e.printStackTrace();
  }
}

结果是小于等于1000的随机数

线程安全问题

  • 原子性
  • 有序性
  • 可见性

cpu时间片切换导致数据安全问题

问题出现 count ++,不是原子操作,编译成三个指令:

  1. 加载到寄存器
  2. 累加
  3. 写入内存

锁(Synchronized)

public class SynchronizedDemo {
  synchronized void demo1(){}//方法
  
  synchronized static void demo2(){}//方法
  
  Object o = new Object();
  void demo3() {
    synchronized(o) {
      //代码块
    }
  }
}
//锁的范围

实例锁/对象锁

锁的范围控制在对象实例内

SynchronizedDemo demo = new SynchronizedDemo();
new Thread(() -> {
  demo.demo1();
}).start();  
new Thread(() -> {
  demo.demo1();
}).start();
void demo() {
  synchronized(this) {
    //代码块
  }
}

类锁

静态方法,类对象,类锁

synchronized static void demo(){}

void demo() {
    synchronized(SynchronizedDemo.class) {
      //代码块
    }
  }

互斥锁的本质

共享资源

通过抢占共享资源实现排他性

锁的存储(对象头)

对象在堆中的布局 -> oop.hpp ->oopDesc

对象标记 markOop _mark

类元信息 markOop _mark

实例数据 _metadata

[对齐填充]

对象标记(markOop.hpp)

hashcode

分代年龄

同步锁标记

偏向锁标记

偏向锁持有者线程ID

monitor() -> ObjectMonitor 争抢锁的实现逻辑

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKaaTadW-1639183807603)(/Users/muprince/Library/Application Support/typora-user-images/image-20211124001751204.png)]

jol 查看类对象头信息布局的工具

-XX:-UseCompressedOops 关闭JVM对象头压缩指针

<dependency>
	<groupId>org.openjdk.jol</groupId>
	<artifactId>jol-core</artifactId>
	<version>0.10</version>
</dependency>
System.out.println(ClassLayout.parseInstance(instance).toPrintable());

偏向锁,一次CAS替换对象头线程ID,默认关闭

轻量级锁,多次CAS替换mark word中的指向栈桢的指针,自旋锁,1.6以前默认自选10次,1.6以后自适应自旋锁

重量级锁,线程阻塞

乐观锁

CAS 比较预期数据和原始数据是否一致,如果一致则修改,不一致则修改失败

对象监视器

monitor,重量级锁实现

线程的通信(wait/notify)

生产者 – 消费者

//消费者
public class Consumer implements Runnable {
  private Queue<String> msg;
  private int maxSize;
  
  public Consumer(Queue<String> msg, int maxSize) {
    this.msg = msg;
    this.maxSize = maxSize;
  }
  
  @Override
  public void run() {
    while(true) {
      synchronized(msg) {
        while(msg.isEmpty()) {//队列为空,无法消费,阻塞
          try {
            msg.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("消费者消费消息:" + msg.ramove());
        msg.notify();//唤醒生产者生产
      }
    }
  }
}

//生产者
public class Producer implements Runnable {
  private Queue<String> msg;
  private int maxSize;
  
  public Producer(Queue<String> msg, int maxSize) {
    this.msg = msg;
    this.maxSize = maxSize;
  }
  
  @Override
  public void run() {
    int i = 0;
    while(true) {
      i ++;
      synchronized(msg) {
        while(msg.size() == maxSize) {//队列满了,不生产,阻塞
          try {
            msg.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("生产者生产消息:消息" + i);
        msg.add("消息" + i);
        msg.notify();//唤醒消费者消费
      }
    }
  }
}

public class App {
  public static void main() {
    Queue<String> msg = new LinkedList<>();
    int maxSize = 10;
    new Thread(new Producer(msg, maxSize)).start();
    new Thread(new Consumer(msg, maxSize)).start();
  }
}

线程的死锁

什么是死锁

一组相互竞争共享资源的线程之间相互等待,导致永久性阻塞

活锁 线程没有出现阻塞并一直循环的状态

死锁产生的条件(同时满足)

  • 互斥,共享资源X和Y只能被一个线程占用
  • 占有且等待,线程1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X
  • 不可抢占,其他线程不能强行抢占线程1占有的资源
  • 循环等待,线程1等待线程2占有的资源,线程2等待线程1占有的资源

通过扩大锁的粒度破坏占有且等待

通过ReentrantLock.tryLock()破坏不可抢占

通过顺序加锁破坏循环等待

ThreadLocal

线程隔离机制 ,线程独有的

每个线程都持有一个ThreadLocalMap

0x61c88647 魔数,黄金分割,斐波那契散列

线性探索 -> 用来解决hash冲突的一种策略

  • 写入,找到发生冲突最近的空闲单元
  • 查找,从发生冲突的地方往后查找
private static final int HASH_INCREMENT = 0x61c88647;

public static void main() {
  magicHash(16);
}

public static void magicHash(int size) {
  int hashCode = 0;
  for (int i = 0; i < size; i ++) {
    hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
    System.out.print((hashCode & (size - 1)) + " ");
  }
}

线程安全性–可见性(volatile)

问题

public class VolatileDemo {
  public static boolean stop = false;
  //public static volatile boolean stop = false;
  //public static volatile int i;
  
  public static void main() {
    new Thread(() -> {
      int i = 0;
      while(!stop) {
        i ++;
        //System.out.println("rs:" + i); 可以结束循环
        //try {
        //	Thread.sleep(0); 可以结束循环
    		//} catch (InterruptedException e) {
      	//	e.printStackTrace();
    		//}
      }
      System.out.println("rs:" + i);
    }).start();
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    stop = true;
  }
}

无法停止线程,不能输出 rs + i

volatile 关键字修饰可以解决可见性问题

-Djava.compiler=NONE 关闭即时编译器

server版本的jdk做了深度优化(JIT) --just in time

while(!stop){
  i ++;
}
//优化后
if(!stop) {
  while(true) {
    i ++;
  }
}

print可以导致循环结束

活性失败

print中包含synchronized修饰

锁的释放会强制把工作内存的写操作同步到主内存

print是IO操作

IO操作会阻塞线程

Thread.sleep()可以结束循坏

官方说明 编译器可以自由的一次或者多次加载this.done

sleep会导致线程切换,致使缓存失效,重新加载

volatile关键字

hsdis 打印汇编指令的工具

lock汇编指令–保证可见性

lock指令出发cpu核心加载缓存操作

什么是可见性问题

线程A修改变量之后对线程B不可见

硬件层面

cpu、内存、IO设备

速度 cpu > 内存 > IO

  • cpu增加高速缓存
  • 操作系统,进程、线程、cpu时间片切换
  • 编译器的优化,更合理的利用cpu的高速缓存

cpu高速缓存

  • L1d 数据缓存,cpu核心独占
  • L1i 指令缓存,cpu核心独占
  • L2 cpu核心独占
  • L3 共享

因为高速缓存的存在,导致缓存一致性问题

总线锁
缓存锁

cpu架构是否支持

当前数据是否存在于缓存行

缓存一致性协议

MSI、MESI、MOSI…

MESI表示四种缓存状态

  • M modify 修改
  • E exclusive 独占
  • S shared 共享
  • I invalid 失效
Store Buffer

引入store buffer解决cpu通知缓存失效的阻塞

引入store buffer导致指令重排序

内存屏障

不允许指令重排序,并写入主内存

volatile 关键字通过内存屏障禁止指令重排序

  • 读屏障 #Load 指令
  • 写屏障 #Store 指令
  • 全屏障 #Fence 指令

防止指令重排序

禁止cpu高速缓存

#Lock指令 -> 等价于内存屏障

不同cpu架构,X86是强一致性架构

软件层面

Java内存模型(JMM)

内存模型定义了共享内存中多线程读写的操作规范

提供可见性和有序性问题的解决方案

JMM基于硬件层面封装 内存屏障指令类型
  • LoadLoad Barriers 顺序加载 Load1;LoadLoad;Load2
  • LoadStore Barriers 先读再写 Load1;LoadStore;Store2
  • StoreStore Barriers 顺序写入 Store1;StoreStore;Store2
  • StoreLoad Barriers 先写再读 Store1;StoreLoad;Load2

javap -v Xx.class 查看字节指令

ACC_VOLATILE

bytecodeInterpreter.cpp

orderAccess.hpp

Happens-Before模型

程序顺序规则(as-if-serial语义)

  • 不能改变程序的执行结果(在单线程环境下,执行的结果不变)

  • 依赖问题,如果两个指令存在依赖关系,不允许重排序

    void test() {
      int a = 1;
      int b = 1;
      int c = a * b;
    }
    

    a happens-before b ; b happens-before c

传递性规则

a happens-before b ; b happens-before c --> a happens-before c

volatile变量规则

volatile变量的写操作一定happens-before后续对于volatile变量的读操作

通过内存屏障防止指令重排序

public class VolatileExample{
  int a = 0;
  volatile boolean flag = false;
  
  //Thread1
  public void writer() {
    a = 1;										// 1 
    flag = true; //修改				    2
  }
  //Thread2
  public void reader() {
    if (flag) { //flag = true		3
      int i = a; //i = 1				4
    }
  }
}

1 happens-before 2 是否成立?是

3 happens-before 4 是否成立?是

2 happens-before 3 --> volatile规则

1 happens-before 4 i = 1 成立

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I1Az0a4D-1639183807604)(/Users/muprince/Library/Application Support/typora-user-images/image-20211124052551123.png)]

监视器锁规则

int x = 10;
synchronized(this) {
  //后续线程读取到的x的值一定是12
  if (x < 12) {
    x = 12;
  }
}

锁的释放一定happens-before后续对于线程加锁的操作

start规则

public class StartDemo {
  int x = 0;
  Thread t = new Thread(() -> {
    //读取x的值一定是20
    if (x == 20){} //一定是成立
  });
  x = 20;
  t.start();
}

线程start之前的操作一定happens-before线程run操作

Join规则

public class JionDemo {
  public static void main() {
    int x = 0;
    Thread t = new Thread(() -> {
      x = 20;
    });
    t.start();
    t.jion();//保证结果的可见性
    //在jion之后读取到的x的值一定是20
  }
}

t线程中对共享变量的修改对于所有jion之后的操作可见

wait/notify,Thread实例对象锁

fatally关键字提供了内存屏障的规则

这篇关于Java并发编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!