谈谈对线程的理解
简单来说,一程序可以调用多个进程,比如一个视频播放器程序,里面就存在两个进程:一个是播放视频的进程,一个是下载上传视频的进程。
一个进程又同时调用多个线程,这个线程是隐藏的,用进程管理器看不到,可用其它的进程管理软件来查看。
三者的逻辑关系是程序调用进程,进程调用线程,一般来说程序下面都是多进程,不同的进程分工不同;进程下面也基本上是多线程的。
可以这样下定义:进程是系统进行资源分配和调用的独立单位,每一个进程,都由它自己的内存空间和系统资源
线程是进程的执行单元,执行路径,线程也是程序使用CPU的最基本单位
run方法和start方法的区别
run方法相当于普通方法,并没有实现多线程程序中依然只有一个主线程。相当于调用Thread类中的run方法。
用start方法来启动线程,真正实现了多线程,无需等待run方法执行完毕。而是直接执行下面的代码。执行start()方法,可以在主线程中重新创建一个新的线程,等得到CPU的时候就回去执行对应的run方法内的代码。
ThreadLocal
ThreadLocal,顾名思义,为线程的局部变量,该变量只能让当前线程访问,独立于其他线程,可以保证并发环境下数据的安全。
//private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i){
this.i = i;
}
@Override
public void run() {
// try {
// Date t = sdf.parse("2018-04-10 16:00:" + i%60);
// System.out.println(i + ":" + t);
// } catch (ParseException e) {
// e.printStackTrace();
// }
if(tl.get() == null){
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
try {
Date t = tl.get().parse("2018-04-10 16:00:" + i%60);
System.out.println(i + ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i = 0;i < 1000;i++){
es.execute(new ParseDate(i));
}
es.shutdown();
}
主要是两个方法
- void set(Object value)
设置当前线程的线程局部变量的值。 - public Object get()
该方法返回当前线程所对应的线程局部变量。
通过ThreadLocal对象实例调用get()和set()方法,首先就是获得线程对象,然后拿到线程对象的ThreadLocalMap对象,最后对该Map对象进行存取。因为每个线程都有一个自己的map容器,所以做到了线程独享。
ThreadLocal使用场景
不需要共享的变量。比如Web中,每个用户的requestid不一样,使用ThreadLocal声明就可以有效的避免线程之间的竞争,无需采取同步措施,因此可以简单的理解为用空间换时间。
深入理解ThreadLocal
Java并发编程:深入剖析ThreadLocal
Java创建线程的方式?哪种比较好
有四种方式创建线程
- 继承Thread类
- 实现Runnable接口
- 使用线程池创建
- 实现Callable接口
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去处理同一个资源
- 可以避免Java中单继承的限制
- 线程池只能放入实现Runnable或Callable的线程,不能直接放入继承Thread的类
- runnable 实现线程可以对线程进行复用, 因为 runnable 是轻量级的对象, 重复 new 不会耗费太大资源, 而 Thread 则不然, 它是重量级对象, 而且线程执行完就完了, 无法再次利用。
说一下死锁?
-
什么是死锁?
死锁是指多个进程因相互竞争资源(相互等待)而造成的一种僵局,若无外力作用,这些进程都将无法向前推进。 -
死锁的四个必要条件?
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内,某资源只能被一个线程占用,其他线程如果想请求,只能等待。
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
不可剥夺条件:进程所获得的资源不能被其他进程强行夺走,只能主动释放。
循环等待条件:许多进程形成了首尾相接循环等待资源的关系。 -
死锁预防
通过破坏死锁四个必要条件来预防死锁。由于资源互斥是资源使用的固有特性,所以互斥性无法破坏。- 破坏“不可剥夺”条件: 一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到 系统的资源列表中,可以被其他的进程使用,而等待的进程只有重新获得自己原有的资源以及新申请的资源才可以重新启动,执行。
- 破坏”请求与保持条件“:第一种方法静态分配即每个进程在开始执行时就申请他所需要的全部资源。第二种是动态分配即每个进程在申请所需要的资源时他本身不占用系统资源。
- 破坏“循环等待”条件:采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。
-
避免死锁
- 加锁顺序(线程按照一定的顺序加锁)
如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生 如果一个线程需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之后,才能获取后面的锁。 - 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行 - 死锁检测
- 加锁顺序(线程按照一定的顺序加锁)
多线程共用一个数据变量时需要注意什么?
- 如果多个线程对数据只有读没有写的话,不用进行特殊操作
- 如果既有读又由写,那么需要保证线程安全,防止脏读,可以使用volatile关键字保证内存可见性,如果这还不能保证线程安全的话,那么就要使用加锁来保证操作的可见性和原子性来保证线程安全。
网友评论