黑马程序员_Java_多线程

来源:互联网 发布:知乎 宋仲基宋慧乔 编辑:程序博客网 时间:2024/05/18 02:43

线程的理解

 1、同一个应用中,多个任务同时进行。就像editplus编辑工具,打开一个文件窗口就是一个线程。

 2、线程可以有多个,但cpu每时每刻只做一件事(多核除外)。由于cpu处理速度很快,我们就感觉是同时进行的。所以宏观上,线程是并发进行的;从微观角度看,线程是异步执行的。

 3、使用线程的目的是最大限度的利用cpu资源。想想当你在editplus中按下"ctrl + shift +S"保存全部文件的时候,如果要保存的文件比较多,没有多线程的话,前面的文件没有完成操作的话,后面的操作是执行不了的~!

创建线程

实现多线程,有两种手段:

一:继承Thread类(启动线程方法:new MyThread().start();)

步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法,
该方法两个作用:启动线程,调用run方法。

线程中,start和run的区别
start():是开启线程并调用run方法
run():存放的是线程执行的代码,如果单纯的调用run方法,只是普通的创建对象调用方法,而并没有开启线程

class 类名 extends Thread{    @Override    public void run() {     //code    }}
class Show extends Thread {public void run(){for (int i =0;i<5 ;i++ ){System.out.println(name +"_" + i);}}public Show(){}public Show(String name){this.name = name;}private String name;public static void main(String[] args) {new Show("csdn").run();new Show("黑马").run();}}

【运行结果】:



发现这些都是顺序执行的,说明调用run()方法不对,应该调用的是start()方法。

把上面的主函数修改为如下:

        public static void main(String[] args) {new Show("csdn").start();new Show("黑马").start();}

在命令行执行:javac Show.java  java Show,输出的可能的结果如下:


因为需要用到CPU的资源,多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。所以每次的运行结果基本是都不一样的;

这也是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说了算。

那么:为什么我们不能直接调用run()方法呢?

我的理解是:线程的运行需要本地操作系统的支持。

如果你查看start的源代码的时候,会发现:

 public synchronized void start() {        /**         * This method is not invoked for the main method thread or "system"         * group threads created/set up by the VM. Any new functionality added         * to this method in the future may have to also be added to the VM.         *         * A zero status value corresponds to state "NEW".         */        if (threadStatus != 0)            throw new IllegalThreadStateException();        /* Notify the group that this thread is about to be started         * so that it can be added to the group's list of threads         * and the group's unstarted count can be decremented. */        group.add(this);        boolean started = false;        try {            start0();            started = true;        } finally {            try {                if (!started) {                    group.threadStartFailed(this);                }            } catch (Throwable ignore) {                /* do nothing. If start0 threw a Throwable then                  it will be passed up the call stack */            }        }    } private native void start0();
在start0()方法中jvm调用run()方法,这个这个方法用了native关键字,native表示调用本地操作系统的函数,多线程的实现需要本地操作系统的支持。

二.实现Runable接口,(启动线程方法:new Thread(new MyRunnable()).start();)

class 类名 implements Runnable {    @Override    public void run() {        //code    }}
class Show implements Runnable {public Show(){}public Show(String name){this.name = name;}@Overridepublic void run(){for (int i =0;i<5 ;i++ ){System.out.println(name +"_" + i);}}private String name;public static void main(String[] args) {new Thread(new Show("csdn")).start();new Thread(new Show("黑马")).start();}}
【可能的运行结果】:

关于选择继承Thread还是实现Runnable接口?

其实Thread也是实现Runnable接口的

public class Thread implements Runnable {    /* Make sure registerNatives is the first thing <clinit> does. */    private static native void registerNatives();    static {        registerNatives();    }     @Override    public void run() {        if (target != null) {            target.run();        }    }}
其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,ThreadRunnable都实现了run方法,这种操作模式其实就是代理模式。

Thread和Runnable的区别:

如果一个类继承Thread,则不能资源共享(有可能是操作的实体不是唯一的);但是如果实现了Runable接口的话,则可以实现资源共享。

class Show extends Thread{@Overridepublic void run(){for (int i = 0; i < 5 ; i++ ){if (count > 0){System.out.println(Thread.currentThread().getName()+": count=" + count--);}}}private int count = 5;public static void main(String[] args) {new Show().start();//new 出来多个实体new Show().start();}}
【运行结果】:

public static void main(String[] args) {Show s = new Show();s.start();s.start();}


我们再以实现Runnable接口的方式修改上面的程序:

class Show implements Runnable{private int count = 10;//假设有10张票@Overridepublic void run(){for (int i = 0; i < 5 ; i++ ){if (this.count > 0){System.out.println(Thread.currentThread().getName()+"正在卖票" + this.count--);}}}public static void main(String[] args) {Show s = new Show(); //注意必须保证只对1个实体s操作new Thread(s,"窗口1").start();new Thread(s,"窗口2").start();new Thread(s,"窗口3").start();}}

【运行结果】:


总结:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

所以,还是以实现接口的方式来创建好些。

java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源;每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实际上就是在操作系统中启动了一个进程。

设置线程优先级主线程的优先级是5,不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源

<span style="font-size:14px;">Thread t = new Thread(myRunnable);t.setPriority(Thread.MAX_PRIORITY);//一共10个等级,Thread.MAX_PRIORITY表示最高级10t.start();</span>
判断线程是否启动
class Show implements Runnable{public void run(){for (int i = 0; i < 5 ; i++ ){System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {Show s = new Show();Thread t = new Thread(s);System.out.println("线程启动前:" + t.isAlive());t.start();System.out.println("线程启动后:" + t.isAlive());}}

【运行结果】:



主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。

join,sleep,yield的用法与区别

join方法:假如你在A线程中调用了B线程的join方法B.join();,这时B线程继续运行,A线程停止(进入阻塞状态)。等B运行完毕A再继续运行。

sleep方法:线程中调用sleep方法后,本线程停止(进入阻塞状态),运行权交给其他线程。

yield方法:线程中调用yield方法后本线程并不停止,运行权由本线程和优先级不低于本线程的线程来抢。(不一定优先级高的能先抢到,只是优先级高的抢到的时间长)


后台线程

<span style="font-size:14px;">Thread t = new Thread(new Show());t.setDaemon(true);t.start();</span>
java程序中,只要前台有一个线程在运行,整个java程序进程不会消失,所以此时可以设置一个后台线程,这样即使java进程消失了,此后台线程依然能够继续运行。
结束线程(修改标示符flag为false来终止线程的运行)

<span style="font-size:14px;">class Show implements Runnable{private boolean flag = true;public void run(){while(flag){System.out.println(Thread.currentThread().getName()+" is living");}}public void shutDown(){this.flag = false;}public static void main(String[] args) {Show s = new Show();Thread t = new Thread(s);t.start();try{Thread.sleep(2);}catch (Exception e){e.printStackTrace();}s.shutDown();}}</span><span style="color: rgb(128, 128, 128); font-size: 15px;"></span>


线程同步synchronized

  synchronized可以修饰方法,或者方法内部的代码块。被synchronized修饰的代码块表示:一个线程在操作该资源时,不允许其他线程操作该资源。

【问题引出】:比如说对于卖票系统,有下面的代码:

class Show implements Runnable{private int count =5;//5张票要卖public void run(){for (int i = 0; i < 10 ; i++ ){if (count > 0){try{Thread.sleep(200);}catch (Exception e){e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+","+count--);}}public static void main(String[] args) {Show s = new Show();new Thread(s,"A").start();new Thread(s,"B").start();new Thread(s,"C").start();}}
【运行结果】:


这里出现了负数,显然这个是错的。,应该票数不能为负值。

如果想解决这种问题,就需要使用同步

所谓同步就是在统一时间段中只有有一个线程运行,其他的线程必须等到这个线程结束之后才能继续执行。


【使用线程同步解决问题】

采用同步的话,可以使用同步代码块和同步方法两种来完成。

同步代码块

语法格式:

synchronized(同步对象){

 //需要同步的代码

}

但是一般都把当前对象this作为同步对象。

class Show implements Runnable{private int count =5;public void run(){for (int i = 0; i < 20 ; i++ ){synchronized(this){if (count > 0){try{//Thread.currentThread().yield();Thread.sleep(300);}catch (Exception e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+","+count--);}}}}public static void main(String[] args) {Show s = new Show();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t3.start();t1.start();t2.start();}}

【运行结果】:


也可以采用同步方法

语法格式为synchronized 方法返回类型方法名(参数列表){

    // 其他代码

}

class Show implements Runnable{private int count =5;public void run(){for (int i = 0; i < 20 ; i++ ){sale();}}public synchronized void sale(){if (count > 0){try{//Thread.currentThread().yield();Thread.sleep(300);}catch (Exception e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+","+count--);}}public static void main(String[] args) {Show s = new Show();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t3.start();t1.start();t2.start();}}
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。
弊端:多个线程都需要判断锁,较为消耗资源,

wait、notify、notifyAll的用法

    wait方法:当前线程转入阻塞状态,让出cpu的控制权,解除锁定。

    notify方法:唤醒因为wait()进入阻塞状态的其中一个线程。

  notifyAll方法: 唤醒因为wait()进入阻塞状态的所有线程。

 这三个方法都必须用synchronized块来包装,而且必须是同一把锁,不然会抛出java.lang.IllegalMonitorStateException异常。


当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁同步中嵌套同步容易引发死锁

此处列举经典的【生产者和消费者问题】:

class Info {     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    }     private String name = "lfz";    private int age = 20;}/** * 生产者 */class Producer implements Runnable{    private Info info=null;    Producer(Info info){        this.info=info;    }         public void run(){        boolean flag=false;        for(int i=0;i<10;++i){            while (true)            {               if(flag){                this.info.setName("adanac");                this.info.setAge(20);                flag=false;                }else{                    this.info.setName("jean");                    this.info.setAge(30);                    flag=true;               }           }        }    }}/** * 消费者类 * */class Consumer implements Runnable{    private Info info=null;    public Consumer(Info info){        this.info=info;    }         public void run(){        for(int i=0;i<10;++i){           System.out.println(this.info.getName()+"<---->"+this.info.getAge());        }    }}class Show {    public static void main(String[] args)     {        Info info=new Info();        Producer pro=new Producer(info);        Consumer con=new Consumer(info);        new Thread(pro).start();        new Thread(con).start();    }}


从结果中看到,名字和年龄并没有对应。

那么如何解决呢?

1)加入同步

2)加入等待和唤醒

先来看看加入同步会是如何:

class Info {     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    } public synchronized void set(String name, int age){        this.name=name;        this.age=age;    }         public synchronized void get(){        System.out.println(this.getName()+"<===>"+this.getAge());}     private String name = "lfz";    private int age = 20;}/** * 生产者 */class Producer implements Runnable{    private Info info=null;    Producer(Info info){        this.info=info;    }         public void run(){        boolean flag=false;        for(int i=0;i<10;++i){while (true){if(flag){this.info.set("adanac",10);flag=false;}else{this.info.set("lfz",20);flag=true;}}        }    }}/** * 消费者类 * */class Consumer implements Runnable{    private Info info=null;    public Consumer(Info info){        this.info=info;    }         public void run(){        for(int i=0;i<10;++i){            try{                Thread.sleep(100);            }catch (Exception e) {                e.printStackTrace();            }            this.info.get();        }    }}class Show {public static void main(String[] args) {Info info=new Info();        Producer pro=new Producer(info);        Consumer con=new Consumer(info);        new Thread(pro).start();        new Thread(con).start();}}


运行结果来看,错乱的问题解决了,现在是adanac对应20,jean对于30。

/*
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
*/

但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。

如果想解决这个问题,就需要使用Object类帮忙了,我们可以使用其中的等待和唤醒操作。

要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:
class Info {     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    } public synchronized void set(String name, int age){        if (!flag)        {try{super.wait();}catch (Exception e){e.printStackTrace();}        }this.name=name;this.age=age;flag = false;super.notify();    }         public synchronized void get(){       if (flag)       {   try   { super.wait();   }   catch (Exception e)   {   e.printStackTrace();   }       }System.out.println(this.getName()+"<===>"+this.getAge());flag = true;super.notify();}     private String name = "xiaoli";    private int age = 22;private boolean flag = false;}/** * 生产者 */class Producer implements Runnable{    private Info info=null;    Producer(Info info){        this.info=info;    }         public void run(){        boolean flag=false;        for(int i=0;i<10;++i){while (true){if(flag){this.info.set("adanac",10);flag=false;}else{this.info.set("lfz",20);flag=true;}}        }    }}/** * 消费者类 * */class Consumer implements Runnable{    private Info info=null;    public Consumer(Info info){        this.info=info;    }         public void run(){        for(int i=0;i<10;++i){            this.info.get();        }    }}class Show {public static void main(String[] args) {Info info=new Info();        Producer pro=new Producer(info);        Consumer con=new Consumer(info);        new Thread(pro).start();        new Thread(con).start();}}

【运行结果】:



/*
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。

Lock:替代了Synchronized
lock 
unlock
newCondition()

Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
*/
0 0
原创粉丝点击