为什么我们需要考虑并发?不考虑的话会出现什么问题?
并发的多个程序(进程/线程)会对计算机资源进行争夺,如果不加以控制会出现混乱、严重影响程序运行效率,甚至错误
首先是对CPU时间片的争夺
对于多线程编程而言,由于创建线程后,线程的执行顺序是由调度程序控制的,也就是说各个线程的执行顺序并没有一个确定的预期,显然在很多情况下这会影响到我们的编程逻辑,所以我们首先需要一些方法能够实现对线程执行顺序时机的控制
其次,是对共享内存访问(读写)的问题
例如我们使用两个线程访问同一个共享的全局变量对其做大量的自加操作,由于:
- 首先自加操作并不是原子的
事实上包含了三条指令
- 从内存中读取变量的值
- 对变量值加一
- 将操作后的变量值写回
- 于是,例如
thread1
在读取count
的值比如0
后,+1
,但是还没写回内存之前,发生了时钟中断,thread1
让出CPU并将状态保存到 TCB,thread2
得到了时间片并开始执行,读取count
的值,由于thread1
的更改还没有写回内存,于是thread2
读到的还是0
那么,最终的结果就是,原本预期是被加了两次结果是2
的count
值,最终得到的却是1
也就是说如果不对并发的内存访问加以控制,那么最终得到的count
值和有可能是小于我们的预期的
这其实也是我遇到的一个面试题,面试官希望我举一个例子说明什么时候会出现并发问题
下面是一段简单的示例代码,用来说明上面的两种问题
public class CreateThread {
static int count = 0;// 一个全局共享变量
/**
* 一个线程内部类
*/
static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) count++;
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
thread1.join(); // 等待 thread1 执行完成
thread2.join(); // 等待 thread2 执行完成
System.out.println(count);
}
}
讲解一下
首先对于问题1,在程序中的体现在于:
我希望在两个子线程执行结束后,然后再打印最终的count
变量,而不是子线程还在执行甚至还没执行就打印了count
值,于是我使用了join()
方法,它会使得主线程在子线程执行结束后再执行
对于问题2,在程序中的体现在于:
两个子线程在执行过程中分别访问并对count变量做了自加操作,各自10000次,我们预期结果应该是20000(当然我们知道这是不对的),但是结果得到了比如:17691、16976、12030
在上面的代码中,执行for (int i = 0; i < 10000; i++) count++;
这段代码的多个线程可能导致竞争状态,即临界区——访问共享变量(资源)的代码片段,一定不能由多个线程同时执行
那么我们如何实现原子操作?
另一种说法是:事务
锁
首先我们可以使用锁机制,锁是一种互斥概念,同一把锁同一时间只能被一个线程持有,而其他线程必须等待
Java语言中对锁的支持全面,大致有以下几种
- 我们最熟悉的
synvhronized
关键字 Lock
接口
ReentrantLock
main()
方法不变,我们将代码改成这样:
private static final ReentrantLock lock = new ReentrantLock();
static int count = 0;// 一个全局共享变量
/**
* 一个线程内部类
*/
static class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
}
测试可打断特性
public class ReentrantTest {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args){
Thread t1 = new Thread(() -> {
try {
/*
如果没有竞争此方法会获取lock对象锁
有竞争就进入阻塞队列,但是可以被其他线程用interrupt()方法打断
*/
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("没有获得锁,返回");
return;
}
try {
System.out.println("获取到锁");
} finally {
lock.unlock();
}
}, "testThread");
lock.lock();
t1.start();
System.out.println("打断t1");
t1.interrupt();
}
}
标签:count,执行,Java,lock,编程,线程,thread1,static,多线程
From: https://www.cnblogs.com/yaocy/p/17274933.html