首页 > 代码库 > java多线程
java多线程
1 什么是进程,线程
进程:
进程是程序执行的一个实例。它是一个动态的概念。比如说,10个用户同时执行IE,那么就有10个独立的进程(尽管他们共享同一个可执行代码)。
进程的特点:
每一个进程都有自己的独立的一块内存空间(独立的堆和栈,不共享堆栈)、一组资源系统。进程由操作系统调度, 进程间的切换会有较大的开销。
守护进程:
在系统的引导的时候会开启很多服务,这些服务就叫做守护进程,随系统关闭时终止。
线程:
线程是程序中一个单一的顺序控制流程。
线程的优点:
同类的多个线程共享一块内存空间和一组系统资源,线程本身的数据通常只有CPU的寄存器数据,以及一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
并发执行指在同一时间内执行多个程序。但是从严格上讲,也不是绝对的同一时刻执行多个程序,只不过CPU在执行时通过时间片等调度算法不同进程高速切换。
CLR:公共语言运行库(Common Language Runtime)和Java虚拟机一样也是一个运行时环境
程序:
程序是一个存储在计算机系统的硬盘等存储空间中的静态文件。
PS:进程是一个动态的概念,是程序的一个运行实例,
程序是静态文件
Java中的进程通过ProcessBuilder.start()创建一个process.本次不过过多介绍。
2 Java多线程
2.1 线程实现
继承Thread
定义一个线程类,它继承类Thread并复写run()方法。Thread类中的相关函数可以启动线程、终止线程、线程挂起等。由于Java只支持单一继承,这种方法定义的类不能再继承其它的类了。
实现Runnable
新建一个类,实现Runable接口,重写 run()方法。这种方法定义的类仍然可以继承其它的类。
实现Callable
Java SE5中引入Callable,可以从方法call()中返回函数执行的值,并且必须使用ExecutorService.submit()方法调用它。submit()方法会产生Future对象,Future拿到了程序执行的返回值。
推荐使用Runable(Callable)的方式实现多线程(其实 Thread 只是实现 Runnable 接口)。
实现Runnable接口比继承Thread类所具有的优势:
1): 适合多个相同的程序代码的线程去处理同一个资源(比较容易实现资源共享)
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
PS: 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
2.2 线程状态
线程的状态有创建、就绪、运行、阻塞、终止
1.创建状态(NewBorn)
Thread thread = new Thread();来创建线程对象
仅仅是一个空的线程对象,系统不为它分配资源。
2.就绪状态()
调用start方法将线程的状态转换为就绪状态。线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级进行调度,从而使该线程拥有能够获得CPU时间片的机会。
3.运行状态(Running)
当线程获得CPU时间片后,它才进入运行状态,真正开始执行run()方法。
4.阻塞状态(Blocked)
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
......
5.消亡状态(Dead)
一般两种方法:
1).线程执行完毕,自然销毁
2). 一个未捕获的异常终止了run方法而使线程猝死。或者调用thread.stop()或thread.interrupt()等方法中断线程;
2.3 线程调度
线程调度是JVM根据线程的优先级在运行的多个线程进行系统级的协调的行为。避免多个线程争用有限资源而导致应用系统死机或者崩溃。
线程优先级:
Java线程用整数表示,取值范围是1~10,数字越大表明线程的级别越高,在创建线程对象之后可以调用线程对象的setPriority/getPriority方法设置/获取该线程的运行优先级。
Thread类有以下三个静态常量
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
2.4 线程同步
1.同步方法
即用synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法前,需要获得内置锁,否则就处于阻塞状态。
代码如:
public synchronized void save(){}
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。如果一个对象中多个方法使用synchronized。则必须等一个方法执行完后释放锁,下一个synchronized才能获得锁执行。
2.同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
3.使用局部变量实现线程同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以通过set,get方法随意修改自己的变量副本,而不会对其他线程产生影响。
注: 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock可以通过lock(),unlock()实现同步,但会大幅度降低程序运行效率,不推荐使用
2.5 线程数据传递
1、 通过构造方法传递数据
适用于简单参数传递。
2、 通过变量和方法传递数据
创建public的方法或变量,通过set方法来设置变量(传递复杂数据,集合、类等建议使用):
3、通过回调函数传递数据 (线程运行的过程中动态地获取数据)
2.6 线程间通信
①同步
这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信。
参考示例:
public
class MyObject {
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
public class ThreadA extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
//线程A与线程B 持有的是同一个对象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}
②while轮询的方式
假如有两个线程,ThreadA不断地改变条件,线程ThreadB不停地通过while语句检测这个条件是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。
③wait/notify机制
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
if (!condition) {
lock.wait();
}
}
}
}
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
if (condition) {
lock.notify();
}
}
}
}
线程A条件不满足时,调用wait() 放弃CPU,并进入阻塞状态。当线程B条件满足时调用 notify()通知 线程A,并让它进入可运行状态。
2.7 多线程的安全问题
出现安全问题的原因:多个线程执行共享数据的代码块时,其中的一个线程还没有执行完代码块,另一个线程就开始执行代码块,这会造成共享数据的错误。从而出现安全问题。
多线程安全问题的解决:同步。因为同步可以保证多线程代码只能被持有锁的线程运行,其它线程不能运行。任意时刻,一个锁只能被一个线程拥有。(持有锁的线程会在执行完多线程代码时释放锁,这样锁就能被其它线程拥有。)
2.8 线程并发
Java并发
Java1.5后增加了Executors框架用来处理线程并发:
Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future
Spring并发
ThreadPoolTaskExecutor(继承Executor)
两种初始化方式:
通过new创建一个对象,通过对象的set方法设置线程数量等,然后初始化。
通过配置文件配置相应的属性,定义变量进行注入。
<bean id="threadPoolTaskExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="http://www.mamicode.com/10" />
<property name="maxPoolSize" value="http://www.mamicode.com/15" />
<property name="queueCapacity" value="http://www.mamicode.com/1000" />
</bean>
利用线程池启动线程
使用线程池的好处:
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及”过度切换”。
Spring和java并发对比:
3 附录
Runable资源共享例子
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
输出:
从上面可以看出,不同的线程之间count是不同的,这对于卖票系统来说就会有很大的问题,当然,这里可以用同步来作。这里我们用Runnable来做下看看
输出:
这里要注意每个线程都是用同一个实例化对象,如果不是同一个,效果就和上面的一样了!
java多线程