文章目录
新建线程的三种方式
方式一:继承Thread类
实现步骤:
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start(),start方法做了两件事 :①启动当前线程 ② 调用当前线程的run()
Thread类构造器
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
Thread(Runnable target, String name):创建新的Thread对象并指定线程实例名
代码演示
public class MyThread extends Thread {}
MyThread thread = new MyThread();
thread.start();
方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类A
- 实现类A中重写run()方法
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
代码演示
可以看到Runnable是一个函数式接口,这意味着我们可以使用Java 8的函数式编程来简化代码。
class MyThread2 implements Runnable{
@Override
public void run() {
// 写逻辑代码
}
}
// 创建target
MyThread2 mThread = new MyThread2();
//4. 将此target作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
方式三: Future和Callable
对比总结
Thread和Runnable的区别
我认为Thread和Runnable接口的区别有四个。
-
Thread是一个类,Runnable是接口,因为在Java语言里面的继承特性,接口可以支持多实现,而类只能单一继承。
所以如果在已经存在继承关系的类里面要实现线程的话,只能实现Runnable接口。 -
Runnable表示一个线程的顶级接口,Thread类其实是实现了Runnable这个接口,我们在使用的时候都需要实现run方法。
-
站在面向对象的思想来说,Runnable相当于一个任务,而Thread才是真正处理的线程,
所以我们只需要用Runnable去定义一个具体的任务,然后交给Thread去处理就可以了,这样达到了松耦合的设计目的。 -
接口表示一种规范或者标准,而实现类表示对这个规范或者标准的实现,所以站在线程的角度来说,Thread才是真正意义上的线程实现。
Runnable表示线程要执行的任务,因此在线程池里面,提交一个任务传递的类型是Runnable。
总的来说,在我看来,Thread只是实现了Runnable接口并做了扩展,所以这两者我认为没什么可比性。
run和start的区别
run()相当于线程的任务处理逻辑的入口方法,它由Java虚拟机在运行相应线程时直接调用,而不是由应用代码进行调用。
而start()的作用是启动相应的线程。启动一个线程实际是请求Java虚拟机运行相应的线程,而这个线程何时能够运行是由CPU调度决定的。start之后拿到cpu进入运行态,没拿到进入就绪态等待
sleep和wait的区别
总的来说,线程的 sleep() 方法和 wait() 方法有以下几点区别:
(1)sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。
(2)sleep() 方法不会释放 lock,但是 wait() 方法会释放,而且会加入到等待队列中。
(3)一般用wait和notify配合synchronized 锁一起实现多线程之间的同步,而sleep只是单纯的使得线程进入休眠状态。
(4)线程调用 sleep() 之后不需要被唤醒,当指定的休眠时间到了就会自动恢复运行状态,但是 wait() 方法需要被重新唤醒
wait和sleep是否会触发锁的释放以及CPU资源的释放?
-
Object.wait()方法,会释放锁资源以及CPU资源。
-
Thread.sleep()方法,不会释放锁资源,但是会释放CPU资源。
首先,wait()方法是让一个线程进入到阻塞状态,而这个方法必须要写在一个Synchronized同步代码块里面。
因为wait/notify是基于共享内存来实现线程通信的工具,这个通信涉及到条件的竞争,所以在调用这两个方法之前必须要竞争锁资源。
当线程调用wait方法的时候,表示当前线程的工作处理完了,意味着让其他竞争同一个共享资源的线程有机会去执行。
但前提是其他线程需要竞争到锁资源,所以wait方法必须要释放锁,否则就会导致死锁的问题。
然后,Thread.sleep()方法,只是让一个线程单纯进入睡眠状态,这个方法并没有强制要求加synchronized同步锁。
当然,如果是在一个Synchronized同步代码块里面调用这个Thread.sleep,也并不会触发锁的释放。
最后,凡是让线程进入阻塞状态的方法,操作系统都会重新调度实现CPU时间片切换,这样设计的目的是提升CPU的利用率。
yield和join的区别
join
很多时候,一个线程的输入可能非常依赖于另外一个线程的输出,此时,这个线程就需要等待依赖的线程执行完毕,才能继续执行。join()实现了这个功能
yield
yield()把正在运行的线程转为就绪态,放在就绪队列里面,等待cpu的调度。
以允许具有相同优先级的其他线程获得运行机会。
stop vs interrupt
stop 强制线程生命期结束并立即退出,是禁止使用的
interrupt方法不会强制线程立马退出,而是通知线程,告诉他你可以停止了,是否中断取决于正在运行的线程自身,所以它能够保证线程运行结果的安全性。
jdk实现的两种条件变量
Object下的wait和notify
wait和notify用来实现多线程之间的协调,wait表示让线程进入到阻塞状态,notify表示让阻塞的线程唤醒。
wait和notify必然是成对出现的,如果一个线程被wait()方法阻塞,那么必然需要另外一个线程通过notify()方法来唤醒这个被阻塞的线程,从而实现多线程之间的通信。
wait和notify为什么要在synchronized代码块中?
在多线程里面,要实现多个线程之间的通信,需要通过共享变量的方法来实现,也就是线程t1修改共享变量s,线程t2获取修改后的共享变量s,从而完成数据通信。
但是多线程本身具有并行执行
的特性,也就是在同一时刻,多个线程可以同时执行。在这种情况下,线程t2在访问共享变量s之前,必须要知道线程t1已经修改过了共享变量s,否则就需要等待。
同时,线程t1修改过了共享变量S之后,还需要通知在等待中的线程t2。
所以在这种特性下要去实现线程之间的通信,就必须要有一个竞争条件控制线程在什么条件下等待,什么条件下唤醒。
而Synchronized同步关键字就可以实现这样一个互斥条件,也就是在通过共享变量来实现多个线程通信的场景里面,参与通信的线程必须要竞争到这个共享变量的锁资源,才有资格对共享变量做修改,修改完成后就释放锁,那么其他的线程就可以再次来竞争同一个共享变量的锁来获取修改后的数据,从而完成线程之间的通信。
AQS下的Condition条件变量
Condition由ReentrantLock对象创建,并且可以同时创建多个,所以可以同时存在多个等待队列,存放被阻塞的线程
更多内容见本文
二者对比
api对比:
await= wait signal= notify signalAll =notifyAll