Java-并发基础
启动线程的方式
只有:
1、X extends Thread;
,然后X.start
2、X implements Runnable;
然后交给Thread
运行
有争议可以可以查看 Thread
源码的注释:
There are two ways to create a new thread of execution.
Callable
的方式需要塞进FutureTask
,FutureTask
最终也是继承了Runnable
的
线程的状态
Java中线程的状态分为6种:
-
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
-
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 -
阻塞(BLOCKED):表示线程阻塞于锁。
-
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。1
-
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
-
终止(TERMINATED):表示该线程已经执行完毕。
只有
sync
才是阻塞状态,拿不到锁被迫阻塞
死锁的四个必要条件
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
预防死锁
打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。
避免死锁常见的算法有有序资源分配法、银行家算法
- 解决死锁案例:
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*类说明:演示尝试拿锁解决死锁
*/
public class TryLock {
private static Lock lock1 = new ReentrantLock();//第一个锁
private static Lock lock2 = new ReentrantLock();//第二个锁
//先尝试拿lock1 锁,再尝试拿lock2锁,lock2锁没拿到,连同lock1 锁一起释放掉
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(lock1.tryLock()){
System.out.println(threadName
+" get lock1");
try{
if(lock2.tryLock()){
try{
System.out.println(threadName
+" get lock2");
System.out.println("fisrtToSecond do work------------");
break;
}finally{
lock2.unlock();
}
}
}finally {
lock1.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
//先尝试拿lock2锁,再尝试拿lock1锁,lock1锁没拿到,连同lock2锁一起释放掉
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(lock2.tryLock()){
System.out.println(threadName
+" get lock2");
try{
if(lock1.tryLock()){
try{
System.out.println(threadName
+" get lock1");
System.out.println("SecondToFisrt do work------------");
break;
}finally{
lock1.unlock();
}
}
}finally {
lock2.unlock();
}
}
Thread.sleep(r.nextInt(3));
}
}
private static class TestThread extends Thread{
private String name;
public TestThread(String name) {
this.name = name;
}
public void run(){
Thread.currentThread().setName(name);
try {
SecondToFisrt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread.currentThread().setName("TestDeadLock");
TestThread testThread = new TestThread("SubTestThread");
testThread.start();
try {
fisrtToSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上述案例为啥加了 Thread.sleep(r.nextInt(3));
?
不加会有活锁问题。加了可以缓解活锁。就是两个线程都在拿锁,但是都没拿到,拿了很多次才拿到
活锁
两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。
ThreadLocal
与Synchonized的比较
ThreadLocal和Synchonized都用于解决多线程并发訪问。可是ThreadLocal与synchronized有本质的差别。synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程訪问。而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
如何隔离的数据?
每个Thread
中都存着一个ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
查看ThreadLocal
的set
和createMap
函数
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
上述代码可知当前 Thread
和 ThreadLocalMap
就绑定在一起了
查看 ThreadLocal
的get
函数
public T get() {
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();
}
通过get
可以看到 getMap
获取的是当前Thread
的 ThreadLocalMap
,也就跟其他线程无关了
ThreadLocalMap
里面有 Entry
就是用来存放数据的
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
为什么不直接搞个Map存一下数据?
import java.util.HashMap;
import java.util.Map;
/**
* 类说明:自己实现的ThreadLocal
*/
public class MyThreadLocal<T> {
/*存放变量副本的map容器,以Thread为键,变量副本为value*/
private Map<Thread,T> threadTMap = new HashMap<>();
public synchronized T get(){
return threadTMap.get(Thread.currentThread());
}
public synchronized void set(T t){
threadTMap.put(Thread.currentThread(),t);
}
}
直接用一个Map虽然可以隔离,但是压力都给到了Map上,在多线程时对这个Map会有激烈的竞争