1.线程的五个状态
2.线程图
3.多线程的实现方式
4.Thread常用方法
1.新建:用new实例化一个线程对象。
2.可运行:线程对象创建后,调用start()方法,通知jvm后,等待被线程调度选中。
3.运行:可运行状态的线程被选中后,获得cpu的时间轮片,开始执行run()方法,处于运行状态。
4.阻塞:阻塞状态指线程因某种原因放弃cpu的使用权,暂停运行。
<1>等待阻塞:wait()方法后(发生在synchronized同步块内),放弃持锁,等待其他持锁线程notify()唤醒
<2>同步阻塞:进入synchronized同步块时,无法持锁则被阻塞,等待持锁线程释放锁。
<3>其他阻塞:sleep()方法的持锁阻塞,join()方法的阻塞等待。
5.死亡:线程的run()方法,主线程main()方法结束,或者线程运行时遇到异常退出,则线程死亡。
随时手画温习:
1.继承Thread类,重写run()方法
Thread类实际也实现了Runnable方法,重写run()方法后,即可通过start()方法来启动线程。
2.实现Runnable接口,重写run()方法,并实例化一个对象传入到Thread实例中
1和2本质上是一样的,即通过实现Runnable接口来重写run()方法,然后实例化Thread对象来操纵线程。
3.实现Callable接口,重写call()方法,通过线程池来执行实现了callable的实例对象
Callable方法是可以提供返回值的线程实现,run()方法是void型的,而call()方法带有返回值,在通过线程池执行后,会返回一个Future对象,然后可以通过Future对象的一些方法,得到call()的返回值。
->这个问题可以转移的点有:
1.通过分析继承Thread和实现Runnable的选择,来谈接口与继承的区别。
2.通过谈到Callable接口,来详细说Callable,Future,FutureTask这几个类与其中的方法
3.通过谈到Callable的实现需要通过线程池,然后去讲线程池。
1.start()方法与run()方法
start()方法是一个本地方法,它将启动一个线程来执行run()方法,调用start()方法以后,线程变成可运行状态,等待JVM的调度。
如果直接调用run()方法,那么这个run()方法就是一个普通方法,不会产生新的线程。
2.sleep()方法
sleep()方法是Thread类的静态方法,可以在指定毫秒数内让当前正在执行的线程休眠。
sleep()通过对象来调用和通过类来调用是一样的,关键在于是在哪个线程里面调用它,理解了暂停当前线程是什么意思,才能知道sleep()要怎么用。
sleep()如果是发生在同步块内,那么持锁着被sleep()暂停后不释放锁。
一开始很困惑当前线程,因此写一段代码来解释当前线程的含义。
public class Main
{
static class Test implements Runnable
{
public void run()
{
try {
System.out.println("2当前线程:"+Thread.currentThread());
Thread.sleep(2000); //这里的当前线程是以Test实例对象为参数的线程
}catch(Exception e) {}
}
}
public static void main(String[] args)
{
//对于下面所有代码来说,当前线程都是主线程main
Thread t1 = new Thread(new Test());
Thread t2 = new Thread(new Test());
System.out.println("1当前线程:"+Thread.currentThread());
//在这里调用Thread.sleep()/t1,sleep()/t2.sleep(),暂停的都是main
t1.start();
//这里也同上
t2.start();
//这里也同上
}
}
3.join()方法
join()方法是一个将几个异步执行的线程再次改回串行(按顺序)执行的方法。
这里说的异步执行,意思等同于相互不干扰的执行,也可以说是同时执行。而串行则是按顺序一个接一个执行。
在一个线程中调用另一个线程的join()方法,则会阻塞当前线程,并等到调用join()的线程执行完,才接触当前线程的阻塞。
如果在主线程main中调用另一个线程的join,则会阻塞main,然后等到这个线程完成,才继续执行后面的内容。
如果在线程T1中调用线程T2的join,则T1进入阻塞,开始执行T2,等T2执行完,T1恢复,再次被调度以后执行T2.join()后面的代码。
用join()方法也可以实现一些简单的同步
4.interrupt()方法
这一系有三个方法:
void interrupt(),boolean interrupted(),boolean isInterrupted(),
interrupt()用来设置中断标志为true,当该线程的中断标值为true时,如果该线程处于阻塞(该线程是sleep()暂停的当前线程、是join()阻塞的当前线程、),那么该线程会抛出一个异常,然后退出阻塞(之后到哪里就看具体代码,比如阻塞语句处有捕获,则会捕获异常跳到catch语句)。但是interrupt()不能跳出由竞争同步锁产生的阻塞,所以synchronized的阻塞存在无限等待可能。
isInterrupted()是用来返回相应线程的中断标值情况的,只返回,不更改值。
interrupted()又是个静态方法,则代表它的作用对象是当前线程,用来返回当前线程此刻的中断标值,之后将值置为false
写一段实验代码:
public class Main
{
public static void main(String[] args) throws Exception
{
Object o = new Object();
Thread th1 = new Thread(new Runnable() {
public void run() {
synchronized(o) {
long begintime = System.currentTimeMillis();
try {
Thread.sleep(5000);
}catch(Exception e) {}
long endtime = System.currentTimeMillis();
System.out.println("th1结束,共计:"+(endtime-begintime));
}
}
});
Thread th2 = new Thread(new Runnable() {
public void run() {
try {
synchronized(o) {
System.out.println("th2拿到锁");
}}catch(Exception ex) {
System.out.println("th2跳出阻塞");
}
}
});
th1.start();
// Thread.sleep(1000);
// th1.interrupt();
th2.start();
th2.interrupt();
}
}
这段代码中(就默认th1先执行吧),th2的interrupt()方法不会使th2在synchronized同步块阻塞时跳出,只会一直请求锁。而如果取消注释,即在主线程1s后调用th1的interrupt()方法,此时th1处在sleep()暂停中,所以会跳出这个阻塞,抛出异常,然后时间总计时间会在1000毫秒上,而不是5000.
5.wait()、notify()、notifyAll()方法
是从Object继承过来的方法,用在同步块上,实现单个条件队列的中断与唤醒。
wait(),使拿锁者阻塞,释放锁,进入待唤醒队列
notify()、notifyAll(),拿锁者唤醒某个(随机),或者全部待唤醒线程。
使用:如在一个while(condition)下,满足这个condition条件的线程都不立即执行(就像有些先期工作还没做,先放放等满条件再试),然后调用wait()阻塞,释放锁,让其他线程先执行。然后再在其他线程的执行代码里,有另一个if(condition2),当condition2满足了,就可以notify()唤醒一个阻塞线程(等于先期工作做完了,可是试着做一做上面被阻塞的线程的工作了)。
6.其他方法(不具体说了)
yield():从running->runnable
isAlive():对应线程的存活情况
currentThread():返回当前线程(静态方法)