疯狂的线程
了解Java的人,都会知道Java是高级语言,它具备其他一切高级语言基本所具备的一切,当然线程也不例外。说到线程必然会提到进程,来揭一下他们的面纱。
线程与进程
1 线程:
是进程中负责程序执行的执行单元。 是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源(每个线程都有自己的一亩三分地),但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
2 进程:
执行中的程序(当我们使用windows系统时,一般电脑特别卡或者某个软件卡住未响应时,我们一般会去ctrl+alt+delete打开任务管理 器,映入眼帘的就是进程啦,各种熟悉浏mysql,oracle,apache HTTP server 。各种陌生的qq、微信、陌陌、探探之类的都是进程。)你说开这么多进程(软件)而且我们打开任务管理器的时候看到是他们在乱跳,你想杀他们吧,一下两下还逮不住。那么你们有没有想过他们这么皮跳的这么快,难道不会跑错地方吗(访问错资源)?其实每个进程都有自己独立的地址空间(内存空间),换句话说只要我们开启一个进程,系统就会为这个进程分配资源空间。一个进程至少包含了一个线程,也就是说进程由线程组成的。
3 单线程:
程序中只存在一个线程,实际上main( )方法就是一个主线程
4 多线程:
在一个程序中运行多个任务,目的是更好地使用CPU资源(比如我们打开电脑管家杀毒的同时清理垃圾同时还进行电脑加速)
创建线程
一般我们最常用的就是两种方法
1、扩展java.lang.Thread类
2、实现java.lang.Runnable接口。
3、使用Callable和Future创建线程。
下面我们简单用这三种方式创建线程,来领略一下。
通过继承Thread类来创建并启动多线程的一般步骤如下
1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2】创建Thread子类的实例,也就是创建了线程对象
3】启动线程,即调用线程的start()方法
public class MyThread extends Thread{//继承Thread类
public void run(){
//重写run方法
System.out.println("我是线程一号");
}
}
通过实现Runnable接口创建并启动线程一般步骤如下:
1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3】第三部依然是通过调用线程对象的start()方法来启动线程
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
System.out.println("我是线程二号");
}
}
1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表达式创建Callable对象
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
详细讲解请看前边的文章java基础之多线程(一)更详细的请看文章结尾的视频讲解链接
关于三者的比较:
1、线程不只是实现Runnable或实现Callable接口,还可以继承其他类。
2、实现接口这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。(后边会详细讲解)
4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。(面试中会碰到,关于创建线程用那种方式更好,一般使用实现接口)
注意:Thread类中有run()方法,public void run(),如果该线程是独立的Runnable运行对象构造的,则调用该Runnable对象的run()方法;否则,该方法不执行任何操作。Thread的子类也应该重写该方法。
目睹线程坎坷的一生
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
为了直观特意找了一张图帮我们理解:
线程安全与线程池
我们知道在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。所以,当线程一起并发运行时,同时对一个数据进行修改 就可能会造成数据的不一致性(比如定义一个数 int x=0; 有A和B两个线程来操作,都对x做加法操作x++,那么当AB同时修改了x的值,势必会有一个线程所做的修改被覆盖,对于x而言最终做了两次加法却增加了一次,值为1)。那么线程安全就是说多线程访问同一代码(资源),不会产生不确定的结果(比如说 x 被AB做完加法操作后,增加了两次值为2了)。编写线程安全的代码是低依靠线程同步。当然实现线程安全的方法比较多,加锁是最常用的,锁又有好多种。更多关于线程安全以及各种如何实现线程安全的问题请关注微信公众号"每天学Java"前边的文章Java基础之多线程(二)详细讲解请点"每天学Java"文章结尾的视频讲解链接。
线程池是什么?
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合 (这是重点期末要考的),然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求(一般都是我们自己定的)。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务(java是不是还挺环保的)。
为什么要用线程池?
1. 线程池改进了一个应用程序的响应时间。(由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。)
2. 线程池节省了CLR ( Common Language Runtime )为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。
3. 线程池根据当前在系统中运行的进程来优化线程时间片。
4. 线程池允许我们开启多个任务而不用为每个线程设置属性。
5. 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。
6. 线程池可以用来解决处理一个特定请求最大线程数量限制问题。
既然有这么多好处,那肯定是要用的,要用就得有这个玩意,那接下来我们就学者创造这个玩意。
怎么创建线程池?
当然了,这里我只是简单的给大家提一下,后边我们会详细解释源码的,所以敬请关注啦
1、 SingleThreadExecutor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,
创建: Executors.newSingleThreadExecutor();
2、 Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,
创建:Executors.newCachedThreadPool()
3、 FixedThread Pool: 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,
创建: Executors.newFixedThreadPool(10)
在构造函数中的参数10是线程池的大小,你可以随意设置,也可以和cpu的数量保持一致,获取cpu的数量的方法int cpuNums =Runtime.getRuntime().availableProcessors();
4、 ScheduledThreadPool : 用来调度即将执行的任务的线程池,
创建:Executors.newScheduledThreadPool()
5、 SingleThread Scheduled Pool : 只有一个线程,用来调度执行将来的任务
创建:Executors.newSingleThreadScheduledExecutor()
当然不是说线程池创建了就完了,其实它里边有很多东西值得我们去深究的比如说一下的内容
线程池状态
任务的执行
线程池中的线程初始化
任务缓存队列及排队策略
任务拒绝策略
线程池的关闭
线程池容量的动态调整
这些知识都很重要,一两句也说不清,我们后边内容去刨根问底!!!!!
注意你想要的请来这里