基本概念
程序 :是指为完成特定任务 ,用某种编程语言编写的一组指令的集合 ,一段静态代码 。
进程 :是指程序的一次执行过程 ,或正在执行的一个程序 。出生 -> 存在 -> 消亡 》生命周期
线程 :程序内部的执行路径 。
例如 :360 木马查杀 、磁盘清理 、修复 。可以同时进行 支持多 线程 。
每个线程用于独立的 虚拟机栈 和 程序计数器 (pc)
一个 Java应用程序 java.exe至少有三个线程 ,主线程 gc 垃圾回收线程,异常处理线程
并行与并发
并行 :多个 CPU 同时执行多个任务 。例如 :多个人同时做不同的事。
并发 :一个 CPU(采用时间片) 同时执行多个任务 。例如 :秒杀 ,多个人做同一件事 。
多线程优点等
1、提高应用程序的相应 。对图形化界面更有意义 ,可增强用户体验 。
2、提高计算机系统 CPU 的利用率
3、改善程序结构 ,将既长又复杂的进程分为多个线程 ,独立运行 ,利于理解和修改 。
何时需要多线程
程序需要同时执行两个或多个任务
程序需要实现一些需要等待的任务时,如用户输入 、文件读写 操作 、网络操作 、搜索等 。
需要一些后台运行的程序时
创建多线程方式一
多线程的创建 ,方式一 :继承于Thread 类
1、创建一个继承于 Thread类 的子类
MyThread extends Thread
2、重写 Thread 类的 run() 方法 , 将此线程执行的操作声明在 run() 方法中 。
@Override
public void run() {
3、创建 Thread类的子类的对象 实例
MyThread thread = new MyThread();
4、通过此对象调用 start() 方法
1、启动当前线程 ,2、调用当前线程的 run() 方法
thread.start();
线程的常用方法
方法名称
作用
void start()
启动线程 ,并执行对象的 run() 方法
run()
线程在被调度时执行的操作
String getName()
返回线程的名称
void setName(String name)
设置线程名称
static Thread currentThread()
返回当前线程 。
static void yield()
线程让步
暂停当前正在执行的线程 ,把执行机会让给优先级更高(或相同)的线程
join()
当某个程序执行流中调用其他线程的 join() 方式时,调用线程将被阻塞,直到 join() 方法加入的join线程执行完为止 。
在线程a中调用 线程b的join(),此时线程b进入阻塞状态 ,直到 线程b完全执行完以后 ,线程a才结束阻塞状态
static void sleep(long millis)
等待时间 (指定时间 :毫秒)令当前活动线程在指定时间段内放弃对 CPU 的控制 ,时间到后重新排队 。
boolean isAlive()
返回 boolean ,判断线程是否存活
stop()
强制线程生命周期结束 (不推荐使用 )
线程优先级的设置
线程的优先级等级
MAX_PRIORITY = 10
MIN_PRIORITY = 1
NORM_PRIORITY = 5
涉及的方法
getPriority():返回线程优先值
setPriority(int newPriority) :改变线程的优先级
注意事项 :
线程创建时 ,继承父线程的优先级 。
低优先级只是获得调度的概率低 ,并非一定 高优先级之后才被调用 。
创建多线程方式二
创建多线程的方式二 :实现 Runnable 接口
1、创建一个实现了 Runnable 接口的类
2、实现类去 重写 Runnable 中的抽象方法 :run()
3、创建实现类的对象 (实例化)
4、将此对象参数传递到 Thread 类的构造器中 ,创建 Thread 类的对象
5、通过 Thread 类的对象调用 start()
两种方式的比较 :
1、实现Runnable 没有类的单继承的局限性
2、实现 Runnable 更适合来处理多个线程有共享数据的情况 。
public class Thread implements Runnable {// Thread类 实际上也是 实现 Runnable 接口
线程的生命周期
JDK 中 用 Thread.State 类定义了线程的几种状态 。
完整的生命周期 经历五种状态 :
1、新建状态(NEW):线程被创建后 ,就进入了新建状态 。 比如 : Thread t1 = new Thread();
2、就绪状态(Runnable):也被称为 “可执行状态” 。线程被创建后 ,其他线程调用 start() 方法来启动线程 。
例如 : t1.start() 处于就绪状态的线程 ,随时可能被CPU调度执行 。
3、运行状态(Running):线程获取CPU权限进行执行任务 。但需要注意 :线程只能从就绪状态进入到运行状态 。
4 、阻塞状态(Blocked):阻塞状态是因为线程因为某种原因放弃CPU使用权 ,暂时停止运行 。直到线程进入就绪状态,才有机会转到运行状态 。阻塞的情况分为三种 :
1、等待阻塞 :通过调用线程的 wait() 方法 ,让线程等待某工作的完成 。
2、同步阻塞 :线程在获取 synchronized 同步锁失败(因为锁被其他线程所占用) ,它会进入到同步阻塞状态 。
3、其他阻塞 :通过调用线程的 sleep() 或 join() 或发出了 I/O请求时 ,线程会进入到阻塞状态 。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时 ,线程重新转入就绪状态 。
5、死亡状态(Dead):线程执行完了或者因异常退出了 run() 方法 ,该线程结束生命周期 。
理解线程的安全问题
线程的同步
方式一 :同步代码块
synchronized (同步监视器) {
// 需要被同步的代码
}
说明 :
1、操作共享数据的代码 ,即视为需要被同步的代码
2、共享数据 :多个线程共同操作的变量 。例如 > ticket(车票) 就是共享数据
3、同步监视器 ,俗称 锁 ,任何一个类的对象都可以充当锁 。
要求 :多个线程必须共有同一把锁 。
补充 :
1、实现 Runnable ,同步监视器 可以使用 this 充当锁 ,代表当前调用run方法的对象 。
2、继承 Thread ,同步监视器 可以使用 当前类 充当 锁 ,类 也是对象 当前类.class
方式二 :同步方法
public synchronized void show() {
// 需要被同步的代码
}
同步方法总结 :
1、同步方法依然涉及到同步监视器 ,但是不需要我们显式的声明
2、非静态的同步方法 ,同步监视器为 this
静态的同步方法 ,同步监视器 为 当前类本身
实现 Runnable ,同步方法不需要使用 static 关键字
继承 Thread ,同步方法 需要使用 static 关键字
死锁的问题
Lock锁方式解决线程安全问题
JDK 5.0新增
Lock 锁 是接口 ,是控制多个线程对共享资源进行访问的工具 。
ReentrantLock 类实现了 Lock ,可以显式加锁、释放锁 。【常用】与 synchronized相同的并发性 。
1、实例化 ReentrantLock
Lock lock = new ReentrantLock();
2、调用锁的反法 lock()
try{
lock.lock()
// 需要被同步的代码
}
3、调用解锁方法 :unlock()
finally {
lock.unlock();
}
面试题 :
1、synchronized 与 lock 的异同 ?
· synchronized 自动释放锁 ,代码块锁 和方法锁
· lock 手动释放锁 ,只有代码块锁 ,
· 使用顺序:
Lock --》同步代码块 --》 同步方法 。
同步机制练习
class Account{
private double balance;
public Account(double balance) {
this.balance = balance;
}
public synchronized void deposit(double amt) {
if (amt > 0) {
balance += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱成功:账户余额:" + balance);
}
}
}
class MyThreadRunnable implements Runnable{
private Account account;
public MyThreadRunnable(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class ThreadRunnableTest {
public static void main(String[] args) {
Account account = new Account(0);
MyThreadRunnable runnable = new MyThreadRunnable(account);
Thread thread01 = new Thread(runnable);
Thread thread02 = new Thread(runnable);
thread01.setName("甲");
thread02.setName("乙");
thread01.start();
thread02.start();
}
}
线程的通信
涉及线程通信的 方法 ,只能够在 同步代码块 或者 同步方法中 使用 。
方法名称
作用
wait()
一旦执行此方法,当前线程就进入阻塞状态 ,并释放同步监视器
notify()
一旦执行此方法 ,就会唤醒被 wait 的一个线程 。如果有多个线程被 wait ,就唤醒优先级高的那个线程
notifyAll()
一旦执行此方法 ,就会唤醒所有被 wait 的线程
上述方法必须使用在 同步代码块 或 同步方法中 。
上诉方法的调用者 必须是同步代码代码块或同步方法中的同步监视器,否则会出现异常 IllegalMonitorStateException
都定义在 Object 类 中 。
面试题 :sleep()和 wait() 的异同 ?
1、声明位置不同 。Thread类中 声明 sleep(),Object类中声明 wait()
2、调用要求不同 。sleep() 可以在任何需要出现的地方调用 。wait() 只能在同步代码块或者同步方法 中使用 。
3、是否释放同步锁 。如果都在 同步代码块或同步方法中执行 ,sleep 不会释放锁 ,wait 会释放锁
生产者消费者 例题
Resource.java【工厂】
public class Resource {
int c = 0;
public synchronized void t1() {
if (c < 20) {
c++;
System.out.println("开始生产第" + c + "个商品");
} else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
}
public synchronized void put() {
if (c > 0) {
System.out.println("开始消费第" + c + "个商品");
c--;
} else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
}
}
producerExample.java【生产者】
public class producerExample implements Runnable{
private Resource resource;
public producerExample(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.t1();
}
}
}
consumerExample.java【消费者】
public class consumerExample implements Runnable{
private Resource resource;
public consumerExample(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.put();
}
}
}
Cashier.java【超市 ,收银员】
public class Cashier {
private int number;
private String merchandise;
public void setMerchandise(String merchandise) {
this.merchandise = merchandise;
}
public String getMerchandise() {
return merchandise;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
测试类
public class Test01 {
public static void main(String[] args) {
Resource resource = new Resource();
producerExample producerExample = new producerExample(resource);
consumerExample consumerExample = new consumerExample(resource);
new Thread(producerExample).start();
new Thread(consumerExample).start();
}
}
创建多线程方式三 :实现 Callable
JDK 5.0 新增
1、创建一个实现 Callable 的实现类
class MyCallableTest implements Callable {
2、Callable 类的 call() 方法 ,将此线程需要执行的操作声明在 call() 中 。
@Override
public Object call() throws Exception {
3、创建 Callable 接口实现类的对象
MyCallableTest myCallableTest = new MyCallableTest();
4、创建 FutureTask 对象 ,将 Callable 接口的实现类对象作为参数传递到 FutureTask 构造器中
FutureTask task = new FutureTask(myCallableTest);
5、创建 Thread对象 ,将 FutureTask 的对象作为参数传递到 Thread 类的构造器中 ,并调用 start() 。
new Thread(task).start();
6、如果需要获取返回值 ,使用 FutureTask 对象 调用 get() 方法 。【可选 】
Object o = task.get();
如何理解 Callable 与 Runnable 的区别 ?
1、call() 可以有返回值
2、call() 可以抛出异常 ,可以被捕获异常 ,获取异常信息 。
3、Callable 支持泛型 。
使用线程池的好处
使用线程池
背景 :经常创建和销毁 ,使用量特别大的资源 。比如并发情况下的线程 ,对性能影响很大 。
思路 :提前创建号多个线程 ,放入线程池中 ,使用时直接获取 ,用完放回池中 。可以避免频繁地创建和销毁 ,实现重复利用 。
好处 :
提高响应速度(减少创建线程的时间)
降低资源消耗 (重复利用线程 )
便于线程管理
CorePoolSize :核心池的大小
MaximumPoolSize :最大线程数
KeepAliveTim :线程没有任务时 最多保持多长时间后会终止 。
JDK 5.0 起提供了 线程池相关的API :ExecutorService 、Executors
ExecutorService :线程池接口 ,常见子类 ThreadPoolExecutor
Future submit(Callable task);// 执行任务/命令有返回值 。 适用于 实现Callable接口
void execute(Runnable command);// 执行任务 ,无返回值 适用于 实现Runnable接口
void shutdown();// 关闭连接池
Executors :工具类 、线程池的工厂类 。用于创建并返回不同类型的线程池 。
Executors.newFixedThreadPool(int nThreads)//创建一个可重用固定线程数的线程池
Executors.newCachedThreadPool()// 创建一个可根据需要创建新线程的线程池
Executors.newSingleThreadExecutor()// 创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(int corePoolSize)// 创建一个线程池 ,它可安排在给定延迟后运行命令或者定期地执行
使用 :
1、提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
2、执行指定的线程的操作 ,需要提供实现 Runnable接口 或Callable 接口的实现类的对象
service.execute(myCallableTest); // 适合 实现Runnable接口
service.submit(myCallableTest);// 适合 实现Callable接口
关闭连接池
service.shutdown();
写一个线程安全的懒汉式 【使用同步机制 修改为线程安全 。】饿汉式
class Bank{
private Bank(){}
private static Bank instance = null;
private static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
小结释放锁的操作
小结不释放锁的操作
入门完结,相信你对多线程有了一个概念