关于多线程,首先熟练分清楚线程和进程的关系:
进程:内存中正在运行的一个程序
线程:进程中的一个最小执行单元。一个进程最少得有一个线程(Java程序中一个请求就是一个线程)。
一、创建多线程
的方式有四种:
1.继承Thread类
1.定义一个子类继承Thread类,并重写run方法 2.创建Thread的子类对象 3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码) public class Test1 extends Thread { //2.必须重写Thread类的run方法 @Override public void run() { // super.run(); //描述线程的执行任务 //任务体 for (int i = 0; i <= 5; i++) { System.out.println("子线程MyThread输出:"+i); } } public static void main(String[] args) { //创建线程类的对象代表一个线程 Test1 test1 = new Test1(); //启动线程(自动执行run方法) test1.start(); for (int i = 0; i <= 5; i++) { System.out.println("主线程MyThread输出:"+i); } } }
2.实现Runable接口
public static void main(String[] args) { Test21 test21 = new Test21(); new Thread(test21).start(); for (int i = 1; i <= 5; i++) { System.out.println("主线程main输出 ===》" + i); } //匿名内部类 // 1、直接创建Runnable接口的匿名内部类形式(任务对象) Runnable target = new Runnable() { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("子线程1输出:" + i); } } }; new Thread(target).start(); // 简化形式1: new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println("子线程2输出:" + i); } } }).start(); }
3.实现Callable接口
1.先定义一个Callable接口的实现类,重写call方法
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Thread的start()方法启动线程(启动后会自动执行call方法)
等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
6.调用FutrueTask对的get()方法获取返回结果
public class MyCallable2 implements Callable<BigDecimal> { private int start1; private int end; public MyCallable2(int start1, int end) { this.start1 = start1; this.end = end; } @Override public BigDecimal call() throws Exception { BigDecimal sum= BigDecimal.valueOf(0); for (int i = start1; i <= end; i++) { sum=sum.add(BigDecimal.valueOf(i)); } return sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { //3.创建一个Callable对象 Callable<BigDecimal> c1 = new MyCallable2(1,2000); Callable<BigDecimal> c2 = new MyCallable2(2001,4000); //4.把Callable的对象封装成一个FutureTask对象(任务对象) //未来任务对象的作用? //4.1、是一个任务对象,实现了Runnable对象 //4.2、可以在线程执行完毕之后,用未来任务调用get方法获取线程执行完毕后的结果。 FutureTask<BigDecimal> bdft1 = new FutureTask<>(c1); FutureTask<BigDecimal> bdft2 = new FutureTask<>(c2); //5.把任务交给一个THread对象 new Thread(bdft1).start(); new Thread(bdft2).start(); //6.获取线程执行完毕后返回的结果 // 注意:如果执行到这儿,假如上面的线程还没有执行完毕 // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。 BigDecimal b1 = bdft1.get(); BigDecimal b2 = bdft2.get(); String s = b1.add(b2).toString(); System.out.println("线程计算的累加和"+s);
4.创建线程池
线程池的核心参数
核心线程数,最大线程数,临时线程的存活时间,阻塞队列,线程工厂,任务丢弃策略(拒绝策略);
拒绝策略分为四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务
关于创建的线程池的工作执行流程,当有任务提交进来的时候,
(1)我们要判断线程池是否达到核心线程数最大了,没有达到,就利用核心线程来创建一个工作线程来执行任务。
(2)如果核心线程都在执行任务了,就判断工作队列是否都满了,没满则将新提交的任务存储在工作队列中。
(3)当队列满了,判断线程数是否达到最大线程,如果没有则创建新的线程来执行任务。
(4)如果达到最大线程数了,就执行拒绝策略。
(5)执行拒绝策略
线程状态
新建,就绪,运行,阻塞,死亡
图解释的很详细了。
***关于经常被问到的
1,线程池创建了,里面有线程吗? 没有
2、继承Thread类和实现Runnable接口,有什么区别?你一般选择哪个?为什么?
根据单一原则,选择实现Runable接口
3,实现Runable接口和实现Callable接口有什么区别?
Callable可以有返回结果
关于解决线程安全问题
在共享的环境中,线程往往是不安全的,解决多线程中的安全问题,我们一般就是加锁
Synchronized 在jvm层面,是关键字,出异常的时候会释放锁,不会出现死锁
不会手动释放锁,只能等同步代码块和方法结束的时候释放锁。
Lock 在API层面,是一个接口 ,出异常的时候不会释放锁,会出现死锁,需要在finally中手动释放,
可以调用api手动释放
//Lock加锁 public class Account { private String nameId; private double money; //。。。构造器 //定义锁Lock private final Lock lk=new ReentrantLock(); //对执行方法枷锁 用try-finally进行闭锁 public void DrawMoney(double money){ //Lock方法 lk.lock();//加锁 try { //谁来取钱 String name = Thread.currentThread().getName(); //现在的钱大于余额是 if (this.money>=money){ System.out.println(name+"来取钱"+money+"成功!"); this.money-=money; System.out.println(name+"取钱后,余额还有"+this.money); }else { System.out.println(name+"来取钱:余额不足"); } } finally { //关锁 lk.unlock(); } } // 。。get、set方法 }
Synchronized
同步方法
public synchronized void DrawMoney(double money){ //同步方法 在方法中直接加,沈括乃日,对象方法。synchronized //谁来取钱 String name = Thread.currentThread().getName(); //现在的钱大于余额是 if (this.money>=money){ System.out.println(name+"来取钱"+money+"成功!"); this.money-=money; System.out.println(name+"取钱后,余额还有"+this.money); }else { System.out.println(name+"来取钱:余额不足"); } }
同步代码块,锁是括号里面的对象【必须共享】
public void DrawMoney(double money) { // synchronized (Account.class){//针对类 synchronized (this) { //谁取钱 String name = Thread.currentThread().getName(); if (this.money >= money) { System.out.println("恭喜"+name + ",获得红包" + money + "元"); this.money -= money; // System.out.println(name + "剩余" + this.money); } else { System.out.println("抱歉,"+name + ",红包不足"); } } }
同步代码块在底层的执行原理其实就是
使用 monitorenter 和 monitorexit 指令实现的,一个执行,一个释放
关于锁的定义