一、 基础概念
1、什么是进程
进程是系统资源分配的独立实体、每个进程都拥有独立的地址空间。
2、什么是线程
线程是CPU调度的最小单位,必须依赖进程而存在,每个线程还拥有自己的寄存器和栈
3.、Cpu时间片轮转机制
cpu轮转分配时间片给线程队列,调度过程需要上下文切换
4、并发编程的利弊
好处:充分利用cpu资源,加快用户响应的时间,程序模块化,异步化
弊端:可能发生线程安全问题、死锁、线程太多拖垮cpu
二、什么是线程安全?
在多线程环境中,能够保证程序的正确性,和我们预期是一样的,它就是线程安全的
原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
可见性:一个线程对主内存的修改可以及时的被其他线程观察到
有序性:在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。
三、 如何在Java中实现线程
主要有以下7种创建方式
- 继承Thread类
public class Demo1 extends Thread {
@Override
public void run() {
System.out.println(this.getName());
}
public static void main(String[] args) {
new Demo1().start();
}
}
- 实现Runnable接口
public class Demo2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
new Thread(demo2, "t1").start();
}
}
- 实现Callable接口
/**
* 异步获取结果
*/
public class Demo3 implements Callable<String> {
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
FutureTask<String> futureTask = new FutureTask(demo3);
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println("result is " + result);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + " is running");
return "结果";
}
}
匿名内部类方式启动
Lambda方式启动
TimeTask方式启动
TimeTask实现了Runnable接口,Timer内部有个TimerThread继承自Thread,故实际还是Thread + Runnable
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate((new TimerTask() {
@Override
public void run() {
System.out.println(LocalDate.now());
}
}), 5000, 3000);
}
- 线程池启动多线程
/**
* 线程池
*/
public class Demo4 implements Runnable {
/**
* ExecutorService的4种方式:
* 1.ExecutorService newFixedThreadPool(5);
* 2.ExecutorService newCachedThreadPool();
* 3.ExecutorService newSingleThreadExecutor();
* 4.ScheduleExecutorService newScheduledThreadPool();
*/
@Override
public void run() {
System.out.println("Thread Pool");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(new Demo4());
}
}
思考:用Runnable还是Thread
Java是不支持类的多重继承的,但是可以实现多个接口,所以如果需要继承其它类时,使用Runnable更好
Java中的Runnable和Callable有什么不同
Runnable和Callable都代表那些要在不同的线程中执行的任务、它们的主要区别是Callable的Call()方法可以返回值和抛出异常,而Runnable的Run()方法没有这些功能
五、线程的生命周期
线程的生命周期的5种状态:新建、就绪、运行、阻塞、终止
-
新建(New)
线程对象创建后,便进入新建状态
-
就绪(Runnable)
调用线程对象的start()方法后,便处于就绪状态,此时线程等待Cpu调度执行
-
运行(Running)
当就绪的线程被调度并获取Cpu资源时,便进入运行状态,执行run()方法中的操作
-
阻塞(Blocked)
处于运行状态的线程,因为某些原因导致线程编程阻塞状态,比如执行sleep()、wait()、join()之后都会处于阻塞状态
需要注意一点
sleep()不会释放cpu资源,而wait将会释放cpu资源,让其它线程执行
终止(Terminated)
线程执行结束,或者出现异常情况导致线程提前终止,那么线程的生命周期都将结束
为了方便大家理解阻塞状态,这里在上一个图,之所以没在画一起是因为放到一起会有一种很乱的感觉,不容易阅读
下一篇:我们细说Synchonized和JMM volatile 缓存一致性