串行
串行是指执行多个任务时,各个任务按顺序执行
,完成一个之后才能进行下一个。
并发
单个处理器上来回切换任务(在物理层面并发其实还是串行,不过是在三个程序上快速的来回切换而不是一个一个执行,在用户看来就相当于三个程序同时执行,所以叫做并发!)
并行
并行指的是多个处理器同时处理多个任务
。单核处理器无法做到并行。并行一定是并发,但并发不一定是并行。
同步
所谓的同步就是某一段流程同时只能有一个线程执行,其它线程需要等待。
锁
公平锁vs非公平锁
公平锁
按照先来后到
的顺序排队获取资源,那么就是公平锁。
非公平锁
但假如两人并不排队,而是通过争抢获取资源
,那么这就是非公平锁
悲观锁vs乐观锁
悲观锁
认为每一次自己的操作大概率会有其它线程在并发,所以自己在操作前都要对资源进行锁定,这种锁定是排他的。悲观锁的缺点是不但把多线程并行转化为了串行,而且加锁和释放锁都会有额外的开支。
乐观锁
认为每一次操作时大概率不会有其它线程并发,所以操作时并不加锁,而是在对数据操作时比较数据的版本,和自己更新前取得的版本一致才进行更新。乐观锁省掉了加锁、释放锁的资源消耗,而且在并发量并不是很大的时候,很少会发生版本不一致的情况,此时乐观锁效率会更高。
守护线程
当 JVM 中没有任何一个非守护线程
时,所有的守护线程
都会进入到 TERMINATED
状态,JVM 退出。在 Java 中,当没有
非守护线程存在
时,JVM 就会结束
自己的生命周期
。而守护进程
也会自动退出
。守护线程一般用于执行独立的后台业务。比如 JAVA 的垃圾清理就是由守护线程执行。而所有非守护线程都退出了,也没有垃圾回收的需要了,所以守护线程就随着 JVM 关闭一起关闭了。
当你有些工作属于后台工作,并且你希望这个线程自己不会终结,而是随着 JVM 退出时自动关闭,那么就可以选择使用守护线程。
并发编程的三大特性
- 原子性:所有操作要么全部成功,要么全部失败。
- 可见性:一个线程对变量进行了修改,另外一个线程能够立刻读取到此变量的最新值。
- 有序性:代码在执行阶段,并不一定和你的编写顺序一致。
实现多线程的方法
根据oracle
官方文档,有两种方法可以创建新的线程。
方法一:
一种方法是将一个类声明为Thread
的子类,通过继承thread
类来定义线程,且重写thread
类中的run()
方法。
/**
* 用Thread方式实现线程
*/
public class ThreadStyle extends Thread {
@Override
public void run(){
System.out.println("用Thread类实现线程");
}
public static void main(String[] args) {
// 每new一个新线程,则会创建一个新的ThreadStyle实例对象
ThreadStyle thread1 = new ThreadStyle();
ThreadStyle thread2 = new ThreadStyle();
thread1.start();
thread2.start();
}
}
方法二:
创建线程的另一种方法是实现Runnable
接口,然后在实现类中实现run
方法。
/**
* 用Runnable方式创建线程
*/
public class RunnableStyle implements Runnable{
public static void main(String[] args) {
RunnableStyle runnableStyle = new RunnableStyle();
// 不管你开辟几个线程,他们都共享runnableStyle这一个实例
Thread thread1 = new Thread(runnableStyle);
Thread thread2 = new Thread(runnableStyle);
thread1.start();
thread2.start();
}
@Override
public void run() {
System.out.println("用Runnable方法实现了线程");
}
}
两种方法的比较
Thread
和Runnable
之间的基本区别是,通过继承Thread
类定义的每个线程都创建一个唯一
的对象,并与该Thread
相关联。另一方面,通过实现Runnable
接口定义的每个线程将共享
同一个对象。
比较 | THREAD | RUNNABLE |
---|---|---|
基本比较 | 每个线程将会创建不同的对象并与之关联。 | 多个线程将共享同一个对象 |
内存 | 因为每新建一个线程都会创建新的对象,因此需要耗费更多的内存 | 由于多个线程共享同一个对象,因此使用的内存更少。 |
扩展 | 在Java中,多重继承是不允许的,因此,在一个类继承了Thread 类之后,它就不能继承任何其他类了。 |
如果一个类定义了实现Runnable 接口,那么它还有机会继承一个类。 |
用法 | 只有当用户想要覆盖thread 类中的其他方法时,才必须继承thread 类。 |
如果您只想专门定制run 方法,那么实现Runnable 是更好的选择。 |
耦合 | 继承Thread 类引入了紧密耦合,因为该类即包含Thread 类模块的代码,同时也包括我们自己新建线程部分的代码 |
实现Runnable 接口引入了松散耦合,因为Thread 类模块的代码与我们自己新建线程部分的代码是分离的。线程控制逻辑在Thread类中,业务运行逻辑在Runnable实现类中,解耦更为彻底 |
总结:
- 线程到底有几种实现方式,从不同的角度看,会有不同的答案。
- 典型答案是两种,分别是实现
Runnable
接口和继承Thread
类。 - 但是,当我们在看原理时,你会发现,其实
Thread
类也是实现Runnable
接口,并且看Thread
类的run
方法,会发现其实那两种本质都是一样的,run
方法的代码如下:
@Override
public void run() {
if (target != null) { target.run();
} }
方法一和方法二,在实现多线程的本质上,并没有区别,都是实例化Thread
,并且提供其执行的run
方法。无论你是通过继承thread
还是实现runnable
接口,最终都是重写或者实现了run
方法。而你真正启动线程都是通过实例化Thread
,调用其start
方法来新建线程
。这两个方法的最主要区别在于run()
方法的内容来源:
- 方法一:最终调用
target.run()
; - 方法二:
run()
整个都被重写
- 然而还有其他的实现线程的方法,例如线程池等,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现
Runnable
接口和继承Thread
类。 - 结论: 我们只能通过新建
Thread
类这一种方式来创建线程,但是类里面的run
方法有两种方式来实现,第一种是重写run
方法,第二种实现Runnable
接口的run
方法,然后再把该runnable
实例传给Thread
类。除此之外,从表面上看线程池、 定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。
网友评论