相关概念
- 程序(program):一段静态的代码,一组指令的有序集合。存储在硬盘上
- 进程(process):进程是程序的动态的执行,进程是最小的独立的内存空间
- 线程(Thread):是在进程中更小的运行单位,每个进程必须要有一个线程,线程是调用CPU的最小的独立单位。多个线程会共享同一个进程中的内存
-
线程的基本使用
第一种创建的方法
创建一个继承了Thread类的类,在run方法中实现子线程的代码
public class MyThread extends Thread{//当前线程的运行代码@Overridepublic 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;@Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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绑定到FutureTaskFutureTask<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(){@Overridepublic 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(){@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println("子线程"+i);}}};thread.setDaemon(true);//父类线程关闭之后,子线程马上关闭thread.start();
setPriority:修改获取线程资源的优先级
Thread thread1 = new Thread(){@Overridepublic void run() {for (int i=0;i<100;i++){System.out.println("*子线程1*"+i);}}};Thread thread2 = new Thread(){@Overridepublic 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");//使用ThreadLocalstatic 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创建对象
//使用ThreadLocalstatic ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){//在当前线程第一次调用get之前就初始化了存储的对象@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};
