线程

多线程概述

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

        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启动。

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

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

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

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

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


线程的对象获取与名称的操作
 
    线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。
操作方法:
    static Thread currentThread():获取当前线程对象。
    getName(): 获取线程名称。
    setName或者构造函数:设置线程名称。
示例代码:
 
class Test extends Thread  {  
    Test(String name){  //构造方法设置线程名称
        super(name);//继承父类方法  
    }  
    public void run(){  //执行代码
        for(int x=0; x<60; x++){  
            System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);  
        }  
    }  
}  
 class ThreadTest{  
    public static void main(String[] args) {  
        Test t1 = new Test("one---");//调用构造函数设置线程名称  
        Test t2 = new Test("two+++");  
        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组。

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

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

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

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

     Sleep:线程会释放执行权,但是不释放锁。