java并发编程之美-阅读记录1
1.1什么是线程?
在理解线程之前先要明白什么是进程,因为线程是进程中的一个实体。(线程是不会独立存在的)
进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程中的一个执行路径,一个进程中至少会有一个线程,进程中的多个线程共享进程的资源。
线程:是cpu分配的基本单位。
由上图可看出,一个进程中会有多个线程,多个线程共享堆和方法区,但是每一个线程都会有自己的栈和程序计数器。
为什么要将栈和程序计数器设置为线程私有的呢?
前边说线程是cpu执行的基本单位,而cpu一般是使用时间片轮转方式轮询占用的,所以当当前线程cpu时间片使用完毕后,要让出cpu,等待下一次轮到自己的时候在调用。
那问题就来了,线程是如何知道之前执行到哪了?
程序计数器就是为了记录之前让出cpu时执行到的地址,等下次再次执行时就可以从程序计数器中获取之前执行到的位置,继续向下执行。(程序计数器是不会记录native方法执行的地址的,它只记录java代码执行的地址),而栈则是存储现场的局部变量,一遍之后再次使用
1.2线程的创建和运行
三种方式:继承Thread、实现Runnable接口、使用FuthreTask方式(实现Callable接口)。
package com.nxz.blog.otherTest; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; public class TestThread { static class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("extend-Thread:"+this.getName());
}
} static class MyThread2 implements Runnable { @Override
public void run() {
System.out.println("implements-Runnable:"+Thread.currentThread());
}
} static class MyThread3 implements Callable<String> { @Override
public String call() throws Exception { System.out.println("implements-Callable:"+Thread.currentThread());
return "Callable接口";
}
} public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.start(); Thread t2 = new Thread(new MyThread2());
t2.start(); FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
try {
// 阻塞,等待执行完毕,并返回结果
String res = futureTask.get();
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} System.out.println("main-Thread");
}
}
上述代码执行结果:
extend-Thread:Thread-0
implements-Runnable:Thread[Thread-1,5,main]
implements-Callable:Thread[Thread-2,5,main]
Callable接口
main-Thread
创建线程后会调用start方法,代表当前线程已经处于就绪状态(也就是说该线程已经获取了除cpu资源外的其他资源,等待cpu执行时间片),当获取了cpu资源后,该线程才处于运行状态,一旦run方法(对于extent Thread来说)执行完毕,该线程就处于终止状态。
使用继承的好处:在run方法内可以直接使用this来获取当前线程(而另两个的this则没有getName等一些方法),而不需要Thread.currentThread()。
1.3线程通知和等待
以下三个方法都是Object类的方法,为所有对象公用
①wait()
当一个线程调用wait()方法后,该线程就会被阻塞挂起,知道被notify/notifyAll调用或者其他线程调用了intercept方法中断了该线程
②wait(long timeout)
表上边一个多了一个超时时间,在等待期间如果没有其他线程唤醒(notify/notifyAll)该线程,该线程仍然会在超过timeout时间后自动返回
③wait(long timeout, int nanos) :两个参数,毫秒timeout,纳秒nanos,其内部还是调用的wait(long timeout)方法,只不多对于第二个参数纳秒,如果其范围是大于0,小于一百万的话(1毫秒= 100 0000纳秒),则将timeout加一秒
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
④notify()/notifyAll()
唤醒在同一个共享变量上等待的线程,区别:一个唤醒一个,一个唤醒所有
需要注意的:
*虚假唤醒:
一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被中断,或者等待超时,这就是虚假唤醒
(在java并发编程之美中这样描述的,感觉不太清楚,找了其他资料有的是这样描述的:在多核处理器下,其他线程的信号可能会唤醒多个线程(阻塞在同一个条件变量上的线程),结果就是,多个被唤醒的线程,前一个执行代码逻辑后,另外的线程再次执行同样的代码后会出现问题)
例子:在下边代码中分别为生产者和消费者,假如在一个生产者和一个消费者的情况下(也就是单线程),是没有问题的,不存在虚假唤醒,假如在多线程环境下,当一个生产者完成之后,调用notifyAll方法后,会唤醒其他生产者和所有的消费者,当第一个消费者消费完成之后,在有消费者消费,此时就会出现问题(也就是出现了虚假唤醒),解决该问题的方法,就是将消费者和生产者中的条件判断if(queue.size() == 1) 和if(queue.isEmpty()) 改为 while( queue.size() == 1) 和while( queue.isEmpty() ) ,即改为循环判断,当被唤醒是,每一个线程都重新判断是否符合条件
static Queue queue; static class ProductThread extends Thread {
@Override
public void run() {
synchronized (queue) {
if (queue.size() == 1) {// 假设队列最多存一个对象
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} queue.add(new Object());
queue.notifyAll();
}
}
} static class ConsumerThread extends Thread {
@Override
public void run() { synchronized (queue) {
if (queue.isEmpty()) {
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} queue.poll();
queue.notifyAll();
}
}
}
1.4等待线程执行完毕 的join方法
当调用join方法(该方法属于Thread类)时,就会等待线程执行完毕后,在继续向下运行
例子:
package com.nxz.blog.otherTest; public class TestThread002 { static class MyThread extends Thread{
@Override
public void run() {
System.out.println("myThread");
}
} public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.setName("myThread");
t1.start(); // 如果注释掉 下边代码的话,main方法的输出应该会是 先main-thread 然后再试mythread,
// 放开的话 main主线程就会等待t1线程执行完毕后,在向下运行
t1.join(); System.out.println("main-thread");
}
}
1.5sleep方法休眠
Thread中的静态方法sleep(),当一个线程调用了sleep方法后,会短暂的让出cpu执行权(也就是不参与cpu的调度),但是线程拥有的锁等资源是不释放的,这一点和wait方法不同
1.6让出cpu执行权yield方法
Thread中的静态方法yield(),当一个线程调用了Thread.yield()时,表名当前线程放弃cpu的使用权,yield之后,当前线程处于就绪状态。线程调度器会从包括该线程的所有线程中随机选出一个线程分配cpu执行时间片。
1.7中断线程
java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接中断线程,而是被中断的线程根据中断标志自行处理。
void interruupt() :中断线程
boolean isInterrupted() :检测当前现车鞥是否中断(调用isInterrupted()方法的实例的中断标志)
boolean interrupted(): 检测当前线程是否被中断(内部是获取当前线程的中断标志而不是调用interruupted()方法的实例的中断标志)
1.8线程的上下文切换
在多线程编程中线程的个数一般都是大于cpu的个数的,而每个cpu在同一时刻只能被一个线程使用。为了让用户感觉实在同时执行,cpu资源的分配采用时间片轮询的侧率。
上下文切换:就是一个线程使用完时间片之后,将cpu资源让给其他线程使用,并且线程状态转变为就绪状态。
1.9线程死锁
多个线程在执行过程中,因争夺资源而造成相互等待的状态,如没有其他因素,线程会一直等待,而无法继续运行。
死锁产生的条件:1、互斥 2、请求并持有资源 3、不可剥夺 4、环路等待
1.10守护线程和用户线程
守护线程:类似垃圾回收线程
用户线程:main函数启动,就是一个简单的用户线程
jvm停止的时机:在用户线程全都执行完毕后,jvm就会停止(无论有没有守护线程)
1.11ThreadLocal
当创建了一个ThreadLocal变量之后,多个线程操作这个变量时,实际上操作的是自己本地内存里的变量,也就是说每个线程都会复制一份变量到自己的本地内存。
THreadLocal是怎么和线程关联起来的呢?
ThreadLocal其实就是一个空壳,当调用ThreadLocal的set或get方法时,其内部是通过set方法把value放到调用线程的threadlocals变量中(ThreadLocal.threadLocalMap),也就是说本地变量不是存在ThreadLocal实例中,而是存入Thread中的threadlocals中。
public void set(T value) {
Thread t = Thread.currentThread();
// 当调用ThreadLocal的set方法时,其实是获取的是当前线程Thread中的ThreadLocalMap变量,如果变量为null,则新建一个ThreadLocalMap,如果变量存在,则之间将当前线程和value存入定制的map中(ThreadLocalMap)
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 从源码getMap中可以看出,其实获取的是当前线程中的threadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public T get() {
// ThreadLocal的get方法,同样是获取的是当前线程Thread中的threadLocals变量(也就是ThreadLocalMap类型的数据)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
java并发编程之美-阅读记录1的更多相关文章
- java并发编程之美-阅读记录11
java并发编程实践 11.1ArrayBlockingQueue的使用 有关logback异步日志打印中的ArrayBlockingQueue的使用 1.异步日志打印模型概述 在高并发.高流量并且响 ...
- java并发编程之美-阅读记录2
2.1什么是多线程并发编程 并发:是指在同一时间段内,多个任务同时在执行,并且执行没有结束(同一时间段又包括多个单位时间,也就是说一个cpu执行多个任务) 并行:是指在单位时间内多个任务在同时执行(也 ...
- java并发编程之美-阅读记录10
同步器 10.1CountDownLatch 在开发过程中经常会遇到在主线程中开启多个子线程去并行执行任务,并且主线程需要等待子线程执行完毕后在进行汇总.在CountDownLatch出现之前使用线程 ...
- java并发编程之美-阅读记录7
java并发包中的并发队列 7.1ConcurrentLinkedQueue 线程安全的无界非阻塞队列(非阻塞队列使用CAS非阻塞算法实现),其底层数组使用单向列表实现,对于出队和入队操作使用CAS非 ...
- java并发编程之美-阅读记录6
java并发包中锁 6.1LockSupport工具类 该类的主要作用就是挂起和唤醒线程,该工具类是创建锁和其他工具类的基础.LockSupport类与每个使用他的线程都关联一个许可证,在默认情况下调 ...
- java并发编程之美-阅读记录5
java并发包中的并发List 5.1CopeOnWriteArrayList 并发包中的并发List只有CopyOnWriteArrayList,该类是一个线程安全的arraylist,对其进行的修 ...
- java并发编程之美-阅读记录4
java并发包中的原子操作类,这些类都是基于非阻塞算法CAS实现的. 4.1原子变量操作类 AtomicInteger/AtomicLong/AtomicBoolean等原子操作类 AtomicLon ...
- java并发编程之美-阅读记录3
java并发包中的ThreadLocalRandom类,jdk1.7增加的随机数生成器 Random类的缺点:是多个线程使用同一个原子性的种子变量,导致对原子变量的更新产生竞争,降低了效率(该类是线程 ...
- Java并发编程之美之并发编程线程基础
什么是线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程,进程的多个线程共享进程的资源. java启动main函数其实就 ...
随机推荐
- html的q标签、blockquote标签
九层之台,起于垒土 一.<q> 定义和用法 <q> 标签定义短的引用.浏览器经常在引用的内容周围添加引号. <html> <body> <p> ...
- jq中的ajax传参
一. jq中的Ajax传参有两种 1.通过url地址来传参 2.通过data来传递参数 1. url来传递参数 function GetQuery(id) { | ...
- Linux知识-不断更新2
为了自己看的更清楚,也为了不断的总结,每次更新后都会另发一篇. 工作中遇到某一文件夹磁盘空间不够,当然每次都是清理日志,最后发现还是不太行,还不能扩容,只能先想办法迁移目录,避免此问题发生,但在这之前 ...
- Linux知识-不断更新
找到使用cpu最高的进程之使用cpu最高的线程的16进制号 shell命令行: ps -eo %cpu,pid | sort -n -k1 -r |head -n 1|awk '{print$2}'| ...
- [Luogu1821][USACO07FEB]银牛派对Silver Cow Party
由题意可知,我们需要求的是很多个点到同一个店的最短距离,然后再求同一个点到很多个点的最短距离. 对于后者我们很好解决,就是很经典的单源最短路径,跑一边dijkstra或者SPFA即可. 然而对于前者, ...
- python关于window文件写入后,换行默认\r\n的问题
因为python兼容各种平台,所以当在window打开文本文件写入后,换行会默认写成\r\n linux是\n 如果想去掉换行的\r 解决方法:在open函数里写入换行要求即可 with open(f ...
- 人生苦短_我用Python_OS对目录/文件操作_005
# coding=utf-8 import os # 操作文件和目录 ", os.getcwd()) # 获取当前文件的目录 ", os.path.realpath(__file_ ...
- Graphics 绘图
Graphics类提供基本绘图方法,Graphics2D类提供更强大的绘图能力. Graphics类提供基本的几何图形绘制方法,主要有:画线段.画矩形.画圆.画带颜色的图形.画椭圆.画圆弧.画多边形等 ...
- Bootstrap的本地引入
今天用前端框架时选择了Bootstrap,然后东西都下好了本地就是引入不进去. 查了一下发现必须jquery要在BootStrap之前引入,然后我更改了引入顺序,发现还是不行 <script s ...
- phpstudy的80端口被占用问题
1.查看电脑中当前程序占用的端口和程序ID 当phpstudy设置端口号为80时显示占用,在命令行中输入:netstat -ano 就可以看到当前电脑中程序占用的端口和程序ID等等信息 2.查看正在运 ...