线程 | ProcessOn991 | 思维导图(新) | ProcessOn
线程 | ProcessOn991 | 思维导图(新) | ProcessOn
一.实现线程的几种方式
1.继承Thread类创建线程
2.实现Runnable接口创建线程
3.Callable,Future,可以有返回结果的线程
4.ThreadPoolExecutor线程池类
//继承Thread
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
//2.实现Runnable
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
/**事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码
public void run() {
if (target != null) {
target.run();
}
}
*/
二.线程的生命周期
线程的生命周期分为5个部分:分别是新建状态、就绪状态、运行状态,阻塞状态、死亡状态。
- 新建状态(new):指新建了一个线程对象。Thread t1 =new Thread();这里就新建了一个Thread类的线程对象。
- 就绪状态(Runnable):当线程对象创建后,该线程对象自身或者其他对象调用了该对象的start()方法。该线程就位于了可运行池中,变的可运行,等待获取cpu的使用权。因为在同一时间里cpu只能执行某一个线程。
- 运行状态(Running): 当就绪状态的线程获取了cpu的时间片或者说获取了cpu的执行时间,这时就会调用该线程对象的run()方法,然后就从就绪状态就入了运行状态。
- 阻塞状态(Blocked):阻塞状态就是线程因为某种原因暂时放弃了对cpu的使用权,暂时停止运行。直到线程再次进入就绪状态,才有机会转到运行状态。阻塞状态分为三种情况:
- 等待阻塞:运行状态的线程调用了wait()方法后,该线程会释放它所持有的锁,然后被jvm放入到等待池中,只有等其他线程调用Object类的notify()方法或者norifyAll()方法时,才能进入重新进入到就绪状态。
- 同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被别的线程占用,JVM就会把该线程设置为阻塞状态,一直到线程获取到同步锁,才能转入就绪状态。
- 其它阻塞:运行的线程在执行sleep()或者join()方法时,或者发出了I/O请求,JVM就会把该线程设置为阻塞状态,当sleep()状态超时、join()等待等待线程终止或者超时、或者I/O处理完毕时,线程重进转入到就绪状态。在这需要注意的是sleep()方法和wait()不同,sleep不会释放自身所持有的锁。
-
死亡状态(Dead):当线程执行完了或者因异常退出了run()的执行,该线程的生命周期就结束了。
三.什么是线程安全
总结:当一个全局变量/对象/方法同时被多个线程执行写的操作,值存存在不确定性。
线程安全是通过线程同步控制来实现的,也就是synchronized关键字。
1.为什么会有线程安全问题
多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
2.引起线程安全的原因有哪些
线程安全问题大多是由全局变量及静态变量引起的,局部变量逃逸也可能导致线程安全问题。 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
线程安全的类 Vector HashTable StringBuffer
非线程安全的类 ArrayList HashMap StringBuilder
四.如何处理线程安全问题
通过synchronized,lock加锁来实现。
为什么是通过加锁来实现?
锁的意义在于使高并发/多线程,由原来的并行,变为串行(单线程),达到对变量的唯一安全控制、
锁只有一把,谁先获取,谁就拥有执行权,其他线程则阻塞
1.volatile关键字
volatile 关键字修饰共享变量,一个线程修改,其他线程立即可见;
2.如何控制某个方法允许并发访问线程的个数
使用Semaphore控制, Semaphore mSemaphore = new Semaphore(5);表示生成五个信号量的实例,保证只有5个线程可以同时执行;
五.线程池
- 所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
- 只有当阻塞队列满了后,才会触发非核心线程的创建。所以非核心线程只是临时过来打杂的。直到空闲了,然后自己关闭了。
- 线程池提供了两个钩子(beforeExecute,afterExecute)给我们,我们继承线程池,在执行任务前后做一些事情。
- 线程池原理关键技术:锁(lock,cas)、阻塞队列、hashSet(资源池)
package com.Thread;
import java.util.*;
import java.util.concurrent.*;
public class MyThread extends Thread{
@Override
//重写run方法
public void run() {
// TODO Auto-generated method stub
System.out.println(1);
}
public static void startThread1(){
MyThread myThread = new MyThread();
myThread.start();
int taskSize = 5;
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
for (int i = 0; i < taskSize; i++) {
pool.execute(new MyThread());
}
// 关闭线程池
pool.shutdown();
}
public static void startThread2() throws InterruptedException, ExecutionException{
Date date1 = new Date();
int taskSize = 5;
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
}
/**
* @param args
* @throws ExecutionException
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
startThread1();
//startThread2();
}
}
//============================
package com.Thread;
import java.util.Date;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
六.多线程
当线程执行缓慢,可以采用多线程,来节省时间,可以采用线程池创建。
七.异步线程
当主线程中存在影响执行效率的代码时,考虑采用异步,如记录日志,发送邮件等。
spring提供了一个@Asyn注解,来实现异步线程。