Java并发--基础知识
一、为什么要用到并发
充分利用多核CPU的计算能力
方便进行业务拆分,提升应用性能
二、并发编程有哪些缺点
频繁的上下文切换
时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU不断通过切换线程,让我们觉得是不断执行的,时间片一般是几十毫秒。而每次切换时,需要保存当前的状态,以便能够进行回复当期状态。而这个切换是非常损耗性能,过于频繁反而无法发挥出多线程编程的优势。通常减少上下文切换可以采用无锁并发编程,CAS算法,使用最少的线程和使用协程。
比如:悲观锁就会导致频繁的上下文切换,而频繁的上下文切换可能无法发挥出多线程编程的优势
无锁并发编程
可以参照jdk1.7分段锁的思想,不同的线程处理不同的数据,这样在多线程竞争的条件下,可以减少上下文切换的时间
CAS算法
利用Atomic下使用CAS(compare and swap)算法来更新数据,使用了乐观锁,可以有效的减少一部分不必要的锁竞争带来的上下文切换。
使用最少的线程
避免创建不必要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态。
线程安全--死锁
多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的情况,一旦产生死锁就会造成系统功能不可用。
避免死锁
- 避免一个线程同时获得多个锁
- 避免一个线程在锁内占有多个资源,尽量保证每个所只占有一个资源
- 尝试使用定时锁,使用lock.tryLock(TimeOut),当超时等待时当前线程不会阻塞
- 对于数据库,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
三、并行和并发
并发指的是多个任务交替执行,而并行指的是真正意义上的同时进行
实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。
四、同步和异步
同步和异步通常用来形容一次方法的调用
同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。
五、阻塞和非阻塞
阻塞和非阻塞常用来形容多线程间的相互影响。
比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试的往前运行
六、线程的临界区资源
临界区用来表示一种公共资源或者说共享数据,可以被多个线程使用,但是每一个线程使用时,一旦临界区资源被一个线程占有,那么其它线程必须等待。
七、新建线程有哪几种方式
一个Java线程从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但是实际上Java程序天生就是一个多线程程序,包含了:
- 分发出路发送给JVM信号的线程
- 调用对象的finalize方法的线程
- 清除Reference的线程
- main线程,用户程序的入口
三种方式(有三种方式实现,JDK源码中标明只有两种方式)
1、继承Thread类,重写run方法
2、实现Runnable接口
3、实现Callable接口
public class NewThread {
/*扩展自Thread类*/
private static class UseThread extends Thread{
@Override
public void run() {
super.run();
// do my work;
System.out.println("I am extendec Thread");
}
} /*实现Runnable接口*/
private static class UseRunnable implements Runnable{ @Override
public void run() {
// do my work;
System.out.println("I am implements Runnable");
}
} public static void main(String[] args)
throws InterruptedException, ExecutionException {
UseThread useThread = new UseThread();
useThread.start();
useThread.start(); UseRunnable useRunnable = new UseRunnable();
new Thread(useRunnable).start(); }
}
public class UseFuture { /*实现Callable接口,允许有返回值*/
private static class UseCallable implements Callable<Integer>{
private int sum;
@Override
public Integer call() throws Exception {
for(int i= ;i<;i++){
if(Thread.currentThread().isInterrupted()) {return null;
}
sum=sum+i;
System.out.println("sum="+sum);
} return sum;
}
} public static void main(String[] args)
throws InterruptedException, ExecutionException { UseCallable useCallable = new UseCallable();
//包装
FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
Random r = new Random();
new Thread(futureTask).start(); Thread.sleep();
if(r.nextInt()>){
System.out.println("Get UseCallable result = "+futureTask.get());
}else{
futureTask.cancel(true);
} } }
八、线程的转换状态
- 线程创建之后调用start()方法开始运行,当调用wait(),join(),LockSupport.lock()方法线程会进入到WAITING状态。
- 而同样的wait(long timeout),sleep(long time),join(),LockSupport.parkNamos(),LockSupport.parkUtil()增加了超时等待的功能,也就是调用这些方法后线程会进入TIME_WAITING状态
- 当超时等待时间到达后线程会切换到RUNNABLE的状态,另外当WAITING和TIME_WAITING状态是可以通过notify/notifyAll方法使线程转换到RUNNABLE状态
- 当线程出现资源竞争时,即等待获取锁的时候,线程会进入到BLOCKED阻塞状态
- 当线程获取锁时,线程进入到RUNNABLE状态
- 线程运行结束,线程进入到TERMINATED状态,状态转换可以说是线程的声明周期
另外需要注意的是:
当线程进入到synchronized方法或者synchronize的代码块的时候,线程切换的是BLOCKED状态,而使用lock进行加锁的时候线程切换的是WAITING或者TIME_WAITING状态,因为lock会调用LockSupport的方法
九、中断标志位--interrupted
中断可以理解为线程的一个标志位,它代表了一个运行中的线程是否被其他线程进行了中断操作。
中断好比其他线程对该线程打了一个招呼。其他线程可以调用该线程的interrupt()方法对其进行中断操作,同时该线程可以调用isInterrupted()来感知其他线程对自身的中断操作,从而做出响应。
另外,同样可以调用Thread的静态方法interrupted()对当前线程进行中断操作,该方法会清除中断标志位。
需要注意的是,当抛出interruptedExection时候,会清除中断标志位,也就是说在调用isInterrupted时会返回false。
十、join
如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待线程threadB线程终止后threadA才会继续执行
关于join方法一共提供了如下这些方法
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = ; if (millis < ) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == ) {
while (isAlive()) {
wait();
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= ) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
} public final synchronized void join(long millis, int nanos)
throws InterruptedException { if (millis < ) {
throw new IllegalArgumentException("timeout value is negative");
} if (nanos < || nanos > ) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
} if (nanos >= || (nanos != && millis == )) {
millis++;
} join(millis);
} public final void join() throws InterruptedException {
join();
}
Thread类出了提供join()方法外,另外还提供了超时等待的方法,如果线程threadB在等待的时间内还没有结束的话,threadA会在超时之后继续执行。join方法源码的关键是
while (isAlive()) {
wait();
}
可以看出来当前等待对象threasA会一直阻塞,知道被等待对象threadB结束后即isAlive()返回false的时候才会结束while循环,当天threadB退出时会调用notifyAll方法通知所有的等待线程。
十一、sleep和wait
public static native void sleep(long millis) throws InterruptedException;
该方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器
需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。sleep方法经常拿来和object.wait()方法进行比较,这也是面试经常被问到的地方
sleep()和wait()的区别
1.sleep方法是threa的静态方法,而wait是objectshilde方法;
2、wait方法必须要在同步方法或者同步块中调用,也就是必须获得对象锁,而sleep方法没有这个限制,可以在任何地方使用
3、wait方法会释放占有的对象锁,使线程进入到等待池中,等待下一次获取资源。而sleep方法只是会让出CPU并不会释放掉对象锁
4、sleep方法在休眠时间达到后如果再次获取CPU时间片就会继续执行,而wait方法必须等到notify/notifyAll通知后,才会离开等待池,并且再次获取CPU时间片才会继续执行
十二、yield
public static native void yield();
1、这是一个thread的静态方法
2、一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不在运行了,吐过在下一次竞争中,又获得CPU时间片当前线程依旧会继续运行。另外让出的时间片智慧分配给当前相同优先级的线程
3、需注意的是sleep和yield方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield方法只允许与当前具有相同优先级的线程能够获得释放出来的CPU时间片。
十三、线程的优先级
在 Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范围从 1~10,在线程构建的时候可以通过 setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。
十四、守护线程
Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在非 Daemon 线程的时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true)将线程设置为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。
Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑。
Java并发--基础知识的更多相关文章
- java并发基础知识
这几天全国都是关键时候,放假了,还是要学习啊!很久没有写博客了,最近看了一本书,有关于java并发编程的,书名叫做“java并发编程之美”,讲的很有意思,这里就做一个笔记吧! 有需要openjdk8源 ...
- Java 并发基础知识
一.什么是线程和进程? 进程: 是程序的一次执行过程,是系统运行程序的基本单元(就比如打开某个应用,就是开启了一个进程),因此进程是动态的.系统运行一个程序即是一个程序从创建.运行到消亡的过程. 在 ...
- Java并发基础知识你知道多少?
并发 https://blog.csdn.net/justloveyou_/article/details/53672005 并发的三个特性是什么? 什么是指令重排序? 单线程的指令重排序靠什么保证正 ...
- 目录-java并发基础知识
====================== 1.volatile原理 2.ThreadLocal的实现原理(源码级) 3.线程池模型以及核心参数 4.HashMap的实现以及jdk8的改进(源码级) ...
- java并发基础及原理
java并发基础知识导图 一 java线程用法 1.1 线程使用方式 1.1.1 继承Thread类 继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继 ...
- Java笔记(十四) 并发基础知识
并发基础知识 一.线程的基本概念 线程表示一条单独的执行流,它有自己的程序计数器,有自己的栈. 1.创建线程 1)继承Thread Java中java.lang.Thread这个类表示线程,一个类可以 ...
- Java 多线程——基础知识
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java并发基础:进程和线程之由来
转载自:http://www.cnblogs.com/dolphin0520/p/3910667.html 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程. ...
- 什么才是java的基础知识?
近日里,很多人邀请我回答各种j2ee开发的初级问题,我无一都强调java初学者要先扎实自己的基础知识,那什么才是java的基础知识?又怎么样才算掌握了java的基础知识呢?这个问题还真值得仔细思考. ...
随机推荐
- day6:双向循环练习&pass_break_continue&for循环
双向循环练习 1.打印10行10列的小星星(两个循环) # j 外循环用来控制行数 j = 0 while j < 10: # i 打印一行十个星星 i = 0 while i < 10: ...
- 各种jar包下载地址
standard.jar和jstl.jar的下载地址 http://repo2.maven.org/maven2/javax/servlet/jstl/ http://repo2.maven.org/ ...
- 初级软件工程师怎么走向BATJ?——献给迷茫中的测试人
软件测试,邀你同行.你好,我是爱码小哥. 又是一个深夜,打开手机备忘录,想记录一些东西,本人比较静的一个人,所以经常会去 IT行业的贴吧论坛交流一下,逛知乎,论坛,社区你就会发现大量这样的帖子,都会出 ...
- [Qt2D绘图]-05绘图设备-QPixmap&&QBitmap&&QImage&&QPicture
这篇笔记记录的是QPainterDevice(绘图设备,可以理解为一个画板) 大纲: 绘图设备相关的类:QPixmap QBitmap QImage QPicture QPixmap ...
- 使用nvm安装node,运行node报错 node: command not found
1. 使用nvm安装node之后,直接运行node命令会报错 node: command not found 需要使用nvm ls 查询一下当前使用的安装的node版本,然后使用node use 版 ...
- Python 简明教程 --- 23,Python 异常处理
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...
- LGTB 与 序列
题目描述 LGTB 有一个长度为 N 的序列 A ,现在他想构造一个新的长度为 N 的序列 B ,使得 B 中的任意两个数都互质.并且他要使 \sum_{1\le i\le N}|A_i-B_i| 最 ...
- 高效C++:实现
本章主要是解决如下问题: 类的声明和定义在什么时候提出 类与类之间的耦合关系如何降低 类型转换怎么正确使用 尽可能延后变量定义式的出现 变量用到时在定义,不要提前定义,防止变量定义而没有使用的情况,因 ...
- nginx location proxy_pass 后面的url 加与不加/的区别
在nginx中配置proxy_pass时,当在后面的url加上了/,相当于是绝对根路径,则nginx不会把location中匹配的路径部分代理走;如果没有/,则会把匹配的路径部分也给代理走. 首先是l ...
- Java bean常见映射工具分析和比较
1. 概述 日常Java开发项目中,我们经常需要将对象转换成其他形式的对象,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型. 进行这种转换除了手动编写大量的get/set代码,还可 ...