没有合适的资源?快使用搜索试试~ 我知道了~
java并发编程1
资源详情
资源评论
资源推荐
三、Java并发编程
阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必
须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程
可以阻塞其他线程,所有的线程都会尝试地往前运行。
0.并发编程的优缺点
优点:并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。面对复杂业务模型,并行程序会比串
行程序更适应业务需求,而并发编程更能吻合这种业务的拆分。
缺点:
频繁的上下文切换:时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU不断通过切换线程,让我们觉
得多个线程是同时执行的,时间片一般是几十毫秒。而每次切换时,需要保存当前的状态起来,以便能够进行恢复先
前状态,而这个切换时非常损耗性能,过于频繁反而无法发挥出多线程编程的优势。通常减少上下文切换可以采用无
锁并发编程,CAS算法,使用最少的线程和使用协程。
无锁并发编程:可以参照concurrentHashMap锁分段的思想,不同的线程处理不同段的数据,这样在多线程竞
争的条件下,可以减少上下文切换的时间。
CAS算法,利用Atomic下使用CAS算法来更新数据,使用了乐观锁,可以有效的减少一部分不必要的锁竞争带
来的上下文切换
使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处
于等待状态
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
由于上下文切换也是个相对比较耗时的操作,所以在"java并发编程的艺术"一书中有过一个实验,并发累加未必会比
串行累加速度要快。 可以使用Lmbench3测量上下文切换的时长 vmstat测量上下文切换次数
线程安全:多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况,一旦产生死锁就
会造成系统功能不可用。
1.什么是线程,与进程的区别
1)定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单
位.
线程是进程的执行单元,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程可以拥有自
己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它可与父进程的其他的线程共享进程所拥有的
全部资源。
2)关系
一个线程可以创建和撤销另一个线程;
一个线程可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列;
同一个进程中的多个线程之间可以并发执行;
一个线程必须有一个父进程;
3)区别
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出
口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多
个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
4)优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于
在SMP机器上运行,而进程则可以跨机器迁移。
5)Java中线程状态转换
线程是会在不同的状态间进行转换的,java线程线程转换图如上图所示。线程创建之后调用start()方法开始运行,当
调用wait(),join(),LockSupport.lock()方法线程会进入到WAITING状态,而同样的wait(long timeout),
sleep(long),join(long),LockSupport.parkNanos(),LockSupport.parkUtil()增加了超时等待的功能,也就是调用这些
方法后线程会进入TIMED_WAITING状态,当超时等待时间到达后,线程会切换到Runable的状态,另外当WAITING
和TIMED _WAITING状态时可以通过Object.notify(),Object.notifyAll()方法使线程转换到Runable状态。当线程出现资
源竞争时,即等待获取锁的时候,线程会进入到BLOCKED阻塞状态,当线程获取锁时,线程进入到Runable状态。
线程运行结束后,线程进入到TERMINATED状态,状态转换可以说是线程的生命周期。另外需要注意的是:
当线程进入到synchronized方法或者synchronized代码块时,线程切换到的是BLOCKED状态,而使用
java.util.concurrent.locks下lock进行加锁的时候线程切换的是WAITING或者TIMED_WAITING状态,因为
lock会调用LockSupport的方法。
2.阅读源代码,并学会使用
Thread、Runnable、Callable、ReentrantLock、ReentrantReadWriteLock、Atomic*、Semaphore、
CountDownLatch、、ConcurrentHashMap、Executors
(1)线程创建方法
继承Thread类创建线程类
实现Runnable接口创建线程类
使用Callable和Future创建线程
Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。
创建Callable接口的实现类,并实现call()方法,该call方法将作为线程执行体,且该call方法有返回值,再创建
Callable实现类的实例。从Java8开始,可以直接使用lambda表达式创建Callable对象。
使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象的target创建并启动新线程
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
new MyFirstThread().start()
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
1
2
MyRunnableImp mri = new MyRunnableImp();
new Thread(mri,"Name").start();
new Thread(mri,"Name2").start();
程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享同一个
线程类(实际上应该是线程的target类)的实例变量。Thread类的作用就是把run()方法包装成线程的执行体。
1
2
3
4
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i = 0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
}
return i;
});
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
if(i == 20){
1
2
3
4
5
6
7
8
9
10
在这段程序中,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值,程
序再最后调用了get()方法来返回call()方法的返回值——该方法将导致主线程被阻塞,直到call()方法结束并返回为
止。
采用Runnable、Callable接口的方式创建多线程的优缺点:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
在这种方式下,多个线程可以共享同一个target对象,
(2)CountDownLatch
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥
的作用)。CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。其实现方式是使
用一个计数器。计数器初始值为线程的数量。
当每一个线程完成自己任务后,通过调用CountDownLatch实例的countDown()方法,使计数器的值就会减一。当计
数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程(通过调用实例的
await()方法阻塞当前进程)就可以恢复执行任务。
CountDownLatch的用法:
CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初
始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就调用ountdownlatch.countDown()实现对
计数器减1。当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是
启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
CountDownLatch典型用法2:实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是
多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法
是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先
coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
CountDownLatch的实现,是使用一个内部类AQS(队列同步器)的共享锁来实现的。具体思路是:
初始化时设置初始参数count,表示需要等待多少个线程,最终是设置AQS中state的值。
各个线程在执行countDown方法时,通过CAS自旋修改state的值,实现state-1操作。
在线程等待其他线程执行结束,即执行await方法时,如果当前state!=0,表示还有线程未执行countDown方
法,则通过addWaiter进入AQS的等待队列,并使用LockSupport.park(this)阻塞当前线程。
等待中的线程唤醒是在countDown方法中进行的,当它发现state=0的时候就唤醒等待队列中的head的直接后
继
new Thread(task, "有返回值的线程").start();
}
}
try {
System.out.println(task.get() + " 是子线程的返回值");
} catch (Exception e) {
e.printStackTrace();
}
11
12
13
14
15
16
17
18
public class CountDownLatch {
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
1
2
3
4
5
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
/**
* 如果直到执行await方法时,state != 0,就说明还有线程没有执行countDown方法,
* 当前线程就会加入到AQS等待队列中,并执行LockSupport.park(this)将当前线程阻塞
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
/**
* 当countDown执行之后state = 0了,就唤醒直接后继等待节点
*/
public void countDown() {
sync.releaseShared(1);
}
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
剩余147页未读,继续阅读
深层动力
- 粉丝: 18
- 资源: 318
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- #P0015. 全排列 超级简单
- pta题库答案c语言之排序4统计工龄.zip
- pta题库答案c语言之树结构7堆中的路径.zip
- pta题库答案c语言之树结构3TreeTraversalsAgain.zip
- pta题库答案c语言之树结构2ListLeaves.zip
- pta题库答案c语言之树结构1树的同构.zip
- 基于C++实现民航飞行与地图简易管理系统可执行程序+说明+详细注释.zip
- pta题库答案c语言之复杂度1最大子列和问题.zip
- 三维装箱问题(Three-Dimensional Bin Packing Problem,3D-BPP)是一个经典的组合优化问题
- 以下是一些关于Linux线程同步的基本概念和方法.txt
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0