Java多线程

  1. 基本概念:程序、进程、线程

程序:静态的代码
进程:运行起来的程序
线程:进程进一步细分为线程,是程序内部的一个执行路径。每个线程有独立的虚拟机栈、程序计数器,所以线程共享同一个进程的方法区和堆空间。多个线程共享数据虽然方便,但是带来一些安全隐患(谁来操作共享数据)。

java程序java.exe至少有三个线程:主线程main()、异常处理线程、垃圾回收线程GC。

并行:多个CPU同时执行多个任务
并发:单个CPU不断切换执行多个任务

  1. 线程的创建和使用

Thread类
两种方法创建线程:
第一种:造一个子类继承Thread类,重写run()方法,造一个子类对象,调用start()方法。
start()方法的作用:1. 启用当前线程;2. 调用run()方法。不可以直接调用run()方法。
一个对象start()方法只可以调用一次,再次调用会抛出异常。
第二种:实现Runnable接口,实现run()方法,然后创造Thread类的实例,将Runnable接口实现类的一个实例放到Thread构造器的形参中。
new Thread§.start();

比较两种方式:
开发中:优先选择实现Runnable接口的方式,
原因:1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合来处理多个线程共享数据的情况。
联系:Thread类本身也实现了Runnable接口。
相同点:两种方式都需要重写run()方法。

Thread类中的常用方法
start()
run()
currentThread()
getName()
setName()
yield(): 释放当前cpu的执行权(有可能再次被分配到)。
join(): 在线程A中调用线程B的join()方法,线程A进入阻塞状态,直到线程B执行完毕,线程A才结束阻塞状态。
stop(): 直接结束当前线程,deprecated.
sleep(long milliseconds): 当前线程
isAlive(): 判断当前线程是否存活。

线程的调度
时间片
抢占式
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY: 5
涉及的方法
getPriority(); setPriority();
优先级高未必一定先执行。

  1. 线程的生命周期

线程的状态:新建、就绪、运行、阻塞、死亡

  1. 线程的同步(关于线程安全问题,有三种解决方法)

问题的提出:多个线程有共享的数据,操作共享数据过程中使得数据出现安全问题

如何解决:当线程A操作共享数据时,其他线程不能参与进来,直到线程A操作完成时,其他线程才可以开始操作共享数据。
即使线程A出现阻塞,也不能改变这一流程。

在Java中通过同步机制来解决线程安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码即为需要同步的代码。
共享数据:多个线程共同操作的变量,比如三个窗口卖100个tickets.
同步监视器:俗称“锁”,任何一个类的对象都可以充当这个锁。要求:多个线程必须共用同一把锁

实现Runnable接口一般使用当前对象this作为监视器
继承Thread考虑使用当前类Xxx.class(类只会加载一次,因此也是唯一的)
解决了线程安全问题,但是操作同步代码时只能单个线程执行。
同步代码块包含的代码不能多也不能少。

方式二:同步方法
操作共享数据的代码完整的声明在一个方法中,将此方法同步

synchronized void show(){} 此时同步监视器为this
static synchronized void show(){} 静态同步方法,监视器为当前类Xxx.class

方式三:同步锁 JDK5.0增加
ReentrantLock lock = new ReentrantLock(boolean fair)
try{
lock.lock();
//同步代码
}finally{
lock.unlock();
}
需要手动释放unlock。

死锁问题
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。
避免同步方法嵌套。

线程安全的懒汉式单例模式

class Bank {
    private Bank(){}
    private static Bank instance = null;
    public static Bank getInstance() {
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}
  1. 线程的通信

wait():当前线程进入阻塞状态。执行wait()会释放线程锁。
notify():唤醒wait的1个线程,如果有多个线程wait,则唤醒优先级高的(或随机)。
notifyAll():唤醒所有wait的线程。

这三个方法只能在同步代码块或者同步方法中使用。
这三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现IllegalMonitorStateException。
这三个方法是定义在Object类中的。

  1. JDK5.0新增的线程创建方式(总共有四种,原有两种,新增两种)

新增方式一:实现Callable接口
1)创建一个实现Callable接口的实现类
2)实现call()方法,将此线程需要执行的操作声明在call()方法中
3)创建Callable接口实现类的对象
4)将此Callable实现类的对象作为参数传递到FutureTask()构造器当中
5)将FutureTask的对象传递到Thread类的构造器中,调用start()方法。
6)获取Callable中call()方法的返回值(调用get()方法。可选,如果不需要返回结果可以不执行)。

与Runnable相比的强大之处:
1)call()可以有返回值
2)call()可以抛出异常,被外面的操作捕获异常信息
3)Callable是支持泛型的。

新增方式二:使用线程池

提高响应速度,减少了创建新线程的时间
降低资源消耗,重复利用,不需要每次都创建
便于线程管理。

ExecutorService接口
Executors工具类

常见面试题:
sleep()和wait()的异同
相同点:都可以使当前线程进入阻塞状态
不同点:
1)两个方法声明的位置不同,Thread类中声明了sleep(),Object类中声明了wait();
2) 调用的要求不同。sleep()方法可以在任何需要的场景下调用,wait()方法必须使用在同步代码块(同步方法)中。
3)是否释放同步监视器(线程锁):sleep()不释放线程锁,wait()释放线程锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值