-----------android培训、java培训、java学习型技术博客、期待与您交流!------------
Java基础之多线程
1.进程和线程的概念及两者之间的关系:
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。比如在Windows系统中,一个运行的xx.exe就是一个进程。
Java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享数据。
多进程:操作系统中同时运行的多个程序;
多线程:在同一个进程中同时运行的多个任务;
一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元,并发运行。如:多线程下载软件。
多个线程可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一致。因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的。可以理解成多个线程在抢cpu资源。
多线程下载:此时线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。多线程是为了同步完成多项任务,不是为了提供运行效率,通过提高资源使用效率来提高系统的效率.线程是在同一时间需要完成多项任务的时候实现的.
线程与进程的比较:
线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—WeightProcess)或进程元;而把传统的进程称为重型进程(Heavy—WeightProcess),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。
进程与线程的区别:
1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。
2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。
2.创建线程的两种方式
第一种,继承Thread类:
子类覆写父类中的run方法,将线程运行的代码存放在run中。
建立子类对象的同时线程也被创建。
通过调用start方法开启线程。
第二种,实现Runnable接口:
子类覆盖接口中的run方法。
通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
Thread类对象调用start方法开启线程。
第一种方式代码演示:
*********************************************************************
publicclassTest_Thread{//测试类
publicstaticvoidmain(String[]args){
//创造两个线程
MyThreadmyThread1=newMyThread("A");
MyThreadmyThread2=newMyThread("BB");
//调用start()方法开户线程(这个方法内部调用run()方法)
myThread1.start();
myThread2.start();
}
}
//线程类(继承Thread)
classMyThreadextendsThread{//继承Thread类
privateStringname;
publicMyThread(Stringname){
super();
this.name=name;
}
@Override
publicvoidrun(){//重写run()方法,将需要开户线程执行的代码放到这个方法里
for(inti=0;i<1000;i++){
System.out.println(name+"::"+i);
}
}
}
*********************************************************************
第二种方式演示
*********************************************************************
publicclassTest_Runnable{//测试类
publicstaticvoidmain(String[]args){
//实例化三个线程对象,调用start()方法开启线程
newThread(newMyRunnable("a")).start();
newThread(newMyRunnable("bb")).start();
newThread(newMyRunnable("ccc")).start();
}
}
//Runnable实现类,实现Runnable接口
classMyRunnableimplementsRunnable{
privateStringname;
publicMyRunnable(Stringname){
super();
this.name=name;
}
@Override
publicvoidrun(){//重写run()方法,将需要开户线程执行的代码放到这个方法里
for(inti=0;i<1000;i++){
System.out.println(name+"::"+i);
}
}
}
*********************************************************************
两种创建线程的方式对比
继承Thread:局限性比较多,A、JAVA是单继承的,类继承了Thread,就不能继承其它类,B、不能用多处线程执行同一个类里面的run()方法(因为线程的开启是自己的实例对象);
实现Runnable:恰好解决了继承Thread的方式中的局限性,所以通常都用这种方式来创建线程。
3.线程的状态
a.被创建:start();
b.运行:具备执行资格,同时具备执行权;
c.冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
d.临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
e.消亡:stop();
需要注意两个容易混淆的概念:
执行权:是相对cpu的,即cpu切换到这个线程执行,就具体了执行权,否则,就没有执行权,
执行资格:是相对自己的状态的,处于冻结及消亡两个状态下是没有资格的,其它状态是具体的执行的资格,只在cpu给到执行权就执行。
也就是说线程的执行需要同时具体执行的资格和执行权,也可以理解为具备执行权的线程一定具备执行资格,具体执行资格的线程不一定有执行权(这时线程就在等待)。发下图,线程各个状态之间的转换关系:
4.多线程环境下的安全问题
安全问题表现:一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据,导致到了错误数据的产生。
产生原因:共享数据,有多条语句对共享数据进行运算。这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。
线程安全问题典型例子演示(银行取款):
*********************************************************************
//银行类
publicclassBank{
privateIntegernum=10000;//银行里总可支出余额
//取款方法
publicvoidgetmoney(intmoney)throwsException{
while(true){
//当银行可支出余额大于0的时候才能取款
if(this.getNum()>0){
Thread.sleep(100);
num=num-money;
System.out.println(Thread
.currentThread()
.getName()+"取走了"+money);
System.out.println("银行帐户金额还有"+num);
}else{
return;//银行存款没有大于0的时候直接退出
}
}
}
publicIntegergetNum(){
returnnum;
}
publicvoidsetNum(Integernum){
this.num=num;
}
}
//Runnable实现类
publicclassMyRunnableimplementsRunnable{
privateBankbank;//银行类的引用
publicMyRunnable(Bankbank){//构造方法,需要传递一个银行类实例参数
super();
this.bank=bank;
}
@Override
publicvoidrun(){//重写Runnable的run()方法
intmoney=100;//每次取款额度
bank.getmoney(money);//调用银行类的取款方法
}
}
}
//测试类
publicclassTest{
publicstaticvoidmain(String[]args){
//实列化一个银行类
Bankbank=newBank();
//实列化一个线程类,将bank传进去
MyRunnablemyRunnable=newMyRunnable(bank);
//通过Thread的实例开启三个执行线程,并同时执行同一个Runnable实现类(myRunnable)里的run()方法
newThread(myRunnable).start();//开启第一个线程
newThread(myRunnable).start();//开启第二个线程
newThread(myRunnable).start();//开启第三个线程
}
}
执行错误结果(出现了余额为负数的安全问题,没在到设计预期:当余额不大于0的时候不能取款):
*********************************************************************
解决安全问题的方法就是将执行的任务进行同步,即:当一个线程在执行多条也要被其它线程执行的语句的时候,不允许其它线程参与进来执行,等这个线程执行完后才让其它线程接着执行。具体来说的两种方式:
A、同步函数:在需要被多个线程执行的方法前面加synchronized关键字修饰,如:
publicsynchronizedvoidgetmoney(intmoney){方法体……}
B、同步代码块:将需要被多个线程执行的代码用synchronized(对象){执行代码}包起来,达到同步的效果。
以上银行取款的例子用同步的方法重构后如下:
*********************************************************************
//将银行类中取款方法的方法体用同步代码块包起来
*********************************************************************
//将银行类中取款方法的方法体用同步代码块包起来
publicvoidgetmoney(intmoney)throwsException{
while(true){
synchronized(this){//同步代码块(要非常注意同步代码块要放到循环的内部,并要有退出的可能条件)
if(this.getNum()>0){
Thread.sleep(100);
num=num-money;
System.out.println(Thread
.currentThread()
.getName()+"取走了+money);
System.out
.println("银行帐户金额还有"+num);
}else{
return;//满足条件时退出本方法s
}
}
}
}
//其它类及测试不变,执行结果如下:
*********************************************************************
从上面的代码中可以看到,同部代码块的小括号中需要给一个对象,这个对象表示同步锁用的是谁,由于同步代码块中是开发人员指定,所在理论上可以给任何对象,但一般在非静态的前提下同步代码块的锁都用”this”,表示本类对象,如果同步代码块是静态的,这个锁可以用本类的Class对象(即xxx.Class),因为静态的内容是先于对象存在的,所在要给一个在静态内容加载前就有的对象。
同步函数也存在锁对象,这个对象无需要开发人员指定,对于非static修饰的方法,他的锁是”this”,static修饰的方法,他的锁是本类的Class对象(即xxx.Class)。
定义同步需要注意:1,必须要有两个或者两个以上的线程,才需要同步,2,多个线程必须保证使用的是同一个锁(这点很重要)。
5.同步环境下的死锁问题
问题的现象及原因:当同步中嵌套同步时,就有可能出现死锁现象。
例子:
*********************************************************************
//Runnable实现类
publicclassLookupTestimplementsRunnable{
privatebooleanflag;
privatestaticfinalObjectobject1=newObject();//锁1
privatestaticfinalObjectobject2=newObject();//锁2
publicLookupTest(booleanflag){
super();
this.flag=flag;
}
@Override
publicvoidrun(){
while(true){
if(flag){//锁1中有锁2
synchronized(object1){
System.out.println("if__object1");
synchronized(object2){
System.out.println("if__object2");
}
}
}else{//锁2中有锁1且与if中的锁是同一把
synchronized(object2){
System.out.println("else__object2");
synchronized(object1){
System.out.println("else__object1");
}
}
}
}
}
}
//测试类中代码
publicstaticvoidmain(String[]args){
//创建两个线程,一个线程执行IF里的代码,一个执行ELSE里的代码
Threadthread1=newThread(newLookupTest(true));
Threadthread2=newThread(newLookupTest(false));
//开启线程
thread1.start();
thread2.start();
}
*********************************************************************
要避免死锁的现象就要注意如果出理同步中含有同步的情况下锁要用对!
6.线程间的通讯
为解决多个线程在操作同一个资源,但是操作的动作却不一样的环境下,线程之间的协作。
要实现这个效果需要注意几个事项:
1. 线程间可以一种机制协作起来,在各自执行代码的时候对方需要配合执行方的动作不受干扰。
2. 将共同执行的资源封装成一个对象,共同操作这个资源的方法要同步起来;
3. 将线程执行的任务(run方法中)也封装成对象,且有多少个任务就要封装多少个对象。
典型例子:生产者与消费者:
*********************************************************************
//资源类
publicclassResource{
privateStringname;
privateintcount=1;
privatebooleanflag=false;//用来记录是否有产品,无则生产,有则不生产
//生产产品
publicsynchronizedvoidset(Stringname){
System.out.println("生产者进来了>>>");
if(flag){
try{
System.out.println("生产者等待中>>>");
this.wait();//如果有资源就等待,暂不生产
}catch(InterruptedExceptione){
}
}
this.name=name+count;
count++;
System.out.println(Thread.currentThread().getName()+"生产了:"+this.name);
this.flag=true;
this.notifyAll();
}
//消费产品
publicsynchronizedvoidout(){
System.out.println("消费者进来了>>>");
if(!flag){
try{
System.out.println("消费者等待中>>>");
this.wait();//如果有资源就等待,暂不生产
}catch(InterruptedExceptione){
}
}
System.out.println(Thread.currentThread().getName()
+"消费了:"+this.name);
this.flag=false;
this.notifyAll();
}
}
//生产者类
publicclassProducerimplementsRunnable{
privateResourceresource;
publicProducer(Resourceresource){
super();
this.resource=resource;
}
@Override
publicvoidrun(){
while(true){
resource.set("苹果");
}
}
}
//消费者类
publicclassConsumerimplementsRunnable{
privateResourceresource;
publicConsumer(Resourceresource){
super();
this.resource=resource;
}
@Override
publicvoidrun(){
while(true){
resource.out();
}
}
}
//测试类
publicclassTest{
publicstaticvoidmain(String[]args){
//实例化资源对象
Resourceresource=newResource();
//实例化生产者
Producerproducer1=newProducer(resource);
//实例化消费者
Consumerconsumer1=newConsumer(resource);
//开启线程
newThread(producer1).start();
newThread(consumer1).start();
}
}
执行结果(生产者与消费者交替执行):
……
Thread-0生产了:苹果18830
生产者进来了>>>
生产者等待中>>>
Thread-1消费了:苹果18830
消费者进来了>>>
消费者等待中>>>
Thread-0生产了:苹果18831
Thread-1消费了:苹果18831
……
*********************************************************************
小结:线程间的通讯用到的方法(等待唤醒机制):
Wait():将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。需要注意wait和sleep的区别:从执行权和锁上来分析:wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。wait:线程会释放执行权,而且线程会释放锁。Sleep:线程会释放执行权,但不是不释放锁。
Notify():唤醒线程池中某一个等待线程。
notifyAll():唤醒的是线程池中的所有线程。
7.JDK1.5以后的同步锁
直接将锁封装成了对象:线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。这个锁对象就是Lock接口(java.util.concurrent)
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。下面就是这个接口的主要方法:
方法摘要 | |
| lock |
| unlock |
使用格式:
Lock l = new ;
l.lock();
try {
同步执行的代码….
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁定。
在原来的模式下的Object 监视器方法(wait、notify 和 notifyAll)被Condition替换掉,并提供了功能一致的方法await()、signal()、signalAll()。
用新模式对生者消费者案例重构:
*********************************************************************
//资源类
public class Resource {
private String name;
private int count = 1;
private boolean flag = false;// 用来记录是否有产品,无则生产,有则不生产
final Lock lock = new ReentrantLock();
final Condition notset = lock.newCondition();
final Condition notout = lock.newCondition();
// 生产产品
public void set(String name) {
lock.lock();// 锁上
try {
System.out.println("生产者进来了>>>");
if (flag) {
try {
System.out.println("生产者等待>>>");
notset.await();// 等待
} catch (InterruptedException e) {
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread()
.getName() + "生产了:"+ this.name);
this.flag = true;
notout.signalAll();// 唤醒
} finally {
lock.unlock();// 释放锁
}
}
// 消费产品
public void out() {
lock.lock();
try {
System.out.println("消费者进来了>>>");
if (!flag) {
try {
System.out.println("消费者等待中>>>");
// 如果有资源就等待,暂不生产
notout.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread()
.getName() + "消费了:"
+ this.name);
this.flag = false;
// this.notifyAll();
notset.signalAll();
} finally {
lock.unlock();
}
}
}
其它类不变,执行效果与原代码一样
*********************************************************************