Java 多线程与并发【知识点笔记】
Java 多线程与并发【知识点笔记】
Java多线程与并发
先说一下线程与进程的由来:
在初期的计算机,计算机只能串行执行任务,并且需要长时间的等待用户的输入才行
到了后来,出现了批处理,可以预先将用户的指令集中成清单,然后批量串行处理用户指令,但是这仍然无法并发执行
然后进程就出现了,进程独占内存空间,保存各自运行状态,相互间不干扰且可以互相切换,为并发处理任务提供了可能,这就解决了无法并发的情况
但是因为一个进程一段时间只做一个事情,如果一个进程有多个任务,只能一个一个的处理,甚是麻烦,因为就出现了线程,一个进程就包括了多个线程,线程可以共享进程的内存资源,相互之间切换更加的快速,支持更细粒度的任务控制,是进程内的子任务得到并发执行
进程和线程的区别
首先我们清楚,进程是资源分配的最小单位,线程是CPU调度的最小单位,而所有的与进程相关的资源都被记录在PCB中
而且,进程是抢占处理机的调度单位,线程属于某个进程,共享其资源,而线程只由堆栈寄存器,程序计数器和TCB组成
那么总的来说,这两个的区别有下面几点:
1.线程不能看做是一个独立的应用,而进程可以看做独立应用
2.进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径,线程没有独立的地址空间,多进程的程序比多线程程序健壮
3.进程的切换比线程的切换开销更大
在Java中进程和线程的关系
1.Java作为与平台无关的编程语言,就会对操作系统提供的功能进行封装,包括进程和线程
2.每运行一个Java程序就会产生一个进程,而进程包含至少一个线程
3.每个进程对应一个JVM实例,多个线程共享JVM里的堆
4.进程类似于投资者,其手中掌握着资源,拿着资源去干活打工的就是线程,而Java采用的是单线程编程模型,程序会自动创建主线程
5.主线程可以创建子线程,原则上要后于子线程完成执行
Thread中的start和run方法的区别
调用start()方法会创建一个新的子线程启动,而run()方法只是thread的一个普通方法的调用
Thread和runnable的关系
Thread是一个类,而runnable是一个接口,Thread是一个可以实现runnable接口的类,可以让runnable支持多线程
因为Java类的单一继承原则,为了提升系统的可扩展性,推荐使业务类实现runnable接口,将业务逻辑分装在run方法里,便于给普通的类附上多线程的特性
怎么给run()方法传参?
实现的方法主要有三种:构造函数传参,成员变量传参以及回调函数传参
如何实现处理线程的返回值?
实现的方式主要有三种:
第一种方法,主线程等待法,实现起来比较简单,缺点就是需要自己实现循环等待的逻辑,当需要等待的变量多起来,代码就会看起来很臃肿
代码如下:
public class CycleWait implements Runnable{
private String value;
public void run() {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "we have data now";
}
public static void main(String[] args) throws InterruptedException {
CycleWait cw = new CycleWait();
Thread t = new Thread(cw);
t.start();
while (cw.value == null){
Thread.currentThread().sleep(100);
}
System.out.println("value : " + cw.value);
}
}
第二种方法,使用Thread类的join()阻塞当前线程以等待子线程处理完毕,这种能够比主线程等待法的控制更加精准,实现起来也更简单,但是缺点是粒度不够细
代码不同之处就是
public static void main(String[] args) throws InterruptedException {
CycleWait cw = new CycleWait();
Thread t = new Thread(cw);
t.start();
t.join();
System.out.println("value : " + cw.value);
}
}
第三种方法,通过callable接口实现:通过futuretask 或者是通过线程池获取,可以更加精准的进行控制
其中的一种代码如下:
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
Future<String> future = newCachedThreadPool.submit(new MyCallable());
if(!future.isDone()){
System.out.println("task has not finished, please wait!");
}
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
newCachedThreadPool.shutdown();
}
}
}
线程的状态
线程主要有六个状态:
新建(new):创建以后尚未启动的线程的状态
运行(runnable):包含running和ready,有可能正在执行,也可能在等待
无限期等待(waiting):不会被分配CPU执行时间,需要显式被唤醒,通过三个方法可以进入无限期等待:调用了没有设置timeout参数的object.wait()方法或者是没有设置timeout参数的thread.join()方法以及locksupport.park()方法
限期等待(timed waiting):在一定时间后会被系统自动唤醒,主要有五个方法可以进入限期等待:第一个是thread.sleep()方法,第二个是设置了设置timeout参数的object.wait()方法,第三个是设置timeout参数的thread.join()方法,第四个是locksupport.parknanos()方法,第五个是locksupport.parkuntil()方法
阻塞(blocked):等待获取排它锁
结束(terminated):已终止线程的状态,线程已经结束执行
线程状态以及状态之间的转换
sleep和wait的区别
基本的差别
sleep是thread类的方法,wait是object类中定义的方法
sleep方法可以在任何地方使用,而wait方法只能在synchronized方法或者是synchronized块中使用
最主要的本质上的区别
Thread.sleep只会让出CPU,不会导致锁行为的改变,而object.wait不仅让出CPU,还会释放已经占有的同步资源锁
notify和notify all的区别
要想先了解这两个的区别,就得先知道两个概念:锁池entrylist以及等待池waitset
锁池的本质可以这样说,假设线程a已经拥有了某个对象的锁,而其他的线程b和c想要调用这个对象的某个synchronized方法或者是synchronized块,由于b和c线程在进入对象的synchronized方法(或者是块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正在被线程a占用,此时线程b和c就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
等待池的本质可以这样说,假设线程a调用了某个对象的wait方法,线程a就会释放该对象的锁,同时线程a就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁
然后就可以说一下notify和notifyall的区别了
notifyall会让所有处于等待池中的线程全部进入锁池去竞争获取锁的机会,没有获得锁的只能在所持中等待下个机会,不能再回到等待池中
而notify只会随机取一个处于等待池中的线程进入锁池中去竞争获取锁的机会
yield函数
概念部分:当调用thread.yield()函数的时候,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示
示例代码如下:
public class YieldDemo {
public static void main(String[] args) {
Runnable yieldTask = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
if (i == 5) {
Thread.yield();
}
}
}
};
Thread t1 = new Thread(yieldTask, "A");
Thread t2 = new Thread(yieldTask, "B");
t1.start();
t2.start();
}
如何中断线程?
已经被抛弃的方法:通过调用stop()方法停止线程(因为太过暴力等问题被停止使用)以及通过调用suspend()和resume()方法
目前在使用的方法:调用interrupt()方法,作用不是中断线程,而是通知线程应该中断了,具体来说,就是如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个interruptedexception异常,那么如果线程处于正常的活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响
因此interrupt并不能真正的中断线程,而是需要被中断的线程来配合中断才可以,也就是说,一个线程有了被中断的需求才能这样做,在正常运行任务的时候,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响
示例代码如下:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Runnable interruptTask = new Runnable() {
@Override
public void run() {
int i = 0;
try {
//在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100); // 休眠100ms
i++;
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i);
}
} catch (InterruptedException e) {
//在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程)
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException.");
}
}
};
Thread t1 = new Thread(interruptTask, "t1");
System.out.println(t1.getName() +" ("+t1.getState()+") is new.");
t1.start(); // 启动“线程t1”
System.out.println(t1.getName() +" ("+t1.getState()+") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态
Thread.sleep(300);
System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
}
}
Java 多线程与并发【知识点笔记】的更多相关文章
- Java 多线程高并发编程 笔记(一)
本篇文章主要是总结Java多线程/高并发编程的知识点,由浅入深,仅作自己的学习笔记,部分侵删. 一 . 基础知识点 1. 进程于线程的概念 2.线程创建的两种方式 注:public void run( ...
- JAVA 多线程和并发学习笔记(三)
Java并发编程中使用Executors类创建和管理线程的用法 1.类 Executors Executors类可以看做一个“工具类”.援引JDK1.6 API中的介绍: 此包中所定义的 Execut ...
- JAVA 多线程和并发学习笔记(二)
一.Java中创建线程方法 1. 继承Thread类创建线程类 定义Thread类的子类,重写该类的run()方法.该方法为线程执行体. 创建Thread子类的实例.即线程对象. 调用线程对象的sta ...
- JAVA 多线程和并发学习笔记(四)
1. 多进程 实现并发最直接的方式是在操作系统级别使用进程,进程是运行在它自己的地址空间内的自包容的程序.多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程. 尽 ...
- Java多线程高并发学习笔记(二)——深入理解ReentrantLock与Condition
锁的概念 从jdk发行1.5版本之后,在原来synchronize的基础上,增加了重入锁ReentrantLock. 本文就不介绍synchronize了,有兴趣的同学可以去了解一下,本文重点介绍Re ...
- Java多线程高并发学习笔记(一)——Thread&Runnable
进程与线程 首先来看百度百科关于进程的介绍: 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动.它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的 ...
- Java多线程高并发学习笔记——阻塞队列
在探讨可重入锁之后,接下来学习阻塞队列,这边篇文章也是断断续续的写了很久,因为最近开始学ssm框架,准备做一个自己的小网站,后续可能更新自己写网站的技术分享. 请尊重作者劳动成果,转载请标明原文链接: ...
- Java 多线程高并发编程 笔记(二)
1. 单例模式(在内存之中永远只有一个对象) 1.1 多线程安全单例模式——不使用同步锁 public class Singleton { private static Singleton sin=n ...
- JAVA 多线程和并发学习笔记(一)
一.进程与线程 1. 进程 当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中的程序.进程是操作系统进行资源分配和调度的一个独立单位.进程的三个特征: 独立性 独立存在的实体,每个进程都有 ...
- JAVA多线程高并发学习笔记(三)——Callable、Future和FutureTask
为什么要是用Callable和Future Runnable的局限性 Executor采用Runnable作为基本的表达形式,虽然Runnable的run方法能够写入日志,写入文件,写入数据库等操作, ...
随机推荐
- 详解 MD5 信息摘要算法
对于软件研发人员来说 MD5 不是一个陌生的词汇,平时的软件研发中,经常使用 MD5 校验消息是否被篡改.验证文件完整性,甚至将MD5当作加密算法使用. MD5虽不陌生,但不是所有研发人员都了解其算法 ...
- 常见链表操作-链表中环的检测(JAVA实现)
问题如何检测一个单链表中是否有环,例如下图的例子. 解决思路1:快慢指针法这是最常见的方法.思路就是有两个指针P1和P2,同时从头结点开始往下遍历链表中的所有节点. P1是慢指针,一次遍历一个节点.P ...
- SpringBoot集成Quartz实现定时器
SpringBoot+Quartz实现定时器,由于本人也是刚学习,不足之处请各位大神指正 .. 1.pom配置 <dependency> <groupId>org.sp ...
- springboot 使用yml配置文件自定义属性
springboot 中在application.yml文件里自定义属性值,配合@Value注解可以在代码中直接取到相应的值,如在application.yml中添加 mqtt: serverURI: ...
- Python中调用Linux命令并获取返回值
方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256/512表示未找到,该方法适用于she ...
- 小程序textarea设置maxlength后不是你想的那样简单
可能很多小伙伴们.看见这个标题后; 觉得作者是一个标题党. textarea设置maxlength后, 限制用户输入的字符呗! 还能怎么样呢? 恭喜你,说对了一半. 之前我也一直是这样想的. 知道今天 ...
- NAT444技术简介
嘛,最近老师布置了一道题目与NAT444技术相关,遂收集一波相关资料. 首先来一波名词解释: ICP:网络内容服务商(Internet Content Provider) BRAS:宽带远程接入服务( ...
- vuejs知识总结
1.Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.ViewModel是Vue.js的核心,它是一个Vue实例. <!DOCTYPE html& ...
- 「AGC020D」 Min Max Repetition
「AGC020D」 Min Max Repetition 传送门 首先这个东西的连续字符个数你可以二分.但事实上没有必要,这是可以直接算出来的. 即 \(k=\max\{\lceil\frac{A}{ ...
- ASP.NET 检测当前计算机及登录用户名
TextBox1.Text = System.Security.Principal.WindowsIdentity.GetCurrent().Name;