黑马程序员——java中的多线程编程

 

 

 

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

1.        什么是线程:

要理解线程,首先必须明白什么是进程。

进程是指内存中正在运行的程序。而每一个进程的执行都有一定的执行顺序,该顺序是一个执行路径,或者叫一个控制单元。这个控制单元就叫做线程。

线程是进程中的一个执行流程,一个进程中可以运行多个线程,多个线程同时执行。线程总是属于某个进程,进程中至少有一个线程,进程中的多个线程共享进程的内存。

注意:对于单核操作系统来说并没有真正意义上的多线程同时执行,而是cpu在不同线程之间进行着快速地切换;多核操作系统才能实现真正意义上的多线程。

2.        Java中的多线程:

每个Java程序都有一个默认的主线程。Java程序总是从主类的main方法开始执行。当JVM加载代码,发现main方法后就启动一个线程,这个线程就称作"主线程",该线程负责执行main方法。在main方法中创建的线程就是其他线程。

 如果main方法中没有创建其他线程,那么当main方法运行至结尾时JVM就会结束Java应用程序。但如果main方法中创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,保证每个线程都有机会使用CPU资源。正常情况下,这时即使main方法返回(主线程结束)JVM也不会结束,而是要一直等到该程序所有线程全部结束才结束Java程序。

3.        Java中多线程的创建和启动:

Java中创建多线程的方式有两种:

a)     创建一个继承自Thread类的子类:

继承Thread 类时,必须复写其中的run() 方法,把作为单独线程要执行的代码放在run() 中。创建一个该子类对象就是创建一个线程。

b)     创建一个类实现Runnable 接口:

Runnable接口中同样有一个run() 方法,所以当创建一个类实现Runnable接口时必须复写该方法,该方法中的代码就是作为单独线程要执行的代码。与Thread子类不同,Runnable 接口的子类创建的对象不是线程,这时要创建线程,必须把Runnable 接口的子类创建的对象作为参数传入Thread类或其子类的构造方法中,通过创建Thread类或其子类的对象来创建线程。

注意:无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。只有Thread及其子类创建的对象才是线程,Runnable 子类创建的对象不是线程;

线程的启动:线程的启动是通过Thread类或其子类对象调用start()方法,注意这里不是调用其中的run() 方法,直接调用run() 方法并不能开启线程;

下面通过创建两个类ThreadDemo1 ThreadDemo2来分别演示两种创建并开启多线程的方式。代码示例如下:

public class ThreadDemo {

 

         publicstatic void main(String[] args) {

                   //通过ThreadDemo1创建两个线程;

                   Threadt1=new ThreadDemo1("线程1"); 

                   Threadt2=new Thread("线程2"); 

                   //通过ThreadDemo2创建两个线程;

                   Threadt3=new Thread(new ThreadDemo2(),"线程3"); 

                   Threadt4=new Thread(new ThreadDemo2(),"线程4");

                   //开启四个线程;

                   t1.start();   

                   t2.start();

                   t3.start();

                   t4.start();

         }

}

//创建Thread类的子类,并复写其中的run()方法;

class ThreadDemo1 extends Thread{

         ThreadDemo1(Stringname){

                   super(name);

         }

         publicvoid run(){

                   for(int i=0;i<50;i++)

                            System.out.println(Thread.currentThread().getName()+":"+i);

         }

}

//创建实现Runnable接口的子类,并复写其中的run()方法;

class ThreadDemo2 implements Runnable{

         publicvoid run(){

                   for(int i=0;i<50;i++)

                            System.out.println(Thread.currentThread().getName()+":"+i);

         }

}

4.        线程的状态与生命周期:

线程在它的一个完整生命周期中通常会经历四种状态:创建、运行、冻结(中断/挂起/阻塞)、消亡。

a)     线程的创建:该过程之前已介绍过,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。只有Thread及其子类创建的对象才是线程。

b)     线程的运行:线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程。这时线程必须通过调用start()方法才能开启线程,开启后线程才会拥有执行的资格并等待享用CPU资源。

c)      线程的冻结:线程被冻结的原因有四种情况:

1)     JVMCPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权,并处于挂起状态。

2)     线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep(int millsecond)方法是Thread类中的一个类方法,线程执行该方法就立刻让出CPU使用权,进入挂起状态。经过参数millsecond指定的毫秒数之后,该线程就重新进到线程队列中排队等待CPU资源,然后从中断处继续运行。

3)     线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进入线程队列等待CPU资源,必须由其他线程调用notify()方法通知它,才能让该线程从新进入到线程队列中排队等待CPU资源,以便从中断处继续运行。

4)     线程使用CPU资源期间,执行某个操作进入阻塞状态,如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入线程队列,只有引起阻塞的原因消除时,线程才能进入到线程队列排队等待CPU资源,以便从中断处继续运行。

d)     线程的消亡:该状态是线程释放了实体,即释放了分配给线程对象的内存。

线程消亡的原因有两个:

1)     正常运行的线程完成了它的全部工作,即执行完run()方法中的全部语句,结束了run()方法。

2)     线程被提前强制性终止,即强制run()方法结束。

下面将对sleep(), wait(),notify() 方法的使用进行举例。代码在main线程中开启了一个名称为线程1的线程,线程1开启后使用sleep(3000)的方法休眠的3秒钟;同时main线程进入for循环,当i==10时,main线程使用wait()方法进入等待状态;线程1休眠结束后进入for循环,当循环结束后使用notify() 方法唤醒main线程,main线程继续剩下的循环。代码示例如下:

public class SleepDemo {

 

         publicstatic void main(String[] args) {

                   //创建并开启线程1

                   newThread(new ThreadDemo3(),"线程1").start();

                   synchronized(SleepDemo.class){   //同步代码块;

                            //main线程进入for循环;

                            for(int i=0;i<50;i++){   

                                     System.out.println("main线程:"+i);

                                     if(i==10){

                                               try{

                                                        System.out.println("main线程进入wait状态*****");

                                                        //main线程进入wait 状态;

                                                        SleepDemo.class.wait();

                                                        System.out.println("main线程被唤醒***********");

                                               }catch (InterruptedException e) {

                                                        //TODO Auto-generated catch block

                                               }

                                     }

                            }

                   }

         }

}

class ThreadDemo3 implements Runnable{

                   publicvoid run(){

                   try{

                            System.out.println(Thread.currentThread().getName()+"休眠3s");

                            Thread.sleep(3000);  //线程1 开始进入休眠状态;

                   }catch (InterruptedException e) {

                            //TODO Auto-generated catch block

                            e.printStackTrace();

                   }

                   synchronized(SleepDemo.class){

                            for(int i=0;i<50;i++)

                                     System.out.println(Thread.currentThread().getName()+":"+i);

                            SleepDemo.class.notify();  //线程1 main 线程唤醒;

                   }

         }

}

5.        Java多线程中的安全问题:

假设有一个线程在执行多条语句,且这些语句中运算同一个数据。若在执行过程中,有其他线程参与进来,并操作了这个数据,那么将会导致线程安全问题的产生。

由此可见线程安全问题的产生涉及到两个因素:

a)     线程中有多条语句对共享数据进行运算。

b)     存在别的线程也在操作该共享数据。

解决线程安全问题的方法就是采用代码同步。实现代码同步的方式有两种:

a)     同步代码块:具体的做法是在需要同步的程序代码前加上synchronized标记,并把代码放置在synchronized标记后的大括号中。

b)     同步函数:具体做法是在需要同步的函数语句的返回值类型前加上synchronized标记。

实现代码同步又叫对代码进行加锁,synchronized(this)语句中的this就是一个锁,它代表执行该段代码的对象。同步函数的锁也是对象,谁调用该函数,谁就是锁。

对代码进行加锁以实现同步的原理就是当程序执行到同步语句时,会先判断锁,这时有两种情况:

a)       如果这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中,本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

b)       假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。

注意:一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁,例如:在执行同步代码块时,遇到异常而导致线程终止,这时锁会被释放;或者在执行代码块时,执行了锁所属对象的wait()方法,这时这个线程会释放对象锁,并进入对象的等待池中。

下面将通过同步代码块演示多线程对一个共享变量进行操作,代码示例如下:

public class TicketDemo {

 

         publicstatic void main(String[] args) {

                   Tickett=new Ticket();

                   //创建并开启四个线程;

                   newThread(t,"线程1").start();

                   newThread(t,"线程2").start();

                   newThread(t,"线程3").start();

                   newThread(t,"线程4").start();

         }

}

class Ticket implements Runnable{

         privateint tic=1000;  //共享变量;

         publicvoid run() {

                   while(true){

                            //同步代码块;

                            synchronized(this){ 

                                     if(tic>0){

                                               System.out.println(Thread.currentThread().getName()+"票数剩余::"+(tic--));

                                     }

                            }

                   }

                  

         }

        

}

6.        死锁问题:

一旦有多个进程,且它们都要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。

最常见的死锁形式是当线程 1 持有对象 A 上的锁,而且正在等待对象B上的锁;而线程 2 持有对象 B 上的锁,却正在等待对象 A 上的锁。这两个线程永远都不会获得第二个锁,或是释放第一个锁,所以它们只会永远等待下去。

因此死锁出现的条件是:同步中嵌套同步,两个同步使用不同的锁;例如a锁嵌套b锁,且b锁嵌套a锁;

要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。例如,如果有四个资源ABC D,并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B C 的锁,而“线程 2”获取了 AC D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。

根据死锁出现的条件,死锁代码示例如下:

//创建一个类用于产生对象以代表锁;

class llock{

         publicstatic llock locka=new llock();

         publicstatic llock lockb=new llock();

}

 

public class DeadLockDemo {

         publicstatic void main(String[] args){

                   Threadt1=new Thread(new lock(true));

                   Threadt2=new Thread(new lock(false));

                   t1.start();

                   t2.start();

         }

}

 

class lock implements Runnable{

         booleanflag=true;

         lock(booleanf){

                   flag=f;

         }

         publicvoid run() {

                   if(flag){

                            while(true){

                                     //同步中嵌套同步,a锁中嵌套b锁;

                                     synchronized(llock.locka){

                                               System.out.println("a");

 

                                               synchronized(llock.lockb){

                                                        System.out.println("b");

                                               }

                                     }

                            }

                   }

                   else

                            show();

         }

         publicvoid show(){

                   while(true){

                            //同步中嵌套同步,b锁中嵌套a锁;

                            synchronized(llock.lockb){

                                     System.out.println("aa");

 

                                     synchronized(llock.locka){

                                               System.out.println("bb");

                                     }

                            }

                   }

         }

}

 ---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

详细请查看:<a href="http://edu.csdn.net" target="blank">http://edu.csdn.net</a>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值