java 多线程 入门_Java 多线程入门

基本概念

程序 :是指为完成特定任务 ,用某种编程语言编写的一组指令的集合 ,一段静态代码 。

进程 :是指程序的一次执行过程 ,或正在执行的一个程序 。出生 -> 存在 -> 消亡 》生命周期

线程 :程序内部的执行路径 。

例如 :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() 方法 ,该线程结束生命周期 。

02e132f590b73dd89a10ff64f1ba66af.png

理解线程的安全问题

线程的同步

方式一 :同步代码块

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;

}

}

小结释放锁的操作

df9e59cb6470007fdd49903200401d50.png

小结不释放锁的操作

c70634db7b2d28bb7e91951e6db20cc3.png

入门完结,相信你对多线程有了一个概念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值