前言
上一讲中我们讲了什么是线程,这一讲我们细细讨论一下关于线程的那些事。
线程的生命周期
先看看线程的状态转换图。
1.首先线程被创建出来。
2.进入就绪队列中等待cpu分配时间片(这里的时间片指的是cpu允许线程执行的最大时间),若在规定时间内未执行完成,则线程继续进入就绪队列等待cpu分配时间片。
3.线程进入运行状态,若没有线程占用锁,则线程拿到锁以后对资源进行处理,然后线程执行结束并销毁线程,但是若在执行在这过程中有其他线程先行一步拿到了锁,则线程进入等待队列中等待,等待线程释放锁,并将线程从等待对列中唤醒,此时线程进入就绪队列中继续等待cpu重新分配时间片
线程安全性
上面我们提到了锁,那么什么是锁呢,在此之前我们先谈一下线程的安全性问题,若线程在运行过程中所操作的资源被另一个线程所修改,我们就说这种情况是线程不安全的。就像你在网吧打游戏,玩得正嗨,突然网管告诉你,你现在不能上了,别人要玩你这台电脑,你是不是会有想打人的冲动,
因此为了保证线程的安全性我们需要对线程加锁,既保证线程必须等待其他线程释放锁以后,才能继续执行。
线程不安全的三要素
1.多线程环境(废话)
2.存在共享资源(也就是这个资源,所有线程都可以操作)
3.对共享资源的操作属于非原子性操作(例如 i++、++i这种)
大家不理解的可能是第三点,非原子性操作,什么是非原子性操作呢,即是对一个资源的操作不可以拆分比如像 int x;int i=1;像x=i++这种操作就是非原子性的,可以分成两部分执行可以,x=i;i=i+1;
线程创建方式常用的有5种
1.继承Thread类
public class Demo1 extends Thread {
public Demo1(String name) {
super(name);
}
@Override
public void run() {
while(!interrupted()) {
System.out.println(getName() + "线程执行了 .. ");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Demo1 d1 = new Demo1("first-thread");
d1.start();
}
}
2.实现Runnable接口
public class Demo2 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("thread running ...");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new Demo2());
thread.start();
}
}
3.使用匿名内部类
public class Demo3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable");
}
}
).start();
}
}
4.实现Callable接口
public class Demo4 implements Callable<Integer> {
public static void main(String[] args) throws Exception {
Demo4 d = new Demo4();
FutureTask<Integer> task = new FutureTask<>(d);
Thread t = new Thread(task);
t.start();
System.out.println("我先干点别的。。。");
Integer result = task.get();
System.out.println("线程执行的结果为:" + result);
}
@Override
public Integer call() throws Exception {
System.out.println("正在进行紧张的计算....");
Thread.sleep(3000);
return 1;
}
}
5.使用线程池创建线程
public class Demo5 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
threadPool.shutdown();
}
}
第一种和第二种大家都很常见啦,他们的区别在于一个是继承,一个是实现某个接口,相比之下第二种类和类的耦合性更低,更灵活,更容易扩展
第三种是通过匿名内部类的方式创建线程
重点讲一下第二种和第四种的区别
共同点:两者都是实现的接口
不同点是:第二种不能够抛出异常,且没有返回值,第三种能抛出异常且存在返回值
第五种,就是我们使用线程池创建线程的方式,使用线程池可以减少线程创建和销毁以及线程上下文切换带来的开销问题。
在以后的博客中会重点讲述 Callable接口和线程池的一些问题的,尽请期待!!
总结
这篇博客主要讲了线程的生命周期以及线程的常见创建,还简单谈了一下线程的安全性问题,这三个话题是面试中经常被问到的,希望引起大家重视!!!
最后送自己一句话也是送各位朋友的一句话,今天你是谁不重要,重要的是你以后是谁,虽然现在大家看起来都差不多,但是两年、五年后差距就慢慢出来了,学习这种事情就在于积累,你可能学历不高,专业不对口,但你只要肯努力,这一切都不是问题,希望大家都能有所收获,那么我写这篇博客的意义就有了,最后感谢大家百忙中抽出时间来看我写的博客,本人能力有限,可能有些东西描述不太恰当,希望大家多多谅解!!!