一、什么是多线程?及其特点?如何执行多线程?
①一般情况下,只有一个顺序执行流的程序我们称其为多单线程,而多线程就是可以包括多个顺序执行流。
②多个执行流之间互不干扰;当一个程序进入内存运行时,即变成一个进程,进程是处于运行状态的程序,具有独立功能,是系统进行资源分配和调度的独立单位。其中线程被称为轻量级进程,线程是进程的组成部分,一个进程可以包括多个线程,线程可以拥有自己的堆栈,程序极计数器,和自己的局部变量,但是没有紫铜资源。
③它与父进程共享系统资源,其中主进程里的多个线程是利用抢占式的多任务策略,是随机执行的,线层是独立运行的,并不知道其他线程的存在。(一个运行的程序最低包含一个进程,一个进程里面可以包含多个线程,但是最起码包括一个线程。
二、并发和并行的区别?
并发(concurrency):多个线程快速轮换的执行,同时刻只有一个线程执行。
并行(Parallel):在有两个以上的CPU时,可以一起执行不同进程,(一起执行)。
三、多线程优点?
a、其在功能是类似于多进程,但是创建成本低,因为其功用系统资源。
b、创建成本低,而且java多线程性能优越。
c、各个线程共享内存,线程之间的通讯简单,方便。
四、如何创建多线程?
⑴利用继承Thread,重写run()方法的方式创建多线程。下面是示例代码:
<span style="font-size:18px;">//继承Thread类创建多线程
public class FirstThread extends Thread
{
<span style="white-space:pre"> </span>@Override<span style="font-family: Arial, Helvetica, sans-serif;">//该方法是重写父类的方法,因为父类里没有返回值,</span>
public void run()<span style="font-family: Arial, Helvetica, sans-serif;">//所以重写也不能有返回值,不能声明超出异常</span>
{
for(int i=0;i<100;i++)
{
//获取当前的线程
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
public static void main(String [] args)
{
new FirstThread().start();
for(int i=0;i<100;i++)
{
//获取当前的线程
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}</span>
}
注:①run()方法下的是线程的执行体。
②启动线程用的是调用Thread的Start方法,(注意,如果调用run方法,只是执行run方法,而不是多线程。
③java默认有一个main主线程。
④使用Thread类的方法来创建的线程类,多条线程之间无法共享线程类的实例变量。
⑤可以通过其内的方法setName(String name) ,类为线程设置名字。
⑵实现Runnable类,重写一个run()方法的方式实现多线程的创建。其代码基本思路同上。但是其启动方式不一样,由于继承的类没有start()的线程启动方法,所以其启动方式较为特殊:
●启动方法:①获取Runnable类的线程实例:->SecondTheread st = new SecondThread;
②把st,即Runnable的线程实例包装成Thread类的:new Thread(st);
③调用Thread的Start()方法启动线程:new Thread (st).start();
⑶实现Callable类,重写一个run()方法的方式实现多线程的创建。其代码基本思路同上,相当于是前两者的增强版(可以有返回值,可以声明抛出前异常,而前两个不可以)。但是其启动方式不一样,由于继承的类没有start()的线程启动方法,所以其启动方式也较为特殊:
●启动方法:①获取Callable接口的线程实例,包装成Futuretask(其是Runnable的实现类,相当于把Callable的线程包装成Runnable)
②再把Runnable的线程实例包装成Thread类。
③调用Thread的Start()方法启动线程。
四、线程状态转换图:
注:
●线程启动之后不是一启动就进入执行状态,而是要经过新建,就绪,运行,阻塞,和死亡五种状态。期间由于CPU需要在多条线程之间切换,于是运行状态就会多次在运行和阻塞之间切换。
●什么是运行和阻塞?
当调用run()方法开始执行线程执行体,是就是运行状态,任何时候只有一条线程处于运行状态。
当进入阻塞时就是放弃现在正在使用的系统资源,给其他线程执行机会。
●下面情况线程会进入阻塞状态以及相应的会结束阻塞:
①线程调用sleep方法,->调用sleep方法经过了指定的时间则结束阻塞。
②线程调用了一个阻塞的IO方法,在该方法返回之前,该线程被阻塞。->IO方法已经放回,则结束阻塞。
③线程试图获得一个同步监视器,但是监视器被其他线程所持有.->获得同步监视器,结束阻塞。
④线程真正等某个通知(notify)唤醒,而唤醒还没有来。->接收到其他线程的通知,结束阻塞。
⑤程序调用了线程的suspend方法将该线程挂起,不过这个方法容易导致死锁,所以一般不用。->使用resume方法恢复。
●线程的死亡:
①run()方法执行结束,线程正常结束。
②线程抛出未捕获的异常,或者ERROR。
③直接调用Stop方法强制结束,容易导致死锁。不建议使用。
●如何设置线程为后台程序:调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。
①后台线程:如果所有的前台线程四死亡后,后台线程也会随之死亡。
②要经某个线程设置为后台线程,应该在线程启动之前设置。
五、线程的安全:(同步代码块和同步方法实现)●为什么需要线程安全?
因为run方法的方法体一般情况下不具有同步安全性,当多条线程并发访问,修改某一个共同资源时,为了避免修改同一个资源带来的不安全问题(经典问题,银行取钱),于是对其进行加锁,让其线程同步。
①实现线程安全的的方法一:同步代码块。
Synchronized (account)//account是同步锁的对象,也就是同步监视器,选择竞争资源。
{
//把代码块里的代码化为一体,不允许其运行时被其他线程打断。
}
②同步方法:同步方法就是使用synchronized关键字来修饰某个方法,则成为同步方法。其无需指定同步监视器,默认为this,也就是对象本省。
实现原理:当线程进入被同步监视锁监视的代码之前,线程获得同步监视器,保证任何时候只有一条线程进入同步监视器内执行代码,执行完后释放同步监视器。
●什么时候释放同步监视器?
①同步代码块执行完成
②代码块遇到break,return时。
③遇到未捕获的异常
④调用同步监视器额wait()方法。
▲线程安全与不安全的利弊?
线程安全:适合多线程访问,但是程序性能较低。
线程不安全:适合单线程程序,程序性能较好,但是不实用。
六、线程的通讯:
①如果不加控制,多个线程“自由的”并发执行。
②可以通过同步解决竞争资源的的问题
③希望线程有序执行,可以使用放置标识,旗标的方法
④wait()控制当前线程一直等待,释放同步锁,直到收到唤醒通知。
⑤notify()发送指定的线程的唤醒通知。
⑥notifyAll()发送所有线程的唤醒通知。
七、线程组:对线程进行批量管理,调用Thread的
●设置优先级:设置线程组的最高优先级,线程组内的优先级不会被改变,但是后面加入的线程的优先级不能高于原有的优先级。
八、线程池:系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互,在这样的情况下,使用线程池(缓存大量线程),以较好的提高性能。
●池的本质:就是一种缓存技术。
●缓存的本质:牺牲空间换取时间。
●Excutors工厂类来创建线程池。
九、常用的方法:
①isAlive(),判断线程是否死亡。
②THread的start()方法启动线程。
③Join()方法使相应线程必须先执行完成后才执行其他线程。
④sleep()方法是暂停当前线程,让其他线程有机会执行,不理会其他线程的优先级,大家获得执行的机会一样。
⑤yield()方法也是暂停当前的线程,但是其只会给优先级相同或者优先级更高的的线程执行机会。