1. 线程
- 线程、进程概念:
进程可以理解为内存空间,进程是系统进行资源分配和调度的基本单位。
线程就是真正处理任务的,线程是进程中的一个执行单元,它负责执行进程中的代码。
- 并发、并行概念:
并发:单位时间片内,只执行一个任务(java就是如此)
并行:单位时间片内,同时执行多个任务
- 如何在java中创建一个线程?(我们可能更常用的就是第二种方法的匿名内部类)
方法一:继承Thread父类
-
创建Thread 子类,继承父类
-
重写其父类run方法
-
创建子类实例
-
执行子类start方法
方法二:实现Runnable接口
- 创建一个Runnable接口的实现类
- 重写接口的run方法
- 创建实现类的实例
- 创建Thread对象,且将实现类实例作为参数传入,调用其start方法
- 线程的方法
setName/getNmae:获取线程名字或设置线程名字
Thread.sleep(long millis);设置当前线程休眠毫秒数
Thread.currentThread();获取当前线程对象,弥补使用实现Runnable接口无法直接获取线程对象的弊端
strat();开启线程
补充:可以在声明线程时给定名字:new Thread(sellTicket, "天猫").start();
sellTicket是runnabe的实现类,名字可以在声明时直接给定
-
如何理解java多线程/为什么多线程可以并发处理多个任务?
答:我们每创建一个线程,都会开辟一个新的栈空间,而CPU可以在不同栈顶交替并发执行多个任务,我们在学习之前线程之前是依靠JVM先执行main方法,为我们开辟主线程。
//补充:再次加深对于面向对象的理解:
//我们通过匿名内部类的方式,分别创建Runnable的实现类r和Thread的子类,此时把r放在Thread的子类的参数位置,请问此时我们输出结果是调用Runnable的实现类r的run方法 还是 Thread的子类的run方法?
//题目解析:
//其实本质上还是在运行Thread匿名子类的方法,接口对象的实现的前提是在调用接口的
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 105; i++) {
System.out.println("第二种匿名内部类"+ i);
}
}
};//通过匿名内部类的方式获取实现Runnable接口的实现类
new Thread(r){
@Override
public void run() {
//super.run();
for (int i = 0; i < 100; i++) {
System.out.println("第一种匿名内部类");
}
}
}.start();//通过匿名内部类的方式,直接获取Thread的子类
2. 线程同步
多线程操作,会有什么问题?当多个线程同时操作公共数据时,可能会导致程序出错
为了解决多线程操作可能出现的问题,我们可以使用三种方法来为程序上锁,当上锁代码在被一条线程执行时,其他线程无法同时执行该段代码
2.1 同步代码块(推荐)
语法格式:synchronized(锁对象){需要被同步的代码 }
直接上代码更好理解:
//三种的原理相同,就是给特定代码上锁,要求学会同步代码块
//以买票举例(演示同步代码块):
//卖票 SellTicket类 继承Runnable接口,重写run方法,使用synchronize同步代码块包裹需要上锁的代码
package cn.itcast.practice.synchronize;
public class SellTicket implements Runnable {
private int count = 100;//一共100张票
private static final Object lock = new Object();//制造锁对象
@Override
public void run() {
while (true) {
synchronized (lock) {
if (count == 0) {
break;
}//一定要把判断条件也包裹在代码块中
System.out.println(Thread.currentThread().getName() + "正在买" + count + "张票");
count--;
}
}
}
}
//另一个类中,建立三条线程,共同操作一个SellTicket实例,一起卖这100张票
package cn.itcast.practice.synchronize;
public class TicketTest {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
//构造方法 new Thread(Runnable target,String name)
new Thread(sellTicket, "天猫").start();
new Thread(sellTicket, "京东").start();
new Thread(sellTicket, "淘宝").start();
}//main
}
2.2 同步方法
语法格式:修饰符 synchronized 返回值类型 方法名(参数列表){}
缺点:
-
无法自定义锁,直接给默认锁,
- 静态方法锁为当前类的字节码的对象:例如字符串的字节码对象就是 String.class
- 非静态方法锁位当前的实例,也就是this
-
方法中所有代码被锁,无法锁住方法中特定的一部分
补充:因为同步方法的缺点,导致使用了同步方法的一批类都消失了,例如Vector是Arraylist的前身,Hashtable是HashMap的前身,对于需要多线程操作时,对于例如:Arraylist和HashMap,可以使用集合工具类为我们提供的Synchronize方法。
//同步版本的和未同步版本的是一母同胞,变化结果相同。
ArrayList<Integer> list = new ArrayList<>();
//使用Collections工具类,把可能会多线程安全出问题的list转变为更安全的list1
List<Integer> list1 = Collections.synchronizedList(list);
list1.add(111);//给list1添加元素
System.out.println(list);//list结果也会变为 [111]
2.3 Lock锁对象
如果不愿意使用自定义锁,可以使用官方为我们提供的锁对象
语法:Lock lock = new ReentrantLock()
使用方式:lock.lock();
手动上锁 lock.unlock();
手动开锁
注意事项:无论前面代码是否正常执行,一定要保证开锁,所以可以用try-finally包裹,所以比较麻烦。
//还是用前面老师的例子,简单了解一下即可
public class SellTicket implements Runnable {
private int ticket = 100;
private Lock lock = new ReentrantLock();//使用Lock的子类ReentrantLock创建锁实例
@Override
public void run() {
while (true) {
try{
lock.lock();
if (ticket <= 0) {
break;
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩下" + ticket + "张");
}finally {
lock.unlock();
}
}
}
}
3. 线程池
定义:存放线程的池子, 线程池中的线程,执行完毕后,不销毁而是归还到池子中,继续服务下一个任务,提高线程的使用率,其解决了频繁创建线程以及销毁线程的动作, 节约时间和资源。
线程池对象:【面试题:线程池七大核心参数】
ThreadPoolExecutor(
int corePoolSize, //核心线程池数量
int maximumPoolSize,// 最大线程池数量, 扩容的线程我们称为临时线程
long keepAliveTime, // 临时线程空闲的时间,就会销毁
TimeUnit unit, // 临时线程空闲的时间单位,就会销毁
BlockingQueue<Runnable> workQueue, // 存放任务的队列
ThreadFactory threadFactory, //创建线程的方式
RejectedExecutionHandler handler// 拒绝策略
)
//补充拒绝策略的四大参数:
//new ThreadPoolExecutor.AbortPolicy(): 直接拒绝, 并且抛出异常(推荐,且为默认值)
//new ThreadPoolExecutor.DiscardPolicy(); 直接拒绝, 不抛异常
//new ThreadPoolExecutor.DiscardOldestPolicy(); 丢弃队列中最老的任务,再执行
//new ThreadPoolExecutor.CallerRunsPolicy(); 不拒绝,自己处理丢给主线程!!!
-
线程触发扩容临时线程的条件: 当任务量> 核心线程数量+队列 就会扩容!!!
-
什么时候会触发拒绝策略: 当任务量 > 最大线程 + 队列,就会触发拒绝策略
线程池方法:为线程池对象提交线程,语法;线程池实例.submit(线程对象)
//线程池案例;
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
3,
5,
30,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() );//线程池的7大参数
for (int i = 0; i < 20; i++) {
tpe.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在服务我");
}
});
}
//案例解析:
//核心线程数量为3,最大线程数量为5,存放任务队列为 10
//如果提交13个以下的任务,任务队列不满,则只有核心线程工作
//如果提交13个以上的任务,所有线程都在工作,
//如果提交的超过了15个,则会触发拒绝策略
补充: 线程池也有自己直接的API:(不太实用,不如自己造线程池)
Executors.newCachedThreadPool(); 不好, 没有上限的线程池!!!
Executors.newFixedThreadPool(int max);不好, 有上限的线程池, 最多是你给的最大限制
补充:线程的状态(配合图解进行学习)
线程的状态: | 表达含义: |
---|---|
NEW | 被创建却并没有被start,尚未被启动 |
RUNNABLE | 已经被start,可以被CPU执行的程序 |
BLOCKED | CPU正在运行此线程,但是遇到锁,进不去的状态 |
WAITING | 无限等待状态, 线程遇到wait方法, 处于无限等待, 必须通过notify或者notifyAll才能唤醒 |
TIMED_WAITING | 计时等待, 线程遇到sleep(long time); 当时间够了会自动唤醒 |
TERMINATED | 销毁状态, 线程执行完后的状态 |