相关概念
- 程序(program):一段静态的代码,一组指令的有序集合。存储在硬盘上
- 进程(process):进程是程序的动态的执行,进程是最小的独立的内存空间
- 线程(Thread):是在进程中更小的运行单位,每个进程必须要有一个线程,线程是调用CPU的最小的独立单位。多个线程会共享同一个进程中的内存
-
线程的基本使用
第一种创建的方法
创建一个继承了Thread类的类,在run方法中实现子线程的代码
public class MyThread extends Thread{
//当前线程的运行代码
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("子线程"+name+"运行的代码");
}
}
在主线程中,创建子线程,并且通过start进行启动
public static void main(String[] args) {
//获取当前线程的名称
String name = Thread.currentThread().getName();
System.out.println("主线程"+name+"运行的代码:你好,同学");
//创建子线程对象
MyThread thread = new MyThread();
//启动子线程
thread.start();
//thread.run();//主线程调用的线程
//
}
案例1:多线程抢票
创建抢票线程
public class TicketThread extends Thread {
static int ticketNum = 10;
@Override
public void run() {
while(ticketNum>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到第"+ticketNum--+"张票");
}
}
}
启动线程进行抢票(创建多个主线程对象,并启动子线程)
public static void main(String[] args) {
//创建线程
TicketThread thread1 = new TicketThread();
TicketThread thread2 = new TicketThread();
TicketThread thread3 = new TicketThread();
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
多线程的第二种创建方式
创建一个实现了runnable接口的类
public class MyRunnable implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"执行的代码");
}
}
将runnable创建出来之后,必须放到一个线程中才可以运行
public static void main(String[] args) {
//Runable可以理解成线程的一部分,必须要以来一个线程才才运
MyRunnable myRunnable = new MyRunnable();
//创建一个线程
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println(Thread.currentThread().getName()+"运行代码");
}
线程池就是一个容器
- 可以对线程的创建,调用,销毁进行维护
调用方只需要是实现runnable接口任务,将任务发送给线程池,线程池会自行调用线程完成任务
MyRunnable r1 = new MyRunnable("任务1");
MyRunnable r2 = new MyRunnable("任务2");
MyRunnable r3 = new MyRunnable("任务3");
MyRunnable r4 = new MyRunnable("任务4");
MyRunnable r5 = new MyRunnable("任务5");
//创建线程池,设置固定的线程数量
ExecutorService service = Executors.newFixedThreadPool(2);
//通过线程池运行任务
service.execute(r1);
service.execute(r2);
service.execute(r3);
service.execute(r4);
service.execute(r5);
//关闭线程池
service.shutdown();
ExecutorService service = Executors.newFixedThreadPool(2);创建线程池,2代表线程的数量
service(线程池名称).execute(r1);通过线程池运行任务
service.shutdown();关闭线程池案例2:线程池抢票
创建抢票的任务
public class TicketRunnable implements Runnable{
public static int ticketNum = 100;
String name;
public TicketRunnable(String name) {
this.name = name;
}
@Override
public void run() {
//每次被调用只抢一张票
System.out.println(
Thread.currentThread().getName()+"调用"
+this.name+"抢到了第"+ticketNum--+"张票"
);
}
}
使用线程池创建3个线程,10个任务,抢100张票
public class TestThread005 {
public static void main(String[] args) throws InterruptedException {
List<Runnable> list = new ArrayList<>();
for (int i=1;i<=10;i++){
list.add(new TicketRunnable("任务"+i));
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(3);
Random random = new Random();
while(TicketRunnable.ticketNum>0){
int index = random.nextInt(10);
service.execute(list.get(index));
Thread.sleep(1);
}
service.shutdown();
}
}
第三种创建方式
无论是用Thread还是Runable都是实现了run方法,但是run方法有一下问题
- 没有返回值
- 不能抛出异常
创建一个实现了callable接口的类
public class MyCallable implements Callable<Integer> {
int start;
int end;
public MyCallable(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Integer call() throws Exception {
System.out.println("子线程开始计算");
int result =0;
for(int i=start;i<=end;i++){
result+=i;
}
return result;
}
}
将callable绑定到futureTask中,在将futureTask绑定到线程中进行运行
//创建call对象
MyCallable myCallable = new MyCallable(1,50);
MyCallable myCallable2 = new MyCallable(51,100);
//将call绑定到FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
FutureTask<Integer> futureTask1 = new FutureTask<>(myCallable2);
//再将FutureTask绑定到线程上才可以运行
Thread thread = new Thread(futureTask);
Thread thread1 = new Thread(futureTask1);
//运行线程
thread.start();
thread1.start();
System.out.println("主线程运行了");
//调用futureTask获取返回结果
int res1 = futureTask.get();//保证myCallable执行完毕
int res2 = futureTask1.get();//保证myCallable2执行完毕
//能够确保子线程运行完毕之后再继续运行
System.out.println("主线还在运行过程中");
System.out.println("计算的结果是"+(res1+res2));
线程的常用方法
getName():可以获取线程的名称
- sleep(); 可以让线程进入到休眠状态,并且会阻塞当前线程
join():让父线程等待子线程结束之后才能继续运行,即在一个线程A中调用另一个线程B的join方法,那么当前线程A停止,等待另一个线程B执行完毕之后再执行
String name = Thread.currentThread().getName();
System.out.println(name+"1");
System.out.println(name+"2");
Thread child = new Thread(){
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"1");
System.out.println(name+"2");
System.out.println(name+"3");
}
};
child.start();
System.out.println(name+"3");
System.out.println(name+"4");
System.out.println(name+"5");
child.join();
int i =100;
while(i>0){
System.out.println(name+i--);
}
setDaemon,将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要再继续执行了,必须要是start调用之前调用
Thread thread = new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("子线程"+i);
}
}
};
thread.setDaemon(true);//父类线程关闭之后,子线程马上关闭
thread.start();
setPriority:修改获取线程资源的优先级
Thread thread1 = new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("*子线程1*"+i);
}
}
};
Thread thread2 = new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("*子线程2*"+i);
}
}
};
thread2.setPriority(Thread.MAX_PRIORITY);
thread1.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
Thread.yield();退让,会先去判断是否有和当前线程相同优先级的线程,如果没有,则自己继续执行,如果有,则将CPU资源让给它,然后进入到就绪状态。
ThreadLocal
simpleDateFormat是被多线程使用的时候,会出现线程并发的问题、、
实现一个工具
public class DateUtil {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException {
return sdf.format(date);
}
public static Date parse(String strDate) throws ParseException{
return sdf.parse(strDate);
}
}
并发的场景
public static void main(String[] args) throws InterruptedException {
//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
//执行15个任务
for (int i = 0; i < 5; i++) {
executorService.execute(() -> {
try {
System.out.println(DateUtil.parse("2022-08-03 15:00:20"));
} catch (ParseException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
方法1:可以使用synchronized解决,影响性能
- 方式2:创建多个对象,浪费内存
方式3:通过ThreadLocal为每个线程创建一个独立的对象副本
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//使用ThreadLocal
static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
public static String formatDate(Date date)throws ParseException {
//从threadlocal中获取副本对象
SimpleDateFormat simpleDateFormat = threadLocal.get();
if(simpleDateFormat == null){
//添加每个线程的副本对象
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
simpleDateFormat = threadLocal.get();
}
return simpleDateFormat.format(date);
}
public static Date parse(String strDate) throws ParseException{
SimpleDateFormat simpleDateFormat = threadLocal.get();
if(simpleDateFormat == null){
//添加每个线程的副本对象
threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
simpleDateFormat = threadLocal.get();
}
return simpleDateFormat.parse(strDate);
}
Threadlocal:可以为每个线程,创建一个对象的独立副本内存
- set方法:本质上就是通过一个Map集合,以线程作为key,存储对象
- get方法:在获取对象的时候直接以线程作为key获取存储的对象
- initialValue:在线程使用ThreadLocal的时候如果返现存储的对象是null,即第一次使用的时候,会直接调用initialValue创建对象
//使用ThreadLocal
static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
//在当前线程第一次调用get之前就初始化了存储的对象
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};