1.线程创建方式
1.1. 继承Thread类
//1. 使用一个类继承Thread类,并重写run()方法
public class SonThread extends Thread{
@Override
public void run() {
System.out.println("sonThread.run");
}
}
//2. 在主线程中调用子类的run方法
SonThread sonThread = new SonThread();
sonThread.run();
//
1.2. 实现Runable接口
无返回值
//1. 使用一个类实现Runnable接口,并重写run()方法
public class RunnableThread implements Runnable{
/**
* 实现runnable接口
*/
@Override
public void run() {
System.out.println("runnable.run()");
}
}
//2. 在主线程中创建Thread对象
Thread thread1 = new Thread(new RunnableThread());
thread1.run();
1.3. 实现Callable接口
线程有返回值
//1. 使用一个类实现Callable接口,并重写call()方法
public class CallableThread implements Callable<Book> {
/**
* 实现callable接口
*/
@Override
public Book call() throws Exception {
System.out.println("callable.call()");
Book book = new Book("1","2");
return book;
}
}
//2. 根据Callable子类实体创建FutureTask实体,再根据FutureTask实体创建Thread类实体并执行run()方法
FutureTask<Book> bookFutureTask = new FutureTask<>(new CallableThread());
Thread thread2 = new Thread(bookFutureTask);
thread2.run();
//3. 接收线程返回值
Book book = bookFutureTask.get();
1.4. 使用线程池
使用线程池原因: 线程是珍贵资源, 不可随意创建和销毁, 所以使用线程池来接收使用完的线程, 提高复用性, 进而提高效率
//自创建线程池
java.util.concurrent.ThreadPoolExecutor类: 线程池对象的类型
public ThreadPoolExecutor(int corePoolSize,//核心线程数 --> 正式员工
int maximumPoolSize,//最大线程数 --> 正式员工 + 临时员工
long keepAliveTime,//空闲线程存活时间 -> 临时员工存活时间
TimeUnit unit,//空闲线程存货时间单位 -> 临时员工存活时间单位
BlockingQueue<Runnable> workQueue,//任务队列 -> 排队的顾客
ThreadFactory threadFactory,//线程工厂 -> 人才市场
RejectedExecutionHandler handler)//任务的拒绝策略 -> 如何拒绝没排上的顾客
2.多线程简介
- 一个线程处于运行状态时是会占用一个CPU的当前使用权, 如果多个线程公用一个CPU, 则只能有一个线程在同一时间点执行, CPU可能会在线程中来回切换, 造成性能下降
- 一个进程可以包涵多个线程
- 线程有生命周期, 分别是创建, 运行, 阻塞, 就绪, 死亡
- 线程共享主进程所有堆中资源, 同时还有在堆中独自占有资源, 开启线程会生成一个栈帧
- 有部分资源不允许并发操作
3.多线程执行策略
3.1. 线程的执行
并行: 多个线程同一时间点做事情
并发: 多个线程同一时间段做事情
3.2. 线程优先级问题
线程优先级默认为5, 区间范围在0-10, 10 为优先级最高
3.3. 多线程并发问题
多个线程操作同一共享数据
线程中可使用锁对象,volatile,原子类等方式进行处理, 当使用互斥锁的时候,只有一个线程能持有该锁,其他需要
4.锁
4.1. volatile
线程在修改volatile关键字修饰的变量时, 会先将本地的副本和堆中线程共享区域的实际变量的版本号进行比较, 不一样则更新本地, 维持了可见性, 同时使用了volatile将不会再指令重排, 杜绝了指令重排的影响;
指令重排: jvm会优化代码, 将jvm指令进行重排来提高运行效率, 发生指令重排的逻辑在本线程中执行的最终结果是一致的, 本线程中指令之间没有依赖性关系, 但是在多线程并发环境下, 指令重排可能会对代码结果产生影响
4.2. synchronize:互斥锁, 不公平锁
synchronize能让同一个时间段, ①只有一个线程操作共享数据, ②可以保证一个线程的变换可以被其他线程看到
锁对象:
①非静态方法: 锁对象是类的实例
②静态方法: 锁对象是类
③同步代码块: 锁对象是同步代码块的实参
package com.nbcb.thread.test;
import com.nbcb.obj.Book;
public class Test02 implements Runnable{
private static int i = 0;
private static int j = 0;
//此时锁对象是类的实例
public synchronized void method(){
for (int i1 = 0; i1 < 100000000; i1++) {
i++;
}
}
//此时锁对象是类
public synchronized static void staticMethod(){
for (int i1 = 0; i1 < 100000000; i1++) {
j++;
}
}
public static void main(String[] args) throws InterruptedException {
Book book = new Book();
synchronized (book){} //此时锁对象是该book实例
synchronized (Book.class){} //此时锁对象是Book类
Thread thread01 = new Thread(new Test02()); //锁对象是匿名实例, 不可能生效
Thread thread02 = new Thread(new Test02()); //锁对象是匿名实例, 不可能生效
//thread01和thread02在执行method方法时, 持有的是不同Test02的实例对象,所以没有锁住
//thread01和thread02在执行staticMethod方法时, 持有的是Test02的类,所以锁住了
thread01.start();
thread02.start();
thread01.join();
thread02.join();
System.out.println("i = " + i);
System.out.println("j = " + j);
//执行结果:i = 100155496 没锁住
//j = 200000000 锁住了
}
@Override
public void run() {
method();
staticMethod();
}
}
synchronized锁原理
ReentrantLock 重入锁, 可公平可不公平
ReadWriteLock 读写锁,
懒得写了, 睡觉了