1、在学习多线程之前,先来总结一下进程与线程之间的联系与区别
(1)进程:是一个正在执行中的程序,每个进程执行都有一个执行的顺序。
(2)线程:就是进程中的一个独立控制单元,线程在控制着进程。
(3)理解了线程与进程的概念,接下来总结线程与进程和进程与程序之间的关系
一个程序至少有一个进程,一个进程至少有一个线程。
(4)线程与进程之间的区别
进程在执行的过程中拥有独立的内存单元,而多个线程共享内存,从而极大提高了程序的运行效率。
(5)程序和应用程序的区别,一个程序就是一个进程,而每个进程,可以是单线程的,
也可以是多线程的。一个应用程序(application)通常由多个程序组成。
(6)多线程:指的是这个程序(一个进程)运行时产生了不只一个线程。
(7)同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
(8)并发:多个CPU同时执行多个任务
并行: 一个CPU(采用时间片)同时执行多个任务
2、多线程的优点
1)发挥多核CPU的优势
多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程 的方式去同时完成几件事情而不互相干扰。
2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
3、创建线程的有哪些方式?
1)继承Thread类创建线程类
2)通过Runnable接口创建线程类(使用最多)
3)通过Callable和Future创建线程
4、线程的状态流转图
Java线程具有五种基本状态
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 — 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 — 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
5.什么是线程池? 有哪几种创建方式?
线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。
java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。
四种线程池的创建:
(1)newCachedThreadPool创建一个可缓存线程池
(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。
(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
(4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
6.线程池的优点?
1)重用存在的线程,减少对象创建销毁的开销。
2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3)提供定时执行、定期执行、单线程、并发数控制等功能。
7.synchronized的作用?
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。
synchronized既可以加在一段代码上,也可以加在方法上。
8.volatile关键字的作用
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger
9、多线程的实现方式(代码)
(1)继承Thread类
(2)实现Runnable接口
实现Runnable接口时,还是使用Thread类的对象的start方法进行启动的。
10、synchronized关键字详解
(1)代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块
(2)Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
(3)方法声明时使用,放在范围操作符(public等)之后,返回类型声明(void等)之前.这时,线程获得的是成员锁,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入.
例如:
public synchronized void synMethod(){
//方法体
}
(4)对某一代码块使用,synchronized后跟括号,括号里是变量,这样,一次只有一个线程进入该代码块.此时,线程获得的是成员锁.
例如:
public Object synMethod(Object a1){
synchronized(a1){
//一次只能有一个线程进入
}
}
11、线程创建和使用
(1)start()的两个作用
① 启动当前线程 ② 调用当前线程的run()
注意:调用start()方法只是让线程进入就绪状态,该线程有能力去争抢CPU资源,而不是进入运行状态。
12、继承Thread类,重写run()方法的方式创建线程注意的两点
① 不能通过直接调用run()的方式启动线程
② 不可以还让已经start()的线程去执行。会报IllegalThreadstatrException
13、线程优先级
高优先级的线程要抢占低优先级的线程cpu的执行权,但是只是从概率上讲,高优先级的线程由高概率执行机会。并不意味着只有当高优先级的线程执行完后,低优先级的线程才执行,只是高优先级的线程获得CPU资源的概率比低优先级的要高。
14、线程同步
背景:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
(1)问题:卖票过程中,出现重票、错票 –》出现了线程的安全问题
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参入进来,也操作车票
(2)如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程操作完ticket时,其他线程才可以开始操作ticket。这种情况即使情况线程a出现了阻塞,也不能被改变。
(3) 在java中,通过同步机制来解决线程的安全问题。
① 方式一:同步代码块
synchronized(同步监视器)
说明:
1、操作共享数据的代码,即为需要被同步的代码(不能多,也不能少)
2、共享数据:多个线程共同操作的变量
3、同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
要求:多个线程必须要共用同一把锁
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器;在继承Thread类的方式创建多线程时,可以考虑使用(类名.class)
(b)同步的方式,解决了线程的安全问题
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
4、关于同步方法的总结
(1) 同步方法任然涉及到同步监视器,只是不需要我们显示的声明。
(2) 非静态的同步方法,同步监视器是:this
(3) 静态的(static)同步方法,同步监视器:当前类本身 (类.class)