您的位置:首页 > 编程语言 > Java开发

java--线程

2015-07-09 14:15 573 查看
多线程概述

            要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。 

        1、 进程

             是一个正在执行的程序。

             每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。 

        2、线程

             就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。

            一个进程中至少有一个线程。 

        3、多线程

            在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程
        运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像这种在一个进程中有多个线程执行的方式,就叫做多线程。 

      4、多线程存在的意义

         多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
 

例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。           下载就是多线程的。  系统在创建进程,线程也是系统创建的。

 

     5、计算机CPU的运行原理 

         我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。 

         而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定

线程状态:

新建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()

创建线程的第一种方式:继承Thread,由子类复写run方法。

    1.子类覆盖父类中的run方法,将线程运行的代码存放在run方法中。

    2.建立子类对象的同时线程也被创建。

    3.通过调用start方法开启线程。

class Demo extends Thread {  

    public void run() {  

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

            System.out.println(“demo run---+”i);  

         }  
   }  

}  

class ThreadDemo{  

    public static void main(String[] args){  

            Demo d = new Demo();//创建一个线程  

            d.start();//调用start方法执行该线程的run方法  

    }  

}  

 

创建线程的第二种方式:实现一个接口Runnable。

    1.子类覆盖接口中的run方法

    2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。

    3.Thread类对象调用start方法开启线程。

 class Test implement Runnable  

{  

    public void run(){  //子类覆盖接口中的run方法

        for(int x=0; x<60; x++){  

            System.out.println(“run..."+x);  

        }  

    }  

 }  

class ThreadTest {  

    public static void main(String[] args) {  

        Test te = new Test();实现Runnable接口的对象  

        Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数  

        Thread t2 = new Thread(te);  

        t1.start();//调用Thread类的start方法开启线程  

        t2.start();  

    }  

两种线程创建方式的区别:

    继承Thread类创建对象:

        1.Thread子类无法再从其它类继承(java语言单继承)。

        2.编写简单,run()方法的当前对象就是线程对象,可直接操作。

    使用Runnable接口创建线程:

        1.可以将CPU,代码和数据分开,形成清晰的模型。

        2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。

        3.有利于保持程序的设计风格一致。

    继承Thread类线程代码存放于Thread子类run方法中;

    实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。

 

为什么要将Runnable接口的子类对象传递给Thread的构造函数?

因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该
run方法所属的对象。 

 

sleep方法需要指定睡眠时间,单位是毫秒。

冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。

         运行状态:具有执行资格和执行权。

         临时状态(阻塞):有执行资格,但是没有执行权。

         冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

         消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常  

 

sleep方法需要指定睡眠时间,单位是毫秒。

冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。被创建:等待启动,调用start启动。

         运行状态:具有执行资格和执行权。

         临时状态(阻塞):有执行资格,但是没有执行权。

         冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

         消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常  

  t1.start();  

        t2.start();  

  

        for(int x=0; x<60; x++){  

            System.out.println("main....."+x);  

        }  

    }  

}   
 线程的安全

    导致安全问题的出现的原因:

        1.多个线程访问出现延迟

        2.线程随机性
 

        线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
 

    解决方法:同步(synchronized)

        格式:

        synchronized(对象){

            需要同步的代码;

        }

同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
 

  4个窗口售票示例:

class Ticket implements Runnable {  
 

    private  int tick = 1000;  

    Object obj = new Object();//建立一个obj类对象,供synchronized作参数  

    public void run(){  

        while(true){  

            synchronized(obj) {  //同步锁

                if(tick>0)  

                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);  

                }  

            }  

      }  

 }  

  class  TicketDemo{  

    public static void main(String[] args) {  

  

        Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用  

  

        Thread t1 = new Thread(t);  

        Thread t2 = new Thread(t);  

        Thread t3 = new Thread(t);  

        Thread t4 = new Thread(t);  

  //开始4个线程  

        t1.start();  

        t2.start();  

        t3.start();  

        t4.start();  

     }  

}  

 

同步的前提:

             |---->同步需要两个或者两个以上的线程

             |---->多个线程使用的是同一个锁

    未满足这两个条件,不能称其同步。
 

同步的弊端:
         当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。

同步函数

    格式:在函数上加上synchronized修饰符即可。

    同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。

    所以同步函数使用的锁是this。

关于死锁

    同步中嵌套同步,但是锁却不同,就会产生死锁

 

如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁

class Dead implements Runnable {  

    private booleanb = false;  

    Dead(booleanb){  

        this.b = b;  

    }  

    public void run(){  

        while(true){  

            if(b){  

                synchronized(Locks.locka){
                    //0线程,持有了A锁  

                    System.out.println(Thread.currentThread().getName()+"locka...+..if");  

                    //等待B锁  

                    synchronized(Locks.lockb){  

                        System.out.println(Thread.currentThread().getName()+"lockb...+..if");  

                    }  

                }  

            }  

            else{  

                synchronized(Locks.lockb) {  

                    //1线程就进来了,持有了B锁  

                    System.out.println(Thread.currentThread().getName()+"lockb...+..else");  

                    synchronized(Locks.locka) {  //等待获得A锁

System.out.println(Thread.currentThread().getName()+"locka...+..else");  

                    }  

                }  

            }  

        }  

    }  

}  

 

class Locks {  //创造锁  

    public static Object locka = new Object();  

    public static Object lockb = new Object();  

}  

class DeadLock {  

    public static void main(String[]args){  

        Dead d1 = new Dead(true);  

        Dead d2 = new Dead(false);  

        Thread t1 = new Thread(d1);  

        Thread t2 = new Thread(d2);  

        t1.start();  

        t2.start();  

    }  

}  

 线程间通信 

        其实就是多个线程在操作同一个资源,但是操作的动作不同。 

 使用同步操作同一资源的示例:

 

/* 

    有一个资源 

一个线程往里存东西,如果里边没有的话 

一个线程往里取东西,如果里面有得话 

*/  

  

//资源  

class Resource {  

    private String name;  

    private String sex;  

    private boolean flag=false;  

      

    public synchronized void setInput(String name,String sex){  

        if(flag){  

            try{wait();}catch(Exception e){}//如果有资源时,等待资源取出  

        }  

        this.name=name;  

        this.sex=sex;  

  flag=true;//表示有资源  

        notify();//唤醒等待  

    }  

  

    public synchronized void getOutput(){         

        if(!flag){  

            try{wait();}catch(Exception e){}//如果木有资源,等待存入资源  

        }  

        System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出  

                  

        flag=false;//资源已取出  

        notify();//唤醒等待  

    }  

}  

  

  

//存线程  

class Input implements Runnable {  

    private Resource r;  

    Input(Resource r){  

        this.r=r;  

  }  

    public void run() {  //复写run方法

        int x=0;  

        while(true)  

        {  

            if(x==0)//交替打印

            {  

                r.setInput("张三",".....man");  

            }  

            else  

            {  

                r.setInput("李四","..woman");  

            }  

            x=(x+1)%2;//控制交替打印  

        }  

    }  

}  

  

//取线程  

class Output implements Runnable {  

    private Resource r;  

    Output(Resource r){  

        this.r=r;  

  }  

    public void run(){  //复写run方法 

        while(true) {  

            r.getOutput();  

        }  

    }  

}  

  

  class ResourceDemo {  

    public static void main(String[] args)  {  

        Resource r = new Resource();//表示操作的是同一个资源  

  

        new Thread(new Input(r)).start();//开启存线程  

  

        new Thread(new Output(r)).start();//开启取线程  

    }  

}  

 

 问题:

        1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

                a,这些方法存在与同步中。

                b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

                c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

        2)wait(),sleep()有什么区别?

wait():释放cpu执行权,释放锁。

              sleep():释放cpu执行权,不释放锁。

        3)为甚么要定义notifyAll?

        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。 

  JDK1.5中提供了多线程升级解决方案。 

        将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。 

升级解决方案的示例:

 

/* 

生产者生产商品,供消费者使用 

有两个或者多个生产者,生产一次就等待消费一次 

有两个或者多个消费者,等待生产者生产一次就消费掉 

 

*/  

  

import java.util.concurrent.locks.*;  

class Resource {     

    private String name;  

    private int count=1;  

private boolean flag = false;  

      

    //多态  

    private Lock lock=new ReentrantLock();  

  

    //创建两Condition对象,分别来控制等待或唤醒本方和对方线程  

    Condition condition_pro=lock.newCondition();  

    Condition condition_con=lock.newCondition();  

  

    //p1、p2共享此方法  

    public void setProducer(String name)throws InterruptedException {  

        lock.lock();//锁  

        try{  

            while(flag)//重复判断标识,确认是否生产  

                condition_pro.await();//本方等待  

  

            this.name=name+"......"+count++;//生产  

            System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产  

            flag=true;//控制生产\消费标识  

            condition_con.signal();//唤醒对方  

        }  

        finally{  

            lock.unlock();//解锁,这个动作一定执行  

        }  

 }  

  

    //c1、c2共享此方法  

    public void getConsumer()throws InterruptedException {  

        lock.lock();  

        try {  

            while(!flag)//重复判断标识,确认是否可以消费  

                condition_con.await();  

  

            System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费  

            flag=false;//控制生产\消费标识  

            condition_pro.signal();  

        }  

        finally{  

            lock.unlock();  

        }  

  }  

}  

  

//生产者线程  

class Producer implements Runnable {  

    private Resource res;  

    Producer(Resource res){  

  this.res=res;  

    }  

    //复写run方法  

    public void run(){  

        while(true){  

            try{  

                res.setProducer("商品");  

            }  

            catch (InterruptedException e)  

            {  

            }  

        }  

    }  

}  

  

//消费者线程  

class Consumer implements Runnable{  

    private Resource res;  

    Consumer(Resource res)  {  

        this.res=res;  

    }  

    //复写run  

    public void run(){  

  while(true){  

            try{  

                res.getConsumer();  

            }  

            catch (InterruptedException e)  

            {  

            }  

        }  

    }  

}  

  

class  ProducerConsumer {  

    public static void main(String[] args) {  

        Resource res=new Resource();  

  

        new Thread(new Producer(res)).start();//第一个生产线程 p1  

        new Thread(new Consumer(res)).start();//第一个消费线程 c1  

  

        new Thread(new Producer(res)).start();//第二个生产线程 p2  

        new Thread(new Consumer(res)).start();//第二个消费线程 c2  

    }  

}  

停止线程 

1.定义循环结束标记(run方法结束)

    因为线程运行代码一般都是循环,只要控制了循环即可。 

2.使用interrupt(中断)方法。

    该方法是结束线程的冻结状态,使线程回到运行状态中来。 

注:stop方法已经过时不再使用。

 

特殊情况:

    当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。

    Thread类提供该方法 interrupt(); 

class StopThread implements Runnable{  

    private boolean flag =true;  

    public synchornized void run(){  

        while(flag){  

            try{  

                wait();//调用线程等待  

            }  

            catch(InterruptedException e) {  

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

                flag = false;//在异常中控制线程的运行  

            }  

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

        }  

    }  

    public void changeFlag() {  

        flag = false;  

    }  

}  

  

class  StopThreadDemo{  

    public static void main(String[] args) {  

        StopThread st = new StopThread();  

          

        Thread t1 = new Thread(st);  

        Thread t2 = new Thread(st);  

  

  

        t1.setDaemon(true);  

        t2.setDaemon(true);  

        t1.start();  

        t2.start();  

  int num = 0;  

  

        while(true) {  

            if(num++ == 60){  

                //st.changeFlag();  

                t1.interrupt();//调用interrupt方法来中断线程,抛出异常  

                t2.interrupt();  

                break;  

            }  

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

        }  

        System.out.println("over");  

    }  

}线程类的其他方法 

   守护线程

    setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。

    注意:该方法必须在启动线程前调用。 

          该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

  join()

  join方法 等待该线程终止。

当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。join可以用来临时加入线程执行。  如果B线程wait了主线程就挂了,B线程还能继续运行。

setPriority()

更改线程的优先级。三个优先级分别为:

    MAX_PRIORITY(最高优先级,10)

    MIN_PRIORITY(最低优先级,1)

    NORM_PRIORITY(默认优先级,5)

首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority 和该线程的线程组的最大允许优先级相比较小的一个。 

yeild()

暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行)   Thread中的toString方法返回该线程的字符串表现形式,包括线程名称、优先级和线程组。

A开启的该线程,该线程就属于A组。

wait和sleep区别:分析这两个方法:从执行权和锁上来分析:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

     Sleep:线程会释放执行权,但是不释放锁。  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: