【Java并发】线程安全和内存模型,hashmap并发
【Java并发】线程安全和内存模型,hashmap并发
-
一、概述
- 1.1 什么是线程安全?
- 1.2 案例
- 1.3 线程安全解决办法:
-
二、synchronized
- 2.1 概述
- 2.2 同步代码块
- 2.3 同步方法
- 2.4 静态同步函数
- 2.5 总结
-
三、多线程死锁
- 3.1 什么是死锁
- 3.2 如何避免
-
四、Threadlocal
- 4.1 什么是Threadlocal
- 4.2 案例
- 4.3 ThreadLoca实现原理
- 4.4 内存泄漏问题
-
五、Java内存模型
- 5.1 主内存和工作内存
- 5.2 内存间交互操作
-
5.3 多线程有三大特性
- 1. 原子性
- 2. 什么是可见性
- 3. 什么是有序性
-
5.4 Volatile
- 1. 什么是Volatile
- 2. 代码
- 3. volatile特性
- 4. volatile 性能:
- 5. volatile与synchronized区别
-
5.5 重排序
- 1. 数据依赖性
- 2. as-if-serial语义
- 3. 程序顺序规则
- 4. 重排序对多线程的影响
一、概述
1.1 什么是线程安全?
- 当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
1.2 案例
- 需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
代码
public class ThreadTrain implements Runnable {
private int trainCount = 100;
@Override
public void run() {
while (trainCount > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
sale();
}
}
public void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
public static void main(String[] args) {
ThreadTrain threadTrain = new ThreadTrain();
Thread t1 = new Thread(threadTrain, "①号");
Thread t2 = new Thread(threadTrain, "②号");
t1.start();
t2.start();
}
}
运行结果
代码样例 代码样例 代码如下 代码样例 示例 线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。 操作系统中产生死锁必须具备以下四个条件: 破坏循环等待条件 我们对线程 2 的代码修改成下面这样就不会产生死锁了。 分析 运行结果 可以看出每个线程会自己生成num,互不干扰 最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。 每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal 对象调用set方法设置的值。ThreadLocal 是 map结构是为了让每个线程可以关联多个 ThreadLocal变量。这也就解释了 ThreadLocal 声明的变量为什么在每一个线程都有自己的专属本地变量。 关于弱引用 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 运行结果 读后写 a = b;b = 1; 读一个变量之后,再写这个变量。 上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。 前面提到过,编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。 注意,这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。 根据happens- before的程序顺序规则,上面计算圆的面积的示例代码存在三个happens- before关系: 由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?请看下面的程序执行时序图: 如上图所示,操作1和操作2做了重排序。程序执行时,线程A首先写标记变量flag,随后线程B读这个变量。由于条件判断为真,线程B将读取变量a。此时,变量a还根本没有被线程A写入,在这里多线程程序的语义被重排序破坏了! 下面再让我们看看,当操作3和操作4重排序时会产生什么效果(借助这个重排序,可以顺便说明控制依赖性)。下面是操作3和操作4重排序后,程序的执行时序图:1.3 线程安全解决办法:
二、synchronized
2.1 概述
2.2 同步代码块
synchronized(对象) { //这个对象可以为任意对象
需要被同步的代码
}
public void sale() {
synchronized (this) {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
}
2.3 同步方法
public synchronized void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
/**
*
* 测试 同步方法(非静态) 的锁是 this 对象
* @author hao
*
*/
public class Test_SyncFun {
public static void main(String[] args) throws InterruptedException {
MyThread threadTrain = new MyThread();
Thread t1 = new Thread(threadTrain, "窗口1");
Thread t2 = new Thread(threadTrain, "窗口2");
t1.start();
Thread.sleep(40);
threadTrain.flag = false;
t2.start();
}
}
class MyThread implements Runnable {
private int trainCount = 100;
private Object oj = new Object();
public boolean flag = true;
public void run() {
if (flag) {
while (trainCount > 0) {
synchronized (this) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ","
+ "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
} else {
while (trainCount > 0) {
sale();
}
}
}
public synchronized void sale() {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ","
+ "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
2.4 静态同步函数
public static synchronized void sale() {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
//上面的就等同于如下代码块,锁对象为当前类的字节码文件对象
public static void sale() {
synchronized (ThreadTrain.class) {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainCount + 1) + "张票");
trainCount--;
}
}
}
2.5 总结
三、多线程死锁
3.1 什么是死锁
/**
* 死锁
*
*/
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
3.2 如何避免
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 2").start();
四、Threadlocal
4.1 什么是Threadlocal
4.2 案例
package com.hao.threadlocal;
public class ThreadLocaDemo extends Thread {
private Res res;
public ThreadLocaDemo(Res res) {
this.res = res;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
}
}
public static void main(String[] args) {
Res res = new Res();
ThreadLocaDemo threadLocaDemo1 = new ThreadLocaDemo(res);
ThreadLocaDemo threadLocaDemo2 = new ThreadLocaDemo(res);
ThreadLocaDemo threadLocaDemo3 = new ThreadLocaDemo(res);
threadLocaDemo1.start();
threadLocaDemo2.start();
threadLocaDemo3.start();
}
}
class Res {
// 生成序列号共享变量
public static Integer count = 0;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
};
};
public Integer getNum() {
int count = threadLocal.get() + 1;
threadLocal.set(count);
return count;
}
}
Thread-1---i---0--num:1
Thread-2---i---0--num:1
Thread-0---i---0--num:1
Thread-2---i---1--num:2
Thread-1---i---1--num:2
Thread-2---i---2--num:3
Thread-1---i---2--num:3
Thread-0---i---1--num:2
Thread-0---i---2--num:3
4.3 ThreadLoca实现原理
4.4 内存泄漏问题
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
五、Java内存模型
5.1 主内存和工作内存
5.2 内存间交互操作
5.3 多线程有三大特性
1. 原子性
2. 什么是可见性
cpu
,那么线程1改变了i
的值还没刷新到主存,线程2又使用了i
,那么这个i
值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。 3. 什么是有序性
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
5.4 Volatile
1. 什么是Volatile
2. 代码
class ThreadVolatileDemo extends Thread {
public boolean flag = true;
@Override
public void run() {
System.out.println("开始执行子线程....");
while (flag) {
}
System.out.println("线程停止");
}
public void setRuning(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
threadVolatileDemo.start();
Thread.sleep(3000);
threadVolatileDemo.setRuning(false);
System.out.println("flag 已经设置成false");
Thread.sleep(1000);
System.out.println(threadVolatileDemo.flag);
}
}
3. volatile特性
4. volatile 性能:
5. volatile与synchronized区别
5.5 重排序
1. 数据依赖性
2. as-if-serial语义
double pi = 3.14; //A
double r = 1.0; //B
double area = pi * r * r; //C
3. 程序顺序规则
4. 重排序对多线程的影响
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; //1
flag = true; //2
}
public void reader() {
if (flag) { //3
int i = a * a; //4
……
}
}
}
相关文章
暂无相关文章
用户点评