黑马程序员——java基础日记——多线程(1)

来源:互联网 发布:帝国cms代码高亮 编辑:程序博客网 时间:2024/05/29 03:27

多线程

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

  一、线程的理解    

           进程是正在执行的程序。在一个进程中至少要要有一个线程。线程是进程中一个负责程序执行的控制单元(执行路径)。 多条执行路径就是多线程。

多线程的好处:

          解决了多部分代码同时运行的问题。没个线程都有自己执行的路径(或者说执行内容,代码块),开启多线程能同时执行多个代码块。

多线程的弊端:

         线程太多,会导致效率变低。因为多个应用程序同时执行是CPU的快速切换完成的,而切换也是要花费时间的。

二、线程的创建

方式一:通过继承Thread类

           1.定义一个类继承Thread类。
           2.覆盖Thread类中的run方法。
           3.直接创建Thread的子类对象创建线程。
           4.调用start方法开启线程并调用线程的任务run方法执行。

如:

package com.heima;class person extends Thread{public void run(){System.out.println("class person is run");}}abstract class test {public static void main(String[] args) {person p = new person();//创建线程p.run();//这里只是调用了线程中的run方法,还没有开启线程//p.start();//这里才是开启线程,开启并调用run。}}

方式二:实现Runnable接口

1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。

4.调用线程对象的start方法开启线程。
如:

package com.heima;class person implements Runnable{public void run(){System.out.println("class person is run");}}abstract class test {public static void main(String[] args) {person p = new person();//新建Runnable子类对象pThread t = new Thread(p);//将p作为参数传递t.start();//开启线程}}


实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

多线程与单线程的区别:

如:

单线程:

package com.heima;class person{private String name;public person(String name){this.name=name;}public void show(){for(int i=0;i<10;i++){System.out.println(name+":"+i);}}}abstract class test {public static void main(String[] args) {person p1 = new person("张三");p1.show();person p2 = new person("李四");p2.show();}}

结果:

由此可看出:在单线程中,在在上一句代码执行完,下一句代码才能执行。

多线程:

package com.heima;class person extends Thread{private String name;public person(String name){this.name=name;}public void run(){show();}public  void show(){for(int i=0;i<10;i++){System.out.println(name+":"+i+" Threadname="+Thread.currentThread().getName());}}}abstract class test {public static void main(String[] args) {person p1 = new person("张三");p1.start();person p2 = new person("李四");p2.start();}}


结果:

可以看出:两条线程是随机的切换运行的。也就是之前说的同时运行。

三、线程的安全问题

        多个线程在操作同一数据的时候容易出现安全问题

如例:

模拟4个线程同时卖100张票。

package com.heima;class Ticket implements Runnable{       private int num = 100;       public void run(){             while(true ){                   if(num > 0){                         try{                              Thread. sleep(10);                        } catch(InterruptedException e){                              e.printStackTrace();                        }                        System.out.println(Thread.currentThread().getName() + "...sale..." + num--);                  }            }      }}class test{       public static void main(String[] args){            Ticket t = new Ticket();            Thread t1 = new Thread(t);            Thread t2 = new Thread(t);            Thread t3 = new Thread(t);            Thread t4 = new Thread(t);            t1.start();            t2.start();            t3.start();            t4.start();      }}

结果:

分析:安全问题的原因在于Thread-0通过了if判断后,在执行到“num--”语句之前,num此时仍等于1。CPU切换到Thread-1、Thread-2、Thread-3之后,

这些线程依然可以通过if判断,从而执行“num--”的操作,因而出现了0、-1、-2的情况。

那这写安全问题呀怎么决解捏?

方法一、

       使用同步代码块。将被多条线程操作的代码封装起来,当有线程执行这些的代码的时候,其它线程都不能参与执行。

格式:

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

如:

package com.heima;class Ticket implements Runnable{       private int num = 100;       Object obj =new Object();       public void run(){      while(true){      synchronized(obj){      if(num>0){      System.out.println(Thread.currentThread().getName()+"...sale..."+num--);      }         }      }      }}class test{       public static void main(String[] args){            Ticket t = new Ticket();            Thread t1 = new Thread(t);            Thread t2 = new Thread(t);            Thread t3 = new Thread(t);            Thread t4 = new Thread(t);            t1.start();            t2.start();            t3.start();            t4.start();      }}


分析:当num=1时,其他的线程无法进入同步代码块,只能等当前操作的线程执行完才能进入同步代码块。当前线程执行完时,num已经等于0,无法通过判断,故停止售票。

同步的前提:必须有多个线程并使用同一个锁。

好处:解决类线程的安全问题。

弊端:每个线程都要去判断同步上的锁,一旦线程太多,就会降低程序运行效率。

方法二、

    使用同步函数。

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

如:public synchronized void show(){}

      同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this
作为锁,就可以实现同步。

      静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class
表示。

四、死锁

           死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁示例:同步嵌套。

package com.heima;class Ticket implements Runnable{       private int num = 100;       boolean flag = true;       Object obj = new Object();       public void run(){             if(flag ){                   while(true ){                         synchronized(obj ){                               if(num > 0){                                show();                               }                        }                  }            } else                   while(true )                        show();        }       public synchronized void show(){       synchronized(obj){       if(num > 0){                   try{                        Thread. sleep(10);                   } catch(InterruptedException e){                        e.printStackTrace();                   }                   System.out.println(Thread.currentThread().getName() + "...function..." + num--);            }       }      }}class test{       public static void main(String[] args){            Ticket t = new Ticket();            Thread t1 = new Thread(t);            Thread t2 = new Thread(t);                        t1.start();             try{              Thread. sleep(10);            } catch(InterruptedException e){                  e.printStackTrace();            }            t. flag = false ;            t2.start();      }}

有可能出现这种情况:


分析:run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。show方法中则要获取this对象锁。

也就是说在程序运行时,当t1获取到run中的obj对象锁执行同步代码块,t2获取show中的this对象锁执行show方法。

同步代码块中的show无法获取this对象锁无法执行,show方法中的同步代码块也无法获取obj对象锁无法执行,从而造成死锁。

五、多线程中的单例设计模式

            饿汉式单例设计模式是没有安全问题的,因为不存在多个线程共同操作数据,在这里主要说一下懒汉式单例设计模式。

懒汉式单例设计模式:

package com.heima;class person{private static person p =null;private person(){}public static person getInstance(){if(p==null){//没有同步,容易出现安全问题p=new person();}return p;}}

为了解决安全问题,我们可以添加同步函数来解决。

如:

public static synchronized person getInstance(){     if(p==null){     p=new person();    }     return p;}

不过这样每次都要去判断同步上的锁,效率比较低。
我们还可以再优化一下,如使用同步代码块,添加双重判断。

public static person getInstance(){if(p==null){synchronized(person.class){if(p==null)p=new person();}}return p;}

       所有的线程先通过第一个if判断,如果对象已经存在,则不用再去看同步的锁,直接获取就行。如果对象不存在,再去判断锁。
获取锁之后,使用第二个if判断对象是否已经存在,不存在才新建,否则跳出锁直接返回对象。这样懒汉式的安全问题就解决啦!!



 

0 0
原创粉丝点击