进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。多进程操作系统能同时达运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程是比进程更小的执行单位,线程是进程的基础之上进行进一步的划分。所谓多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。进程与线程的区别如图所示:
二、并行与并发并行: 多个cpu同时执行多个任务。 比如:多个人同时做不同的事
并发: 一个cpu(采用时间片)同时执行多个任务。 比如:秒杀、多个人做同一件事。
三 、创建多线程的方式1.继承于Thread类
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
//2.重写Thread类的run()方法
public void run(){
//遍历100以内的所有的偶数
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName() + "运行,i = " + i) ;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread mt1 = new MyThread() ; // 实例化对象
MyThread mt2 = new MyThread() ; // 实例化对象
mt1.setName("线程A");
mt2.setName("线程B");
//4.通过此对象调用start()
mt1.start();
mt2.start();
}
}
2 .实现Runnable接口
Runnable runnable=new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("子线程输出,i = " + i) ;
}
}
};
Thread t1=new Thread(runnable);
t1.start();
for (int i=0;i<10;i++){
System.out.println("主线程输出,i = " + i) ;
}
//也可以简化成
new Thread(() -> {
for (int i=0;i<10;i++) {
System.out.println("子线程输出,i = " + i);
}
}).start();
for (int i=0;i<10;i++){
System.out.println("主线程输出,i = " + i) ;
}
3.通过Callable和Future创建线程
public class MyCallable implements Callable{ @Override public String call() throws Exception { int sum=0; for (int i = 0; i < 100; i++) { sum+=i; } return "子线程的返回结果是:"+sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask task=new FutureTask(new MyCallable()); Thread t1=new Thread(task); t1.start(); //get()方法等待任务的程序执行完毕后才会执行 System.out.println(task.get()); } }
开发中优先选择实现Runnable接口的方式,原因:
1.可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
2.有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。 多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数 实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
3.适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
四、线程的常用方法| String getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
| void setName(String name) | 将此线程的名称更改为指定的名称,通过构造器也可以设置线程名称 |
| public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
| public static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
| public void run() | 线程任务方法 |
| public void start() | 线程启动方法 |
| void wait() | 让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法 |
| void notify() | 唤醒正在等待的单个线程 |
| void notifyAll() | 唤醒正在等待的所有线程 |
1.线程的优先级
java 中的线程优先级的范围是1~10,默认的优先级是5。10级最高。
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY:5 默认优先级
2.如何获取和设置当前线程的优先级
getPriority():获取线程的优先级
setPriority(int p): 设置线程的优先级
高优先级的线程要抢占低优先级的线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率情况下 被执行。并不意味着只有当高优先级的线程执行以后低优先级的线程才会执行
六、线程的生命周期在它的一个完整的生命周期中通常要经历如下五种状态:
1:新建状态(new): 使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前. 新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已.Thread t = new Thread();//此时t就属于新建状态当新建状态下的线程对象调用了start方法,此时从新建状态进入可运行状态.线程对象的start方法只能调用一次,否则报错:IllegalThreadStateException.
2:可运行状态(runnable)
分成两种状态,ready和running。分别表示就绪状态和运行状态。
就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行).
运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行.
3:阻塞状态(blocked)
正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态.此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态.阻塞状态只能先进入就绪状态,不能直接进入运行状态.
阻塞状态的两种情况:
1):当A线程处于运行过程时,试图获取同步锁时,却被B线程获取.此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.
2):当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态.
4:等待状态(waiting)(等待状态只能被其他线程唤醒):
此时使用的无参数的wait方法,
1):当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中.
5:计时等待状态(timed waiting)(使用了带参数的wait方法或者sleep方法)
1):当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.
2):当前线程执行了sleep(long time)方法.
6:终止状态(terminated)
通常称为死亡状态,表示线程终止.
1):正常执行完run方法而退出(正常死亡).
2):遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).
-------------------------------------------------------------------
线程一旦终止,就不能再重启启动,否则报错(IllegalThreadStateException).
1.同步代码块
作用:把出现线程安全问题的核心代码给上锁。
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
synchronized (同步锁对象){
操作共享资源的代码(核心代码 )
}
锁对象要求:对于当前同时执行的线程来说是同一个对象即可。对于实例方法建议使用this作为锁对象,对于静态方法建议使用字节码(类名.class)对象作为锁对象。
2.同步方法
作用:把出现线程安全问题的核心方法给上锁。
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
同步方法底层是有隐式锁对象,锁的范围是整个方法代码。如果方法是实例方法,同步方法默认用this作为锁的对象。如果方法是静态方法,同步方法默认用类名.class作为锁的对象。
3.Lock锁
//final修饰后锁对象唯一 private final Lock lock=new ReentrantLock(); //上锁 lock.lock(); //解锁 lock.unlock();
synchronized与Lock都可以解决线程安全问题,synchronized机制在执行完相应的同步代码以后,自动的释放同步监听器。Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
优先使用顺序:Lock→同步代码块(已经进入了方法体,分配了相应的资源)→ 同步方法(在方法体之外)
八、线程池1.使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
ExecutorService 处理Runnable任务
ExecutorService executorService=new ThreadPoolExecutor(4,6,2,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
//处理Runnable任务
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();//等待全部任务执行完毕后关闭
ExecutorService处理Callable任务
ExecutorService executorService=new ThreadPoolExecutor(4,6,2,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());
Future submit = executorService.submit(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return "子线程的返回结果是:" + sum;
});
String s = submit.get();
System.out.println(s);
executorService.shutdown();//等待全部任务执行完毕后关闭
2.自定义ThreadPoolTaskExecutor线程池,使用它可以明确线程池的运行规则,规避资源耗尽的风险。
@Configuration
@EnableAsync
public class TaskExecutePool {
@Bean
public Executor myTaskAsyncPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); //核心线程数
executor.setMaxPoolSize(20); //最大线程数(要大于等于核心线程数)
executor.setQueueCapacity(1000); //队列大小
executor.setKeepAliveSeconds(300); //线程最大空闲时间
executor.setThreadNamePrefix("async-Executor-");//线程前缀名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略(一共四种,此处省略)
executor.initialize();
return executor;
}
}
大型并发系统环境中使用Executors如果不注意可能会出现系统风险。



