程序
是为完成特定任务、用某种语言编写好的代码。
我们电脑上会安装很多的应用程序比如Notepad++,微信,QQ,Word,PPT,Excel等。
进程
是一个正在运行的程序
线程的概念
线程是进程的执行单元 就是用来执行代码的
- 一个进程最少有一个线程
- 一个进程也可有多个线程,称为多线程程序
了解并发和并行
并行:
多个事件在同一时刻,同时执行
并发:
多个事件在同一时间段,交替执行
计算机中的单核CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码。
多线程运行原理
CPU在多个线程间快速切换, 造成"同时"运行的假象。
多线程的创建
多线程的创建方式一:继承Thread类
定义一个子类MyThread继承线程类java.lang.Thread,重写run()方
创建MyThread类的对象
调用线程对象的start()方法启动线程(启动后还是执行run方法的)
public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("正在执行子线程run方法" + i); } } }MyThread myThread = new MyThread(); myThread.start();
方式一优缺点
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展,没有返回值。
多线程的注意事项
1、启动线程必须是调用start方法,不是调用run方法。
2、不要把主线程任务放在启动子线程之前。
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。
只有调用start方法才是启动一个新的线程执行。
这样主线程一直是先跑完的,相当于是一个单线程的效果了。
多线程的创建方式二:实现Runnable接口
1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
2、创建MyRunnable任务对象
3、把MyRunnable任务对象交给Thread处理。
![](https://img-blog.csdnimg.cn/b105d788ce4944118b24342d9fdc3286.png)
4、调用线程对象的start()方法启动线程
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println("正在执行Runnable的Run方法"); } }public static void main(String[] args) { //写法一 MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); //写法二 new Thread(new Runnable() { @Override public void run() { System.out.println("666"); } }).start(); //写法三 new Thread(() -> { System.out.println("777"); }).start(); }
方式二的优缺点
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个Runnable对象,没有返回值。
多线程的第三种创建方式:利用Callable接口、FutureTask类来实现。
1、 创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
2、把线程任务对象交给Thread对象。
3、调用Thread对象的start方法启动线程。
4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { System.out.println("正在执行Callable的call方法"); return "666"; } }public static void main(String[] args) throws Exception { MyCallable myCallable = new MyCallable(); FutureTask<String> futureTask = new FutureTask<>(myCallable); Thread thread = new Thread(futureTask); thread.start(); Object o = futureTask.get(); System.out.println(o); }
线程创建方式三的优缺点
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
线程操作相关方法
常用方法补充:
线程安全
什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
例如:
取钱的线程安全问题
场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?
public class Account { // 账户 private String id; // 卡号 private double balance; // 余额 // 取钱的方法 public void drawMoney(double money) { // 1. 先判断取的钱够不够 if (balance >= money) { String name = Thread.currentThread().getName(); // 2. 取钱 System.out.println(name + "取出了" + money + "元"); // 3. 更新余额 balance -= money; System.out.println("当前余额为:" + balance); } else { System.out.println("您的余额不足"); } } }public class UserThread extends Thread{ private Account account; public UserThread(Account account ){ // 用于接收一个账户信息 this.account = account; } @Override public void run() { account.drawMoney(100000); } }public static void main(String[] args) { Account account = new Account("vip0001" , 100000); UserThread xiaoMing = new UserThread(account); xiaoMing.setName("小明"); UserThread xiaoHong = new UserThread(account); xiaoHong.setName("小红"); xiaoMing.start(); xiaoHong.start(); //balance = -100000.0 }
线程同步
让多个线程实现先后依次访问共享资源,这样就解决了安全问题
线程同步的常见方案
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能
再加锁进来。
同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
synchronized(同步锁) {
访问共享资源的核心代码
}public void drawMoney(double money) { synchronized (this) { // 一般,对于实例方法用 this 静态方法用 类名.class // 1. 先判断取的钱够不够 if (balance >= money) { String name = Thread.currentThread().getName(); // 2. 取钱 System.out.println(name + "取出了" + money + "元"); // 3. 更新余额 balance -= money; System.out.println("当前余额为:" + balance); } else { System.out.println("您的余额不足"); } } }
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。
同步锁的注意事项
对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
同步方法
把访问共享资源的核心方法给上锁,以此保证线程安全
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}public synchronized void drawMoney(double money) { //默认 锁对象 实例方法是this 静态方法是 类名.class 字节码对象 // 1. 先判断取的钱够不够 if (balance >= money) { String name = Thread.currentThread().getName(); // 2. 取钱 System.out.println(name + "取出了" + money + "元"); // 3. 更新余额 balance -= money; System.out.println("当前余额为:" + balance); } else { System.out.println("您的余额不足"); } }
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
同步方法底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
private final ReentrantLock lock = new ReentrantLock();public void drawMoney(double money) { try { lock.lock(); // 1. 先判断取的钱够不够 if (balance >= money) { String name = Thread.currentThread().getName(); // 2. 取钱 System.out.println(name + "取出了" + money + "元"); // 3. 更新余额 balance -= money; System.out.println("当前余额为:" + balance); } else { System.out.println("您的余额不足"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }
线程通信
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。
线程通信的常见模型(生产者与消费者模型)
生产者线程负责生产数据
消费者线程负责消费生产者生产的数据。
注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!
Object类的等待和唤醒方法:
public class Desk {
// 使用一个集合用于存放包子
private ArrayList<String> list = new ArrayList<>();
private int index;// 定义一个方法,用于生产包子
public synchronized void create() {
String name = Thread.currentThread().getName();
if (list.size() == 0) {
index++;
System.out.println(name + "生产了第" + index + "个包子");
list.add("第" + index + "个包子");
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}// 定义一个方法,用于吃包子
public synchronized void eat() {
String name = Thread.currentThread().getName();
if (list.size() == 1) {
String s = list.get(0);
System.out.println(name + "吃掉了" + s);
list.remove(0);
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}}
public static void main(String[] args) {
// 1.先创建一个桌子
Desk desk = new Desk();// 2.创建3个厨师【线程】
new Thread(() -> {
while (true) {
desk.create();
}
}, "厨师1号").start();new Thread(() -> {
while (true) {
desk.create();
}
}, "厨师2号").start();new Thread(() -> {
while (true) {
desk.create();
}
}, "厨师3号").start();
// 3.创建2个吃货【线程】new Thread(() -> {
while (true) {
desk.eat();
}
}, "吃货1号").start();new Thread(() -> {
while (true) {
desk.eat();
}
}, "吃货2号").start();
}
线程的生命周期
java总共定义了6种状态
6种状态都定义在Thread类的内部枚举类中
public class Thread{
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}...
}
线程池
每次创建线程存在的问题
1、每个线程在创建和销毁时都要写占用CPU,内存等资源。
2、在并发系统中,频繁创建和销毁大量线程, 对性能影响很大,甚至会导致系统资源耗尽的风险。
线程池介绍
线程池是一个容器,可以保存一些长久存活的线程对象,负责创建、复用、管理线程。
线程池的优势
降低资源消耗,重复利用线程池中线程,不需要每次都创建、销毁 。
便于线程管理,线程池可以集中管理并发线程的数量。
Executors工具类创建线程池【不使用】
JDK 1.5起提供了线程池,Executors类是线程池的工具类,通过Executors工具类可以创建线程池。
ExecutorService:真正的代表线程池的接口。常见实现类ThreadPoolExecutor
ExecutorService线程池中的常用方法
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { return Thread.currentThread().getName() + "执行了!"; } }public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newFixedThreadPool(3); MyCallable m1 = new MyCallable(); Future<String> future1 = threadPool.submit(m1); System.out.println(future1.get()); Future<String> future2 = threadPool.submit(m1); System.out.println(future2.get()); Future<String> future3 = threadPool.submit(m1); System.out.println(future3.get()); Future<String> future4 = threadPool.submit(m1); System.out.println(future4.get()); threadPool.shutdown(); //pool-1-thread-1执行了! //pool-1-thread-2执行了! //pool-1-thread-3执行了! //pool-1-thread-1执行了! }
使用ThreadPoolExecutor创建线程池【使用】
Executors使用可能存在的弊端
高并发系统环境中使用Executors如果不注意可能会出现资源耗尽的风险。
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit, -》TimeUnit.单位
BlockingQueue<Runnable> workQueue, -》new ArrayBlockingQueue<>(6)
ThreadFactory threadFactory, -》Executors.defaultThreadFactory(),RejectedExecutionHandler handler)
-》new ThreadPoolExecutor.AbortPolicy()
参数一 corePoolSize:指定线程池的线程数量(核心线程) -》 正式员工数量
参数二 maximumPoolSize:指定线程池可支持的最大线程数 -》最大员工数量 = 正式员工 + 临时员工数量
参数三 keepAliveTime:指定临时线程的最大存活时间 -》临时工没事做多久解雇
参数四 unit:指定线程存活时间的单位(秒、分、时、天) -》时间单位
参数五 workQueue:指定任务存放的队列 -》等候区
参数六 threadFactory:指定用谁来创建线程 -》人才市场,提供员工
参数七 handler:任务满的时候如何处理(拒绝策略) -》客人太多坐不下怎么办
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 1.核心员工
6, // 2.总的员工数量 = 临时工 + 核心员工
100, // 3.指定时间
TimeUnit.SECONDS, // 4.指定时间单位
new ArrayBlockingQueue<>(6), // 5.等候区
Executors.defaultThreadFactory(), // 6. 人才市场
new ThreadPoolExecutor.AbortPolicy() // 7. 学会拒绝
// new ThreadPoolExecutor.DiscardPolicy() // 直接抛弃任务,不抛异常
// new ThreadPoolExecutor.DiscardOldestPolicy() // 抛弃最开始等待的任务,把当前的放进去
// new ThreadPoolExecutor.CallerRunsPolicy() // 当前线程去执行任务
);
Runnable r = () -> {
System.out.println(Thread.currentThread().getName() + "执行了任务~~~");
};
// 核心线程
pool.submit(r);
pool.submit(r);
pool.submit(r);// 6个线程处于等候区
pool.submit(r);
pool.submit(r);
pool.submit(r);
pool.submit(r);
pool.submit(r);
pool.submit(r);// 交给临时线程处理
pool.submit(r);
pool.submit(r);
pool.submit(r);// 如果任务再多就要拒绝
pool.submit(r);pool.shutdown();
}