线程
线程是进程中的一个单一顺序的执行单元,也被称为轻量进程(lightweight process)。线程有自己的必须资源,同时与其他线程进程共享全部资源。同一个进程中,线程是并发执行的。由于线程的之间的相互制约,线程有就绪、阻塞、运行、结束等状态。
一 新建线程
我们先创建两个线程类:
新建一个MyThread类 继承 Thread来覆盖 run方法:
新建MyRunnable类 实现Runnable 接口:
创建以及执行两个线程类
二 验证线程并发
在上述创建的MyThread类和MyRunnable类各自的run方法输出上 加个for循环for(int i=0;i<10000;i++);
如果是顺序执行,应该会是 extendsThread全部输出完 ,才输出implement thread。但是这并不是而是出现交叉结果 证明线程是并发执行。
交叉原理就是每个代码 在cpu时间片内执行一段时间,到时间就换一个代码 在cpu时间片内执行,但是如果时间片内打印完了,就看不见交叉现象。
三 线程声明周期(或者线程状态)
java中线程基本上有以下图片中的几种状态:
首先我们要做一个输出线程状态的类:
线程状态
public static void printThreadState(Thread thread){
Thread.State state=thread.getState();
switch(state){
//1.新建状态
caseNEW:
info(thread.getName()+" state is NEW");break;
//2.执行状态
case RUNNABLE:
info(thread.getName()+" state is RUNNABLE");break;
//3.等待超时状态
case TIMED_WAITING:
console.info(thread.getName()+" state is TIMED_WAITING");break;
//4.等待状态
case WAITING:
info(thread.getName()+" state is WAITING");break;
//5.阻塞状态
case BLOCKED:
info(thread.getName()+" state is BLOCKED");break;
//6.结束状态
caseTERMINATED:
info(thread.getName()+" state is TERMINATED");break;
default:
info
(thread.getName()+" state is UNKOWN");
}
}
//打印
public static void info(Object o){
System.out.println(String.valueOf(o));
}
1.新建状态:
new之后 start之前都属于NEW状态,一旦状态变成其他,则不会再变成NEW状态。
public static void main(String args[]) throws InterruptedException{
Thread t=new Thread(new Runnable() {
@Override
public void run() {
}
});
printThreadState(t);//查看new之后的thread状态
}
2.执行状态
在run()方法里的时候,线程处于执行状态。一旦run()方法执行完毕,则状态就不是执行状态了
3.等待超时状态
当调用wait(long timeout)方法的时候,线程处于等待状态,并且超时后并不马上返回,而是等待获取锁后返回。
验证超时后并不马上返回,而是等待获取锁后返回。
所以我们会发现一个问题,wait的超时只是一个预想, 多少时间后我再去获取锁,如果拿到就返回 拿不到就处于等待状态。这里就会发现这里等待是最少需要long timeout时间。
wait(等多少时间后 再去竞争锁),wait的时间内 我不去竞争 跟别人抢锁,所以这个时间 不保证锁一定能抢回来。
注意:Thread.sleep 也会进入TIME_WAITING状态。
5.等待状态
wait()睡眠不参与抢锁,等待notify来进行唤醒。
wait 也可以通过interrupt唤醒,interrupt是中断线程的方法。
5.阻塞状态
如果获取不到锁,就一直在阻塞状态,直到获取锁为止。
6.结束状态
在run()方法执行结束以后,线程处于结束状态
但是当这个线程执行结束了之后,是否代表这个线程被销毁了呢。然而只是线程执行单元销毁了,但是线程对象还在。虽然这个线程对象还存在,但是它已经不能进行再次的start()方法进行执行了。
四 研究多线程锁问题
经典并发问题:当运行一万个线程想List插入的时候,预期结果是有10000个,但是输出确不是10000个,而是少于1000个。原因是 线程并发执行获取list的长度是过期的。
比如我拿到list ,你也拿到list ,咱俩同时看list里有1条数据 我把数据插入第二个位置 ,你也插入第二个位置 ,就会导致list最终结果只有2条数据,实际上插入了3条数据。
public static void ThreadMuliteTest() throws InterruptedException {
final List list=new ArrayList();
for(int i=0;i<10000;i++){
new Thread(new Runnable() {
@Override
public void run() {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
这就是线程并发问题,并发问题有个解决办法 就是对线程进行枷锁让他排队,我拿到了 你就不能拿。
synchronized 代码段是java给线程的一个控制权,可以锁住一个资源,防止被其他人用。加锁之后线程就进入了 排队状态 ,只有一个线程能拿到list的权限 其他等待。
还有一种操作 就是我得到资源后 ,可以释放一段时间给别人用 ,超时了 我在拿回来自己用。
public static void ThreadGetWait() throws InterruptedException {
Object lock=new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
console.info("线程一:拿到锁");
try {
lock.wait(100);//释放100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
console.info("线程一:锁又被我拿回来了");
try {
Thread.sleep(1000);//睡1s 锁不给别人
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread.sleep(10);//休眠10ms防止第二个线程拿到锁
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
console.info("线程二:拿到锁");
try {
Thread.sleep(2000);//拿到锁后我要休眠2s
} catch (InterruptedException e) {
e.printStackTrace();
}
console.info("线程二:锁又回来了?");
}
}
}).start();
}
因为线程1先运行 肯定拿到锁,线程一暂时释放。所以线程二紧接着拿到了锁,线程二休眠了2s 期间并没有释放锁,所以线程1一直处于阻塞状态,线程二执行完成后 释放锁 线程一 打印 最后一句。