1 多线程的概念
(1)并发:同一时间段内完成多件事情,使用分时复用的原理,例如一个CPU在多个任务之间来回切换;
1)对于java虚拟机而言,不管CPU是单核还是多核,都是使用并发的方式,例如java代码开启多个线程,这多个线程就是并发的,分析的时候需要使用考虑到线程安全问题。
(2)并行:同一时刻完成多件事情,多个资源单独处理每件事情,例如多核CPU,一个核心处理一个任务。
2 多线程的执行原理
(1)创建Thread类或其子类对象,获取开辟线程的方法
(2)调用start()方法,会在内存中创建一个该新线程的栈空间,用来执行新线程的代码
3 Thread类
(1)Thread类是线程相关的类,主要用来开启新线程,设置线程的参数,获取线程信息等功能
(2)Thread类的常用方法
构造方法
1)public Thread() :分配一个新的线程对象。
2)public Thread(String name) :分配一个指定名字的新的线程对象。
3)public Thread(Runnable target) :分配一个带有指定目标新的线程对象,也就是传递一个Runnable接口子实现类。
4)public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象(Runnable接口实现类)并指定名字
常用方法
1)public String getName() :获取当前线程名称。
2)public void start() :使此线程开始执行; Java虚拟机调用此线程的run方法。
3)public void run() :此线程要执行的任务在此处定义代码。
4)public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
5)public static Thread currentThread() :返回对当前正在执行的线程对象的引用
4 创建线程的方式
(1)创建一个继承Thread类的子类,重写run()方法,创建该子类对象并调用start()执行新线程;
(2)创建Runnable接口的实现类,new一个Thread类对象,将Runnable实现类作为参数传递给Thread构造方法,并调用start()方法开启线程
(3)使用匿名内部类的方式实现上面两种创建线程的方法
1)匿名内部类-Thread子类
new Thread(){
@Override
public void run() {
//线程执行代码
}
}.start();
2)匿名内部类-Runnable接口
new Thread(new Runnable() {
@Override
public void run() {
//线程执行代码
}
}).start();
(4)Thread和Runnable两种创建方式的区别
1)Thread通过产生其子类,重写子类中的run()方法来实现新线程的调用,不适合资源的共享,例如共享线程执行方法,使用Runnable可以很容易实现资源的共享(不同线程使用同一个接口实现类),所以实际使用一般使用Runnable方式实现
2)Runnable实现新线程的方式的优点汇总
1)Runnable方式容易实现资源的共享,通过多个线程使用同一个接口实现类实现
2)Runnable方式代码的耦合性更低,编译代码的修改维护
3)在使用线程池的时候,只能放入实现Runnable或则是Callable类的线程,不能直接放入Thread
4) 避免单继承的局限性
5 线程安全
(1)在使用多线程的时候,如果过个线程对同一个资源(数据)机型读写的时候,就容易发生线程安全问题;
例子:线程1在对一个数据合法性判断后,刚要操作数据时被切换到线程2,线程2也对数据进行操作,导致数据变得不符合线程1判断,此时cpu切回线程1时就会对不符合线程1要求的数据进行操作,从而产生问题。
(2)避免线程安全的方法
1)线程同步:一个代码还没有被一个线程执行完,不允许其他线程执行此代码,从而保证要同步代码执行的原子性;
2)具体实现方式
1)同步代码块:使用synchronized关键字修饰一个代码块,该代码块中的内容就是同步代码
1)锁,可以是任意类型对象,但是多个代码块同步,需要保证使用同一个锁;
//同步代码块1
synchronized(同步锁a){
//需要同步操作的代码
}
....
//同步代码块2
synchronized(同步锁a){//两个代码块是同步的,必须使用同一个锁
//需要同步操作的代码
}
2)同步方法:使用synchronized关键字修饰的方法(比较少用)
public synchronized void method(){
//可能会产生线程安全问题的代码
}
3)lock锁:Java一个接口java.util.concurrent.locks.Lock ,(一般使用其实现类ReentrrantLock)提供了比同步代码块和同步方法更加广泛的操作
1)实现锁方法
1)public void lock() :加同步锁。
2)public void unlock() :释放同步锁。
6 线程的运行状态
(1)在线程的这个生命周期中,有6中状态,分别是新建态、可执行态、无限等待态、计时等待态、锁阻塞态、终止态
(2)状态之间的切换
(3)多线程状态涉及的常用方法
1)wait()/wait(time):使用锁调用,并且要在同步代码块中调用,使当前线程进入等待态,带参方法时间到了还没有被notify唤醒,也会自动唤醒;
2)notify():使用锁调用,并且要在同步代码块中调用,唤醒同一个锁下的等待线程中的一个线程;
3)notifyAll():使用锁调用,必须使用同步代码块,唤醒同一个锁下的所有等待线程;
4)sleep():使线程进入等待态,时间到才能唤醒;
(4)sleep()和wait()方法的区别
1) sleep()进入等待态后,不会释放锁,如果在同步代码块中使用该方法,则其他线程在该线程等待期间也不能执行该方法
2)sleep是Thread类的方法,wait是Object的方法,也就是所有的对象都可以调用;
3)wait必须在同步代码块中使用锁调用,sleep没有限制。
7 多线程通信——等待唤醒机制
(1)多线程之间的通信
1)当多线程之间的协作完成一件任务的时候,需要线程间通信,例如一个线程完成数据的产生,一个线程完成数据的获取,这个就要涉及到操作的先后问题,先要有数据才能去取。这就涉及线程之前的通信
2)常用的线程通信方式是等待唤醒机制,等待就是线程调用wait进入等待态,放弃CPU执行权,唤醒就是当需要一个任务来执行的时候,使用notify唤醒任务。例如生产者在生产完数据,就会唤醒消费者来获取数据,自己进入等待态,消费者消费完数据后,就会唤醒生产者进行数据生产,然后自己进入等待态,以此过程进行循环;
3)使用等待唤醒机制注意事项
1)必须使用同一个锁调用wait和notify
2)等待唤醒必须在同步代码块中执行,因为必须通过锁调用等待和唤醒方法
8 线程池
(1)线程池就是一个装有多个线程的容器,通过该容器获取线程,可以降低频繁开启和关闭线程的资源消耗
(2)线程池的好处
1)减少资源消耗:通过线程池获取线程,可以通过线程的复用,减少频繁开启和关闭线程的资源消耗;
2)便于管理线程:可以更方便对线程进行管理,例如可以根据系统来选择线程池中线程数量;
3)提高响应速度:需要创建新线程的时候,可以直接从线程中获取。
(3)线程池的相关类
1)java线程池中提供了相关的接口和类来支持线程池的使用
1)java.util.concurrent.Executor是线程池的顶级接口,但只是提供了一些执行方法,并不算真正的线程池接口
2)java.util.concurrent.ExecutorService是线程池的接口,常用方法
public Future<?> submit(Runnable task),用来获取线程池中的某一对象并执行
3)java.util.concurrent.Executors:是一个工程类,用例产生各种线程池,常用的方法是
public static ExecutorService newFixedThreadPool(int nThreads),创建一个有界线程池,创建时需要指定线程数量