之前认为有3种
新启线程- 只有两种方式
Callable这一种严格意义上讲不能算是
Thread源码:
1. There are two ways to create a new thread of execution. One is to
2. declare a class to be a subclass of <code>Thread</code>.
3. ......
4. The other way to create a thread is to declare a class that
5. implements the <code>Runnable</code> interface.
只有两种方式创建一个线程去执行
派生自Thread 这个类
实现一个Runnable接口,把接口实例化后交给一个线程去执行
看看Thread的构造方法:
构造方法并没有可以接收Callable这种参数的构造方法,一个都没有。
我们把Callable交给Thread的执行的时候,本质上是Callable的实例包装成了一个FutureTask,FutureTask又实现了RunnableFuture接口,RunnableFuture接口派生自Runnable接口,说到底Callable包装成Runnable交给了线程去执行的,本质上还是实现了Runnable接口。
Java里面线程的状态总共分为六种:
1.初始态:
new 出一个线程,并没开始执行,调用start()方法以后就开始Runnable运行状(就绪态,运行态)
2.运行态:
3.等待态:
Object.wait() 当前线程进入等待,如果没有唤醒,(Object.notify()/Object.notifyAll())就会一直等,会等待超时,
Object.wait(long) 传一个时间长度,时间到了没人唤醒,也要恢复
4.等待超时态:
Object.wait() 当前线程进入等待,如果没有唤醒,会等待超时
5.阻塞态:
当前线程调用了同步方法,调用了synchronized关键字修饰的代码块或者方法的时候,如果没有拿到锁,这个时候就进入阻塞态,重新获得锁就进入Runnable状态
6.消亡态:
run()结束、main()结束
问题:
规范定义:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
死锁的必要条件:
学术化死锁定义:
尝试拿到锁:
/**
*类说明:演示尝试拿锁解决死锁
*/
public class TryLock {
private static Lock No1 = new ReentrantLock();//第一个锁
private static Lock No2 = new ReentrantLock();//第二个锁
//先尝试拿No1 锁,再尝试拿No2锁,No2锁没拿到,连同No1 锁一起释放掉
private static void fisrtToSecond() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No1.tryLock()){
System.out.println(threadName
+" get 1");
try{
if(No2.tryLock()){
try{
System.out.println(threadName
+" get 2");
System.out.println("fisrtToSecond do work------------");
break;
}finally{
No2.unlock();
}
}
}finally {
No1.unlock();
}
}
//休眠一小段时间
//不休眠的话,拿锁的过程会拉长 活锁
Thread.sleep(r.nextInt(3));
}
}
//先尝试拿No2锁,再尝试拿No1锁,No1锁没拿到,连同No2锁一起释放掉
private static void SecondToFisrt() throws InterruptedException {
String threadName = Thread.currentThread().getName();
Random r = new Random();
while(true){
if(No2.tryLock()){
System.out.println(threadName
+" get 2");
try{
if(No1.tryLock()){
try{
System.out.println(threadName
+" get 1");
System.out.println("SecondToFisrt do work------------");
break;
}finally{
No1.unlock();
}
}
}finally {
No2.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("MainThread");
TestThread testThread = new TestThread("TestThread");
testThread.start();
try {
fisrtToSecond();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
TestThread get 2
MainThread get 1
MainThread get 2
fisrtToSecond do work------------
TestThread get 2
TestThread get 1
SecondToFisrt do work------------
如果代码中没有休眠一小段随机时间(把两段时间错开)Thread.sleep(r.nextInt(3));,那么会拿锁的过程会拉长,活锁。
A(1)<2> A 拿到1尝试拿2
B(2)<1> B拿到2尝试拿1
在不断拿锁释放锁子中进行下去—活锁
MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
MainThread get 1
MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
MainThread get 1
MainThread get 2
fisrtToSecond do work------------
TestThread get 2
TestThread get 1
SecondToFisrt do work------------
线程饥饿 : 线程总是拿不到CPU执行权
线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal可以让每个线程拥有一个属于自己的变量的副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离。
自己用map实现ThreadLocal:
/**
* 类说明:自己实现的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);
}
}
public class UseThreadLocal {
//static ThreadLocal threadLocal = new ThreadLocal<>();
//static ThreadLocal threadLocal2 = new ThreadLocal<>();
static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();
/**
* 运行3个线程
*/
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
String threadName = Thread.currentThread().getName();
threadLocal.set("线程"+id);
/*if(id==1) {
threadLocal2.set(id);//线程1才会执行
}*/
System.out.println(threadName+":"+threadLocal.get());
}
}
public static void main(String[] args){
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
Thread-0:线程0
Thread-1:线程1
Thread-2:线程2
这样的实现看似的的确确是让每一个线程拥有一个自己变量的副本,但是多个线程却会在这个大的map上面产生一个激烈的竞争,
JDK里面的ThreadLocal是怎么实现的呢?
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
把当前线程传给 getMap(t)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
点击查看threadLocals,进入了Thread.java,返回的ThreadLocalMap 是当前线程的一个成员变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 是在 ThreadLocal.java 里面定义的(ThreadLocalMap 和ThreadLocal 没有关系,把ThreadLocalMap 提出来也可以,静态内部类和普通的类没有差别)
ThreadLocalMap 里面定义了一个Entry型的数组
static class ThreadLocalMap {
......
private Entry[] table;
......
}
Entry型是ThreadLocal作为键,Object作为value
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
总结逻辑:
1.线程thread-1已经有了
2.在每个线程实例的内部产生一个threadLocalMap这样一个对象实例
3.threadLocalMap内部有一个Entry型的数组
4.当调用set方法时,就有了元素,元素的键就是threadLocal
什么要用一个数组呢?
上面的代码换成ThreadLocal,并且在id = 1时多set一个值
public class UseThreadLocal {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
//static MyThreadLocal threadLocal = new MyThreadLocal<>();
/**
* 运行3个线程
*/
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
String threadName = Thread.currentThread().getName();
threadLocal.set("线程"+id);
if(id==1) {
threadLocal2.set(id);//线程1才会执行
}
System.out.println(threadName+":"+threadLocal.get());
}
}
public static void main(String[] args){
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
结果是:
ThreadLocal是如何实现让每个线程有自己的副本呢?不是用一个容器来存,而是在每个线程内部自己保存变量的副本,同时一个线程是允许多个threadLocal型的变量的,所以内部就用数组来保存多个threadLocal型的变量