多线程详解
目录线程进程并行并发优点何时需要多线程Thread类常用方法线程的调度java的调度方法线程优先级用户线程与守护线程创建线程与使用1.继承Thread类2.实现Runnable接口两种方式对比3.实现Callable接口如何理解Callable比Runnable强大4.线程池线程池相关API线程的生命周期线程安全线程同步方法一:同步代码块方法二:同步方法线程的死锁问题Lock锁(公平,非公平)两种
目录
线程进程
单核cpu是假的多线程,在一个事件单元中,只能执行一个线程
多核才可以更好发挥多线程的效率
java程序至少有三个线程 主线程,gc线程,垃圾回收线程
并行并发
并行:多个cpu同时执行多个任务
并发:一个cpu同时执行多个任务
优点
单核cpu,先后完成多个任务,肯定比多个线程来完成用的时间更短,为何使用多线程?
单核切换线程要消耗时间,如果是多核肯定第二种快
1.提高应用程序的响应,对兴华界面更有意义,可增强用户体验
2.提高计算机系统cpu的利用率
3.改善程序结构,将复杂的进程分为多个线程,独立运行,利于理解和修改
何时需要多线程
程序需要同时执行两个或多个任务
程序需要实现一些需要等待的任务时,如文件输入,文件读写,网络操作,搜索等
需要一些后台运行的程序时
Thread类常用方法
void start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作
String getName():返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread():返回当前线程,在Thread子类中就是this,通常用于主线程和Runnable实现类
yield():释放当前cpu执行权,线程让步
join():a线程中调用b线程的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,a结束阻塞状态
stop():已过时,当执行此方法,强制结束当前线程,直接到死亡态
sleep(毫秒):让当前线程睡眠指定时间,阻塞状态
isalive():执行完就返回false
线程的调度
调度策略:
时间片
抢占式:高优先级的线程抢占cpu
java的调度方法
同优先级线程组成先进先出队列(先来先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程优先级
java中Thread类中三个常量设置优先级最大为10
getPriority():返回线程优先级
setPeiority(int newPriority):改变线程的优先级
说明:线程创建时继承父线程的优先级,低优先级只是获得调度的概率低,并非一定在高优先级之后才被调用
用户线程与守护线程
各方面几乎相同,唯一的区别是判断jvm何时离开
守护线程是用来服务用户线程的,通过在start()方法前调用thread.Setdaemon(true)可以把一个用户线程变成一个守护线程
java垃圾回收就是一个典型的守护线程
若jvm中都是守护线程,当前jvm将退出
创建线程与使用
通过Thread类去实现
可抢占式的
1.继承Thread类
创建继承Thread类的子类
重写run方法
创建子类对象
通过此对象调用start方法(启动当前线程并自动调用了run方法)
可以用匿名子类的方式new Thread(){
public void run(){
}
}.start()
卖票线程安全问题三个窗口100张票
2.实现Runnable接口
创建一个实现了Runnable接口的类
实现类去实现Runnable中的抽象方法:run()
创建实现类的对象
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
卖票问题同一个线程任务都是100张
重票是线程安全问题
两种方式对比
联系:Thread类底层也是实现了Runnable接口
相同点:都要重写run(),将线程要执行的逻辑声明在run()中
优先选择实现接口的方式
实现的方式,没有类的单继承的局限性
实现的方式更适合来处理多个线程共享数据的情况
3.实现Callable接口
与Runnable相比,Callable功能更强大些
call()方法相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
futureTask的get()方法返回值为FutureTask构造器参数Callable实现类重写的call()方法的返回值
也是用thread的start方法启动线程
如何理解Callable比Runnable强大
call()可以有返回值
可以抛出异常,被外面的操作捕获,捕获异常的信息
支持泛型
4.线程池
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建,销毁,实现重复利用,类似生活中的公共交通工具
好处:提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理:
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止 ..........
线程池相关API
jdk5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
void excute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类,线程池的工厂类,用于创建并返回不同类的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,他可安排在给定延迟后运行命令或者定期的执行
ExecutorService service=Excutors.newFixedThreadPool(10);
使用service.设置属性需要将ExecutorService类型强制转化为ThreadPoolExecutor类型可以调用方法设置线程池属性
service.excute(new Runnable());
service.submit(new Callable());可以用futuretask接收
service.shutdown();
线程的生命周期
Thread.State内部类定义了状态
新建:当一个Thread类或其子类的对象声明被创建时,新生的线程处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待cpu时间片,此时它已具备了运行的条件,只是没分配到cpu资源
运行:当就绪的线程被调度并获得cpu资源时,便进入运行状态,run()方法定义了线程的操作与功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时中止自己的执行,进入阻塞状态
死亡:线程完成了他的全部工作或线程被提前强制性的中止或出现异常导致结束
线程安全
多个线程执行的不确定性引起执行结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据
卖票出现错票,重票
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票(共享数据)
如何解决:当一个线程在操作时,其它线程不能参与进来,直到当前线程操作完时,其他线程才可以操作,即使当前线程出现阻塞,也不能被改变
在java中通过同步机制实现
线程同步
解决了线程安全的问题(好处)
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:操作共享数据的代码
共享数据:多个线程共同操作的变量(数据)
同步监视器:俗称锁 任何一个类的对象都可以充当锁 要求多个线程共用同一把锁
局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低一些
方法二:同步方法
如果操作共享数据的代码完成的声明在一个方法中,我们不妨将此方法声明同步的
synchronized修饰方法时同步监视器是this(实现Runable方式用这个)
static synchronized修饰方法时同步监视器是类.class(继承Thread类用这个)
总结:同步方法仍然涉及到同步监视器,只是不需要我们显式声明,非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身
线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,不会继续
解决方法:专门的算法,原则 尽量减少同步资源的定义,尽量避免嵌套同步
Lock锁(公平,非公平)
jdk5.0开始,java提供更强大的线程同步机制,通过显示定义同步锁来实现同步,同步锁使用Lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
公平锁:private ReentrantLock lock=
new ReentrantLock(true);先进先出
不写默认非公平锁
lock.lock() lock.unlock()
两种方式对比
使用Lock锁,jvm将花费少量的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
优先使用顺序:Lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外),lock更灵活,可以手动加锁释放锁,同步代码块性能较好,粒度更小
线程通信
这三个方法只能出现在同步方法或者同步代码块中不是Lock
这三个方法的调用者是同步代码块,同步方法中的同步监视器
这三个方法定义在object中,因为任何对象都可以当做同步监视器,然后调用这三个方法只能同步监视器调用,所以任何对象都得有这个方法
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会被唤醒wait的一个线程,多个线程被wait,唤醒优先级高的那个,源码是唤醒任意一个,具体实现要看jvm厂商,唤醒规则,取决于jvm厂商不同厂商实现可能方式不同的,官方的HotSpot虚拟机,DequeueWaiter方法在进行唤醒时每次会将_WaitSet等待集合中获取第一个元素进行出队操作,notify是顺序唤醒的,照线程的休眠顺序,依次唤醒线程
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
更多推荐
所有评论(0)