java多线程
java多线程
wz_osChina 发表于7个月前
java多线程
  • 发表于 7个月前
  • 阅读 6
  • 收藏 0
  • 点赞 0
  • 评论 0

新睿云服务器60天免费使用,快来体验!>>>   

线程的生命周期:

       1)创建:新建一个线程对象,如Thread thd=new Thread()。

       2)就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服

务,具备了运行的条件,但并不一定已经开始运行了)。

       3)运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑。

       4)阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了CPU资源,暂停了自己的执行,便进入

了阻塞状态,如调用了sleep()方法。

      5)终止:线程的run()方法执行完毕,或者线程调用了stop()方法,线程便进入终止状态

 

这里我们可以用一个经典的线问题就是生产者和消费者问题的实例:

import java.util.*;  

  

public class ProducerConsumer{  

 public static void main(String[] args){  

       SyncStack ss = new SyncStack();  

      Producer p = new Producer(ss);  

        Consumer c = new Consumer(ss);  

          

       new Thread(p).start();  

        new Thread(c).start();  

   }  

}  

 

  

//生产与消费对象  

class WoTou{  

    int id;   

      

    //构造方法  

    WoTou(int id){  

        this.id = id;  

   }  

      

   //重写toString()方法  

    public String toString(){  

       return "WoTou : " + id;  

    }  

}  

  

  

//容器类  

class SyncStack{  

    int index = 0;  

    WoTou[] arrWT = new WoTou[4];  

      

    public synchronized void push(WoTou wt){  

        while(index == arrWT.length){  

            try{  

                //这里的wait()方法指的是Object类中的方法  

                this.wait();  

            }catch(InterruptedException e){  

                e.printStackTrace();  

            }  

        }  

        //这里的notifyAll()方法指的是唤醒所有线程,而notify()方法唤醒一个线程  

        this.notifyAll();         

        arrWT[index] = wt;  

        index ++;  

    }  

      

    public synchronized WoTou pop(){  

        while(index == 0){  

            try{  

                //这里的wait()方法指的是Object类中的方法  

                this.wait();  

            }catch(InterruptedException e){  

                e.printStackTrace();  

            }  

        }  

        //这里的notifyAll()方法指的是唤醒所有线程,而notify()方法唤醒一个线程  

        this.notifyAll();  

        index--;  

        return arrWT[index];  

    }  

}  

  

  

//生产者  

class Producer implements Runnable{  

    SyncStack ss = null;  

      

    Producer(SyncStack ss){  

        this.ss = ss;  

    }  

      

    public void run(){  

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

            WoTou wt = new WoTou(i);  

           ss.push(wt);  

            System.out.println("生产了:" + wt);  

            try{  

                Thread.sleep((int)(Math.random() * 200));  

            }catch(InterruptedException e){  

                e.printStackTrace();  

            }             

        }  

    }  

}  

  

  

//消费者  

class Consumer implements Runnable{  

    SyncStack ss = null;  

      

    Consumer(SyncStack ss){  

        this.ss = ss;  

    }  

      

    public void run(){  

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

            WoTou wt = ss.pop();  

            System.out.println("消费了: " + wt);  

            try{  

                Thread.sleep((int)(Math.random() * 1000));  

            }catch(InterruptedException e){  

                e.printStackTrace();  

            }             

        }  

    }  

 

关于一些问题的解析:

      执行线程sleep()方法是依然占着cpu的,操作系统认为该当前线程正在运行,不会让出系统资源。

      执行wait()方法是让线程到等待池等待,让出一系列的系统资源,其他线程可以根据调度占用cpu线程的资源有不

少,但应该包含CPU资源和锁资源这两类。

      sleep(long mills):让出CPU资源,但是不会释放锁资源。

      wait():让出CPU资源和锁资源。

      锁是用来线程同步的,sleep(long mills)虽然让出了CPU,但是不会让出锁,其他线程可以利用CPU时间片了,但

如果其他线程要获取sleep(long mills)拥有的锁才能执行,则会因为无法获取锁而不能执行,继续等待。但是那些没有

和sleep(long mills)竞争锁的线程,一旦得到CPU时间片即可运行了。

 

 (2)守护线程代码示例:

import java.io.*;  

import java.util.*;  

  

class DaemonThread implements Runnable{  

    public void run(){  

        System.out.println("进入守护线程"+Thread.currentThread().getName());  

        try{              writeToFile();  

        }catch(Exception e){  

            e.printStackTrace();  

        }  

        System.out.println("退出守护线程");  

    }  

      

    private void writeToFile() throws Exception{  

        File filename = new File("E:\\Java\\JavaSE\\Thread"+File.separator+"daemon.txt");   

        OutputStream os = new FileOutputStream(filename,true);  

        int count = 0;  

        while(count < 999){  

            os.write(("\r\nword"+count).getBytes());  

            System.out.println("守护线程"+Thread.currentThread().getName()+"向文件中写入了word"+ count++);  

            Thread.sleep(1000);  

        }  

    }  

}  

  

public class DaemonThreadDemo{  

    public static void main(String[] args){  

        System.out.println("进入主线程"+Thread.currentThread().getName());  

          

        DaemonThread daemonThread = new DaemonThread();  

        Thread thread =new Thread(daemonThread);  

        thread.setDaemon(true);  

        thread.start();  

          

        Scanner sc = new Scanner(System.in);  

        sc.next();  

          

        System.out.println("退出主线程"+Thread.currentThread().getName());  

    }  

}  

 

       运行结果:

一线程创建的两种方式比较

      线程创建和启动有两种方式,这里只是列出步骤,不再进行详细解释。

      (1)继承Thread类

class MyThread extends Thread{  

     public void run(){  

         ...  

     }  

}  

  

MyThread mt=new MyThread();//创建线程  

mt.start();//启动线程  

 

      (2)实现Runnable接口

class MyThread implements Runnable{  

     public void run(){  

        ...  

     }  

}  

  

MyThread mt=new MyThread();  

Thread td=new Thread(mt);//创建线程  

td.start();//启动线程  

 

      (3)两种方式的比较

      1)Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。

      2)Runnable方式的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况。

 

 (1)使用Thread方式模拟买票

   

class MyThread extends Thread{  

    //一共有五张火车票  

   private int ticketsCount = 5;  

    //窗口,也就是线程的名字  

    private String name;  

  

    //构造方法  

    public MyThread(String name){  

       this.name = name;  

    }  

  

    public void run(){  

        while(ticketsCount > 0){  

            //如果还有票,就卖掉一张  

            ticketsCount--;  

            System.out.println(name+"卖了1张票,剩余票数为:"+ticketsCount);  

        }  

    }  

}  

  

public class TicketsThread{  

    public static void main(String[] args){  

        //创建三个线程,模拟三个窗口买票  

        MyThread mt1 = new MyThread("窗口1");  

        MyThread mt2 = new MyThread("窗口2");  

        MyThread mt3 = new MyThread("窗口3");  

  

        //启动三个线程,也就是窗口开始卖票  

        mt1.start();  

        mt2.start();  

        mt3.start();  

    }  

}  

 

      运行结果:

      得到的结果并不是我们想要的结果。

      在票的数量加static修饰关键字得到的结果是正确的。这里不再进行演示。

      (2)使用Runnable方式模拟买票

      TicketsRunnable.java源文件代码:

class MyThread1 implements Runnable{  

    //一共有五张火车票  

    private int ticketsCount = 5;  

      

    public void run(){  

        while(ticketsCount > 0){  

            //如果还有票,就卖掉一张  

            ticketsCount--;  

            System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数为:"+ticketsCount);  

        }  

    }  

}  

  

public class TicketsRunnable{  

    public static void main(String[] args){  

        MyThread1 mt = new MyThread1();  

        //创建三个线程,模拟三个窗口买票  

        Thread th1 = new Thread(mt,"窗口1");  

        Thread th2 = new Thread(mt,"窗口2");  

        Thread th3 = new Thread(mt,"窗口3");  

          

        //启动三个线程,也就是窗口开始卖票  

        th1.start();  

        th2.start();  

        th3.start();  

    }  

}  

 

      得到了预期的结果:

       (3)结果分析

       由于线程的执行是随机的,打印的结果也是随机的。

       Thread方式

       三个线程,创建了三个Thread对象,每个线程都有自己的Thread对象,都有自己的ticketsCount变量,它们三个

线程并不是共享ticketsCount变量,也就是每个线程都可以卖出5张火车票,即三个窗口卖出去15张火车票。

       Runnable方式

       三个线程共用一个Runnable对象,也就是三个线程共用一个ticketsCount变量,即三个窗口一共卖了5张火车票。

       前者不是一个线程三个对象,是三个Thread对象,也是三个线程,这三个线程启动后都会执行5次卖票,实现不

了共享“5张票”这个资源,所以输出就会有15张票卖出去,显然不符合实际,用Runnable就可以解决这个问题,创建

的三个线程可以共享"5张票"这个资源。

      ticketsCont变量是实例变量,它的值自然是存在堆中(每个java对象在堆中都会占据一定内存,而实例变量的值就

是存储在这块内存中,类似于结构体,因此每个对象对应一个ticketsCont的值),ticketsCont跟值传递没有关系啊,如

果是Runnable方式的话,传递的也只是MyThread对象引用的副本,不管ticketsCont的事,但是因为ticketsCont的值

在引用和引用副本所指向的堆内存中,所以无论是引用还是引用副本改变了堆内存中ticketsCont的值,都会产生效

果!

       这个根据你的需要来操作,这样说吧,如果有一个比较大的资源要你下载,那么你用Thread方式那么你就只能一

个线程去吧这个资源下载完,如果是runable方式的话你就可以 多new几个子线程来出来,通过共享runable对象里面

的资源来用多个子线程来下载这个资源,这样的话,下载资源的时候 runable方法会使下载的线程多一些几率在cpu里

面,也会让你下载速度变快继承Thread类是多个线程分别完成自己的任务,实现Runnable接口是多个线程共同完成

一个任务。

  • 打赏
  • 点赞
  • 收藏
  • 分享
共有 人打赏支持
粉丝 5
博文 12
码字总数 12119
×
wz_osChina
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: