线程对象是可以产生线程的对象。比如在
Java
平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线程的优势有:
(1)进程之间不能共享数据,线程可以;
(2)系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小;
(3)Java语言内置了多线程功能支持,简化了java多线程编程。
1、线程实现方式:
* 第一种方式:
* 1.自定义类,继承Thread;
* 2.重写run( )方法;
* 3.启动线程:
* 1).实例化自定义线程对象;
* 2).调用自定义线程对象的start()方法启动线程;
*
* 第二种方式:
* 1.自定义类,实现Runnable接口;
* 2.重写run( )方法:
* 3.启动线程:
* 1).实例化自定义的Runnable子类对象;
* 2).实例化一个Thread对象,将自定义对象作为参数传给Thread的构造方法;
* 3).调用Thread对象的start()启动线程;
区别:
一种是扩展java.lang.Thread类
另一种是实现java.lang.Runnable接口
好处:
在实际开发中通常以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类可以避免继承的局限,一个类可以继承多个接口,适合于资源的共享。
线程和进程的区别:
进程:
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,
一个进程包含1–n个线程。
(进程是资源分配的最小单位)。
线程:
同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
(线程是cpu调度的最小单位)。
2、多线程
好处:
1.可以充分利用CPU资源;
2.对于线程中代码,可以独立于”主线程”单独运行,它不用等待前面的代码执行完毕;
也就意味着,我们的程序可以同时做多件事情;
问题:
1.多线程有几种实现方案,分别是哪几种?
* 三种:
* 1.继承Thread,重写run()方法;
* 2.实现Runnable接口,重写run()方法;
* 3.(JDK5)实现Callable接口,重写call()方法;
*
3.启动一个线程是run()还是start()?它们的区别?
1).start()启动线程;
2).run是普通方法,我们把线程中需要做的事情写在这里;
start方法用于启动线程,它会调用run()方法;
4.sleep( )和wait( )方法的区别:
1).sleep:
a.属于Thread类;
b.让当前线程休眠指定的时间,当时间到,会自动醒来;
c.在同步方法中,不会释放锁;
2).wait:
a.属于Object类;
b.可以指定时间,也可以不指定;当时间到时,或者使用notify()或notifyAll()方法后,会醒来;
c.在同步方法中,会释放锁;
5.为什么wait( ),notify( ),notifyAll( )等方法都定义在Object类中:
1).因为任何对象都有可能被多线程并发访问,所以,任何对象都有让当前访问的线程”等待”的权利,也有需要”唤醒”的义务;
6.线程的生命周期
新建:
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
就绪:
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,
等待系统为其分配CPU。
一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行:
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞:
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待IO设备等资源,
将让出CPU并暂时停止自己的运行,进入阻塞状态。在阻塞状态的线程不能进入就绪队列。
死亡:
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。
7.”并行”与”并发”:
1).”并行”:是指多个线程在”某个时间段内”,在同时运行;
2).”并发”:是指多个线程在”某个时间点上”,同时访问一个共享资源;
3、并发
定义:
是指多个线程在”某个时间点上”,同时访问一个共享资源;
同步的方式:
1.synchronized:
1).同步代码块;
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
注:
同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
2).同步方法;
即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
2.(JDK5)
ReentrantLock
锁:
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注意:
ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
例子:
//
存钱
public
void
addMoney(
int
money) {
lock.lock();
//
上锁
try
{
count
+=
money;
System.out.println(System.currentTimeMillis()
+ “存进:” +
money);
}
finally
{
lock.unlock();
//
解锁
}
}
3.使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉
虚拟机
该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
好处:解决了并发性访问的问题;
弊端:由于要处理线程的等待、阻塞等问题,所以效率会降低;
4、线程池
1).获取线程池对象:JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
public static ExecutorService newCachedThreadPool():创建一个可根据需要创建新线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池
public static ExecutorService newSingleThreadExecutor():创建一个使用单个 worker 线程的 Executor
2).操作线城池:
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
它提供了如下方法
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
3).使用线程池的好处
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,
还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
但是要做到合理的利用线程池,必须对其原理了如指掌。
4).线程池的处理流程如下
-
首先线程池判断
基本线程池
是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
-
其次线程池判断
工作队列
是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
-
最后线程池判断
整个线程池
是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
5) .线程池的组成部分
一个比较简单的线程池至少应包含线程池管理器、工作线程、任务列队、任务接口等部分。
其中线程池管理器的作用是创建、销毁并管理线程池,将工作线程放入线程池中。工作线程是一个可以循环执行任务的线程,在没有任务是进行等待;任务列队的作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。
线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。
工作线程是一个可以循环执行任务的线程,在没有任务时将等待。
任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。
6). 线程的终止(shutdown、shutdownnow)
ExecutorService线程池就提供了shutdown和shutdownNow这样的生命周期方法来关闭线程池自身以及它拥有的所有线程。
shutdown:
(1)线程池的状态变成SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常。
(2)线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出
。
shutdown做了几件事:
1. 检查是否能操作目标线程
2. 将线程池状态转为SHUTDOWN
3. 中断所有空闲线程
shutdownNow:
(1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。
(2)终止等待执行的线程,并返回它们的列表;
(3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt()
,但是大家知道,
如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。
所以,ShutdownNow()并不代表线程池就一定立即就能退出,
它可能必须要等待所有正在执行的任务都执行完成了才能退出。
版权声明:本文为qq_32816979原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。