网络多线程总结

来源:互联网 发布:php arg 编辑:程序博客网 时间:2024/06/04 19:44


我的理解: 

画图理解

线程在进程的内部,时间片,每次循环执行每一个程序的一个线程,如果开启了多个线程,那么时间片分配的多,从而提升了运行速度。
 线程与进程的区别:  1.进程独占空间,线程的空间是公用了它所属的进程空间。
                             2.线程的生命周期与它所属的进程是一样的。反之不成立
                             3.线程之间共享资源方便,进程之间共享资料麻烦。 (例如酷狗能边下载边播放,而酷狗不能播放迅雷边在下载的音乐文件)
多进程:在操作系统能同时运行多个任务
多线程:在同一应用程序中有多个顺序流同时执行


sleep 与 yield 
sleep和yield都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,但两者的区别在于:
     sleep给其它线程运行的机会,但不考虑其它线程的优先级;但yield只会让位给相同或更高优先级的线程;
     当线程执行了sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;
     sleep方法有可能抛出异常,而yield则没有;
     在一般情况下,我们更建议使用sleep方法。  


优先级较高的获取时间片的概率较高,概率值从1~10
线程状态:  join  isAlive interrupt

 sleep  yield  join  wait  
sleep和yield都会使线程放弃CPU,而yield只会让位给优先级高的线程,sleep不管优先级如何
执行了sleep后,转到阻塞状态,而yield则转到就绪状态
sleep可能有异常,而yield没有。



join 方法  
join是活雷锋,只有等另一个线程完成后自己的线程才开始执行。 看起来是顺序执行效果一样



只能在同步控制方法或同步控制块里调用wait(),notify()和notifyAll()。如果在非同步控制方法里调用这些方法,程序能通过编译,但运行的时候,将得到IllegalMonitorStateException异常,并伴随着一些含糊的消息,比如"当前线程不是拥有者"。消息的意思是,调用wait(),notify()和notifyAll()的线程在调用这些方法前必须"拥有"对象的锁。
  可以让另一个对象执行某种操作以维护其自己的锁。要这么做的话,必须首先得到对象的锁。比如,如果要在对象x上调用notify(),那么就必须在能够取得x的锁的同步控制块中这么做:
  synchronized(x){  x.notify();}





class A extends Thread{void run(){        sychrnoized(this){               xxx;            }}}class B extends Thread{   void run(){      sychrnoized(this){               xxx;            }   }}



如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。所以我们大部分都实现Runnable接口 

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

1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。


So,一直用Runnable,我记得第一次学习线程的时候是这个样子的new Thread(new Runnable(){  public void Run(){  /*TODO:*/ } }).start(); 瞬间觉得高大上,匿名内部类啊 


对了,还有些底层的东西

java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

 好了,来介绍各种方法的使用吧。 

1.join 我最喜欢活雷锋了。 

只有等另一个线程完成后自己的线程才开始执行。

public class Join {public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he,"线程");        demo.start();        for(int i=0;i<50;++i){            if(i>10){                try{                    demo.join();  //强制执行demo                }catch (Exception e) {                    e.printStackTrace();                }            }            System.out.println("main 线程执行-->"+i);        }    }static class hello implements Runnable {    public void run() {        for (int i = 0; i < 3; i++) {            System.out.println(Thread.currentThread().getName());        }    }}}

执行后的结果为: 

main 线程执行-->0
main 线程执行-->1
main 线程执行-->2
线程
main 线程执行-->3
线程
线程
main 线程执行-->4
main 线程执行-->5
main 线程执行-->6

.......

main 线程执行-->49



2.yield  这个家伙喜欢达尔文的哲学,谁强它就屈服    

 <pre name="code" class="java">public class H {public static void main(String[] args) {Runs runs1=new Runs();Runs runs2=new Runs();Thread t1=new Thread(runs1,"a");Thread t2=new Thread(runs2,"b");t1.setPriority(8);t1.start();t2.setPriority(5);t2.start();}static class Runs implements Runnable{public void run() {if("a".equals(Thread.currentThread().getName())){Thread.yield();   //yield只会将执行权给优先级高的线程}for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"--->"+i);}}}}


运行结果: 

a--->0
a--->1
b--->0
b--->1
a--->2
b--->2
a--->3
b--->3
b--->4
b--->5
b--->6
b--->7
b--->8
b--->9
a--->4
a--->5
a--->6
a--->7
a--->8
a--->9
很明显,b的运行级别高,如果将倆者运行级别对换,那么运行结果自然是a快些 ,这是相对而言,并不是每一次结果都是那样,只是多次运行后的结果显示。



3.Interrupt 中断线程 

这个不是很常用

public class Interrupt {static class hello implements Runnable {    public void run() {        System.out.println("执行run方法");        try {            Thread.sleep(10000);   /*睡十秒*/            System.out.println("线程完成休眠");        } catch (Exception e) {            System.out.println("休眠被打断");            return;  //返回到程序的调用处        }        System.out.println("线程正常终止");    }} public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he, "线程");        demo.start();        try{            Thread.sleep(2000);  /*主线程睡2秒*/        }catch (Exception e) {            e.printStackTrace();        }        demo.interrupt(); //2s后中断线程    }}
结果:

执行run方法
休眠被打断


下面来俩个非常经典的例子来学习异步,锁这些概念。 我在网上找的资料,也是CSDN的吧,好像是博客园的,不管这么多了,下面的文字转自互联网  

先来看一段程序: 

public class Synchronized_I implements Runnable{private int count=10;  /*票数*/    public void run() {        for(int i=0;i<10;++i){            if(count>0){                try{                    Thread.sleep(1000);                }catch(InterruptedException e){                    e.printStackTrace();                }                System.out.println(count--);            }        }    }    public static void main(String[] args) {Synchronized_I he=new Synchronized_I();    Thread h1=new Thread(he);    Thread h2=new Thread(he);    Thread h3=new Thread(he);    h1.start();    h2.start();    h3.start();}}
运行后的结果: 

10
9
8
7
5
6
4
2
3
1
0
-1 


很明显这是错误的,由于没有同步导致的,所谓同步,打个比方,假如中学时代打饭,一个窗口只能每次服务一个人吧。 而打饭的阿姨就是资源。 在这段代码就是篇数count 


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

同步的实现: 可以使用同步代码块同步方法两种来完成

语法格式:

synchronized(同步对象){
 //需要同步的代码
}



 采用同步方法。
语法格式为synchronized 方法返回类型方法名(参数列表){
    // 其他代码
}



第一次改进 

public class Synchronized_II implements Runnable{private int count=10;  /*票数*/    public void run() {        for(int i=0;i<10;++i){        synchronized (this){   //在统一时间段中只有有一个线程运行            if(count>0){     //其他的线程必须等到这个线程结束之后才能继续执行                try{                    Thread.sleep(1000);                }catch(InterruptedException e){                    e.printStackTrace();                }                System.out.println(count--);            }         }       }    }    public static void main(String[] args) {Synchronized_II he=new Synchronized_II();    Thread h1=new Thread(he);    Thread h2=new Thread(he);    Thread h3=new Thread(he);    h1.start();    h2.start();    h3.start();}}
运行结果: 

10

9

8

7

6

5

4

3

2

1


第二次改进:

public class Synchronized_III implements Runnable{private int count=10;  /*票数*/    public void run() {    for(int i=0;i<10;i++){    sale();    }    }    public synchronized void sale() {if(count>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count--);}}public static void main(String[] args) {Synchronized_III he=new Synchronized_III();    Thread h1=new Thread(he);    Thread h2=new Thread(he);    Thread h3=new Thread(he);    h1.start();    h2.start();    h3.start();}}
运行结果同上 


但是需要注意的是:

提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁


那么怎么会死锁呢?  个人觉得就是你用了我的资源,我用了你的资源,俩个都在等对方的资源。


 假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。


高级工具

wait()和notify()、notifyAll()
这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用
synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,
如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢? 此时就用这三个方法来灵活控制。

wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。

当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;

如果锁标志等待池中没有线程,则notify()不起作用。

notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
注意 这三个方法都是java.lang.Object的方法。

下面列举经典的消费者和生产者的例子 

公共资源Info.java 

public class Info {private String name = "Rollen";    private int age = 20;    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;    }}


生产者Producer.java   每隔执行一次就修改info的属性

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<25;++i){            if(flag){  //如果标志为true的话,睡眠0.1秒后一切按照原来的那样,最后改为false                this.info.setName("Rollen");                try{                    Thread.sleep(100);                }catch (Exception e) {                    e.printStackTrace();                }                 this.info.setAge(20);                flag=false;              }else{    //名字改为chunGe,睡眠0.1秒后年龄也改                this.info.setName("chunGe");                  try{                    Thread.sleep(100);                }catch (Exception e) {                    e.printStackTrace();                }                this.info.setAge(100);                flag=true;            }        }    }}


消费者 Consumer.java 输出当前info的俩个属性 

class Consumer implements Runnable{    private Info info=null;    public Consumer(Info info){        this.info=info;    }         public void run(){        for(int i=0;i<25;++i){            try{                Thread.sleep(100); /*睡眠0.1秒后输出用户信息*/            }catch (Exception e) {                e.printStackTrace();            }            System.out.println(this.info.getName()+"<---->"+this.info.getAge());        }    }}


测试类Test.java

public class Test { 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();}}
运行结果为: 

chunGe<---->100
Rollen<---->100
chunGe<---->20
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
chunGe<---->100

很显然有点混乱了。 这样的话是因为资源还没操作完就被使用。没有同步所致,那么加入同步呢 ? 

修改了info.java 新加了俩个同步资源的方法 

 public synchronized void set(String name, int age){        this.name=name;        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        this.age=age;    }         public synchronized void get(){        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        System.out.println(this.getName()+"<===>"+this.getAge());    }

在生产者中使用如下: 

    public void run(){        boolean flag=false;        for(int i=0;i<25;++i){            if(flag){  //如果标志为true的话,睡眠0.1秒后一切按照原来的那样,最后改为false            this.info.set("Rollen", 20);                flag = false;             }else{    //名字改为chunGe,睡眠0.1秒后年龄也改            this.info.set("ChunGe", 100);                flag = true;            }        }    }
运行结果为: 

ChunGe<===>100
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100

.........

这里出现了重复读取的问题


最后使出最强武器 : 等待和唤醒

public class Info {private String name = "Rollen";    private int age = 20;    private boolean flag=false;        /**     * 同步资源,锁     * @param name     * @param age     */    public synchronized void set(String name, int age){        if(!flag){            try{                super.wait();            }catch (Exception e) {                e.printStackTrace();            }        }        this.name=name;        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        this.age=age;        flag=false;        super.notify();    }         public synchronized void get(){        if(flag){            try{                super.wait();            }catch (Exception e) {                e.printStackTrace();            }        }                 try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        System.out.println(this.getName()+"<===>"+this.getAge());        flag=true;        super.notify();    }    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;    }}
运行结果正常: 

运行流程如下:

当俩个线程开启后,因为标识符为false,所以set方法被调用,直接进入super.wait(),然后当前线程等待,会释放锁标志,从而synchronize get()方法可以执行,然后get方法执行完了之后执行了notify()方法,set方法被执行,开始修改info的属性。  标识符改变,周而复始,保持了资源访问的一致性。 

在我看来,就是在同步函数中需要对资源访问和修改的代码前面加上wait,后面加上notify,表明这段代码像数据库中的事务一样,必须一起执行。



线程池我还没有深入去看,下一篇去学习。


















0 0