线程
-
线程Thread是一个程序内部的一条执行流程
-
程序中如果只有一条执行流程,那这个程序就是单线程的程序
多线程
-
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)
创建线程
多线程创建方式之一:继承Thread类
package itheima.demo.thread; public class demo1 { //main方法本身是由一条主线程负责推荐执行的 public static void main(String[] args) { //创建线程类的对象,代表线程 MyThread mt = new MyThread(); //调用线程的start方法,启动线程 mt.start(); for (int i=0;i<5;i++){ System.out.println("zhu线程执行了"+i); } } } //定义一个子类继承Thread类,成为一个线程类 class MyThread extends Thread{ //重写Thread类中的run方法 @Override public void run() { //线程要执行的任务 for (int i=0;i<5;i++){ System.out.println("zi线程执行了"+i); } } }
-
定义一个子类MyThread继承线程类java.lang.Thread,重写run方法
-
创建MyThread类的对象
-
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
方式一优缺点:
-
优点:编码简单
-
缺点:线程类已经继承Thread类,无法继承其他类,不利于功能扩展
创建线程的注意事项
1.启动线程必须是调用start方法,不是调用run方法。
-
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行
-
只有调用start方法才是启动一个新的线程执行
2.不要把主线程任务放在启动子线程之前
多线程创建方式二:实现Runnable接口
1.定义一个线程任务类MyRunnable实现Runnable接口,重写run方法
2.创建MyRunnable任务对象
3.把MyRunnable任务对象交给Thread处理
4.调用线程对象的start方法()启动线程
package itheima.demo.thread; public class demo2 { public static void main(String[] args) { Runnable mr = new MyRunnable(); //把线程任务对象交给一个线程对象来处理 Thread t = new Thread(mr); t.start(); for (int i=0;i<5;i++){ System.out.println("主线程执行了"+i); } } } class MyRunnable implements Runnable{ @Override public void run() { for (int i=0;i<5;i++){ System.out.println("子线程执行了"+i); } } } //定义一个线程任务类实现Runnable接口
方式二优缺点
-
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
-
缺点:需要多一个Runnable对象,如果线程有执行结果是不能直接返回的
匿名内部类写法
1.可以创建Runnable匿名内部类对象
2.再交给Thread线程对象
3.再调用线程对象的start()启动线程
package itheima.demo.thread; public class demo3 { public static void main(String[] args) { Runnable mr = new MyRunnable(){ @Override public void run(){ for (int i=0;i<5;i++){ System.out.println("子线程执行了"+i); } } }; Thread t = new Thread(mr); t.start(); for (int i=0;i<5;i++){ System.out.println("主线程执行了"+i); } } }
创建线程方式三:实现Callable接口
前两种线程创建方式都存在一个问题
-
假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果
怎么解决这个问题
-
jdk5.0提供了Callable接口和FutureTask类来实现
-
这种方式最大的优点:可以返回线程执行完毕后的结果
1.创建任务对象
-
定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据
-
把Callable类型的对象封装成FutureTask(线程任务对象)
-
把线程任务对象交给Thread对象
-
调用Thread对象start方法启动线程
-
线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果
package itheima.demo.thread; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class demo3 { public static void main(String[] args) { // 3.创建线程类对象,代表线程 Callable<String> mc = new MyCallable(50); // 4. 创建线程对象,代表线程 FutureTask<String> ft = new FutureTask<>(mc); // 未来任务对象的作用 // 本质是一个Runnable接口,可以交给Thread线程对象执行 // 可以获取线程执行结果 // 5.把FutureTask对象作为参数交给Thread线程对象执行 Thread t = new Thread(ft); t.start(); try { //如果发现线程还没有执行完毕,则让出CPU,等待执行完毕 String result = ft.get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } } } //1.定义一个类实现callable接口 class MyCallable implements Callable<String>{ private int num; public MyCallable(int num){ this.num = num; } // 2.实现call方法,定义线程执行体 @Override public String call() throws Exception { int sum = 0; for (int i=0;i<num;i++){ System.out.println("线程执行了"+i); sum+=i; } return "线程执行完毕,结果是"+sum; } }
线程常用方法
package itheima.demo.thread; public class ApiDemo4 { public static void main(String[] args) { MyThread1 mt = new MyThread1(); //调用线程的start方法,启动线程 mt.setName("线程1"); mt.start(); System.out.println(mt.getName()); MyThread1 mt2 = new MyThread1(); mt2.start(); System.out.println(mt2.getName()); //获取当前线程 Thread m=Thread.currentThread(); System.out.println(m.getName());//main } } //定义一个子类继承Thread类,成为一个线程类 class MyThread1 extends Thread{ //重写Thread类中的run方法 @Override public void run() { //线程要执行的任务 for (int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"zi线程执行了"+i); } } }
package itheima.demo.thread; public class ApiDemo5 { public static void main(String[] args) { //Sleep方法,线程休眠 for (int i=0;i<5;i++){ System.out.println("zhu线程执行了"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package itheima.demo.thread; public class ApiDemo6 { public static void main(String[] args) { MyThread2 mt = new MyThread2(); mt.start(); for (int i = 0; i < 5; i++) { if (i == 3){ try { mt.join();//插队,先让线程mt执行完再执行主线程 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"-->"+i); } } } class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName()+"-->"+i); } } }
线程安全
多个线程,同时操作共享资源的时候,可能会出现业务安全问题
出现原因
-
存在多个线程同时执行
-
同时访问一个共享资源
-
存在修改该共享资源
线程同步的常见方案
-
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来
同步代码块
-
作用:把访问共享资源的核心代码上锁,以此保证线程安全
-
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
同步锁注意事项
-
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则就会出bug
锁对象随便选择一个唯一的对象好不好呢
-
不好,会影响其他无关线程的执行
锁对象使用规范
-
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
-
静态方法建议使用字节码(类名.class)对象作为锁对象
synchronized (同步锁){ 访问共享资源的核心代码 }
同步方法
-
作用:把访问共享资源的核心方法上锁,以此保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表){ 操作共享资源的代码 }
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
同步方法底层原理
-
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码
-
如果方法是实例方法,同步方法默认用this作为锁对象
-
如果方法默认是静态方法,同步方法默认用类名.class作为锁对象
同步代码块好还是同步方法好
同步代码块效率更高
Lock锁
-
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
-
Lock是接口,不能直接实例化,可以采用他的实现类ReentrantLock来构建Lock锁对象
private final Lock lk=new ReentrantLock(); lk.lock();//上锁 finally{ lk.unkock(); }
锁对象建议加上final修饰,防止别人篡改
释放锁操作放到fianlly代码块中,确保锁用完了一定会被释放
线程池
-
线程池是一个可以复用线程的技术
不使用线程池的问题
-
用户发起一个请求,后台就需要创建一个新线程来处理,创建新线程的开销很大,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能
创建线程池
-
JDK5.0起提供了代表线程池的接口ExecutorService
如何创建线程池对象
-
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创一个线程池对象
参数一:corePoolSize 指定线程池的核心线程的数量 正式工3
参数二:maximumPoolSize 指定线程池的最大线程数量 最大员工数5 临时工2
参数三:keepAliveTime 指定临时线程的存活时间 临时工空闲多久被开除
参数四:unit 指定临时线程存活的时间单位 (秒,分,时,天)
参数五:workQueue 指定线程池的任务队列 客人排队的地方
参数六:threadFactory 指定线程池的线程工厂 HR
参数七:hander 指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了应该怎么处理)
package itheima.demo.thread; import java.util.concurrent.*; public class Demo7 { public static void main(String[] args) { //1.使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象 ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //2.使用线程池对象来执行任务,看是否会复用线程 MyRunnable1 mr = new MyRunnable1(); pool.execute(mr);//创建线程,自动启动线程处理这个任务 pool.execute(mr); pool.execute(mr); pool.execute(mr); pool.execute(mr); //关闭线程池 pool.shutdown();//线程池不再接受新的任务,但是会等待正在执行的任务完成 pool.shutdownNow();//线程池不再接受新的任务,并且会打断正在执行的任务 } } package itheima.demo.thread; public class MyRunnable1 implements Runnable{ @Override public void run() { for (int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"执行了"+i); } } }
-
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
package itheima.demo.thread; import java.util.concurrent.*; public class Demo7 { public static void main(String[] args) { //1.使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象 ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()); //2.使用线程池处理callable任务 Future<String> f1=pool.submit(new MyCallable1(50)); Future<String> f2=pool.submit(new MyCallable1(100)); try { System.out.println(f1.get()); System.out.println(f2.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } package itheima.demo.thread; import java.util.concurrent.Callable; //1.定义一个类实现callable接口 public class MyCallable1 implements Callable<String> { private int num; public MyCallable1(int num){ this.num = num; } // 2.实现call方法,定义线程执行体 @Override public String call() throws Exception { int sum = 0; for (int i=0;i<=num;i++){ // System.out.println(Thread.currentThread().getName()+"线程执行了"+i); sum+=i; } return Thread.currentThread().getName()+num+"和结果是"+sum; } }
线程池的注意事项
什么时候开始创建临时线程
-
新任务提交时发现核心线程都在忙,任务队列都满了,并且还可以创建临时线程
什么时候拒绝新任务
-
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
通过Executors创建线程池
-
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象
-
这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象
package itheima.demo.ThreadSafe; import java.util.concurrent.*; public class Demo8 { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(3); Future<String> f1=pool.submit(new MyCallable(50)); Future<String> f2=pool.submit(new MyCallable(50)); try { System.out.println(f1.get()); System.out.println(f2.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
newFixedThreadPool(int nThread) 创建固定数量的线程
newSingleThreadExecutor() 创建只有一个线程的线程池对象
newCachedThreadPool() 线程数量随着任务数增加而增加
newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务
Executors使用可能存在的陷阱
-
FixedThreadPool和SingleThreadPool允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求,从而导致OOM
-
CachedThreadPoo和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量线程,从而导致OOM
-
该工具类底层基于ThreadPoolExecutor实现的线程池对象