黑马程序员——Java基础---多线程(1)

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

多线程

一、线程概述:
1 、进程:是一个正在执行的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径或是叫一个控制单元
2、线程:是进程中一个独立 的控制单元。线程在控制着程序的执行。
一个进程中至少有一个线程。
3、java VM启动的时候会有一个进程,java.exe
该进程中至少有一个线程负责 java程序 的执行。
而且这个线程运行的代码存在于main方法中,该线程称之为主线程
4、扩展:
其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。
二、自定义线程:
1、如何在自定义代码中,自定义一个线程?
调用系统中的动作就能完成,
通过对API的查找,java已经提供 了对线程这类事物的描述,即Thread类。
2、创建线程有两种方式
第一种:继承Tread类。
步骤:
1)定义类继承Thread
2)复写Thread类中的run()方法
复写run()方法的目的:将自定义代码存储到 run方法中,让线程运行。
3)调用线程的start方法(start:启动线程,调用run方法)
发现运行结果每次都不同:因为多线程都在获取CPU的执行权,cpu执行到谁,谁就执行。
明确一点:某一时刻,cpu中只能有一个程序在执行(多核cpu除外)。CPU在做着快速切换的动作,以达到看上去是同时执行的结果。我们可以把多线程的运行行为看作是抢夺CPU的执行权。
3、这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说的算。
示例:
package com.itheima.demo;
//线程
class Demo extends Thread{
     public void run(){
           for( int i=0;i<60;i++)
              System. out.println( "demo run"+i);
     }
}
public class ThreadDemo {

           public static void main(String[] args) {
          Demo d= new Demo(); //创建一个对象,实际就是生成一个线程
          d.start(); //开始执行线程,调用run()方法
           //d.run();相当于线程没有开启,还是main线程执行的。相当于一般方法的调用
          
           for( int i=0;i<60;i++)
              System. out.println( "HelloWorld"+i);
     }
}
4、run()方法
如果该线程是使用独立的Runable运行对象构造的,则调用Runable对象的run()方法。否则,该方法不执行任何操作返回。
Thread子类应该重写该方法。
5、为什么要覆盖run()方法呢?
Thead类用于描述线程。那么,该类就定义了一个功能,用于存储要运行的代码,该存储功能就是run方法。
也就是说:Thread类中的run方法用于存储线程要运行的代码。
例如:主线程要用到的代码存储在main中,这个是JVM定义的。
复写run()方法的目的:将自定义代码存储到 run方法中,让线程运行。
6、练习:
需求:创建两个线程,和主线程交替运行。
package com.itheima.demo;
//需求:创建两个线程,和主线程交替运行。
class Test extends Thread{
     private String name;
     Test(String name){
           this. name=name;
     }
     public void run(){
           for( int i=0;i<50;i++)
              System. out.println( name+ ":test run"+i);
     }
}
public class ThreadDemo2 {

     public static void main(String[] args) {
          Test t1= new Test( "1");
          Test t2= new Test( "2");
          t1.start();
          t2.start();           
           for( int i=0;i<50;i++){
              System. out.println( "main run"+i);
          }
     }
}
7、继承Thread类方法的总结:(线程创建方式一
1)子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2)建立子类对象的同时线程也被创建。
3)通过调用start方法开启线程。
三、线程运行状态

四、获取线程对象及名称
getName()获取线程名称,setName()设置线程名称
1、线程有自己默认的名字:Thread-编号(编号从0开始)
2、自定义线程名称:
父类已经有私有的name方法,子类只要拿过来用就好了。(父类的构造方法Thread(String name))
3、对象的方法:currentThread():返回当前线程对象,该方法是静态的,没有访问特有数据。
可以用类名直接访问。
this==Threadd.currentThread()
示例:
package com.itheima.demo;
//需求:创建两个线程,和主线程交替运行。
class Test extends Thread{
//   private String name;
     Test(String name){
           super (name);
     }
      public void run(){
           for ( int i=0;i<50;i++)
               System. out .println(Thread. currentThread().getName()+ ":test run" +i); //线程对象调用自己父类中的方法。
               //System.out.println(this.getName()+":test run"+i);//this==Threadd.currentThread()
     }
}
public class ThreadDemo2 {

      public static void main(String[] args) {
          Test t1= new Test( "1" );
          Test t2= new Test( "2" );
          t1.start();
          t2.start();
          
           for ( int i=0;i<50;i++){
              System. out .println( "main run" +i);
          }

     }
}
五、售票的例子:
示例:多个窗口同时买票
package com.itheima.demo;
/*需求:简单的 买票程序
 * 多个窗口同时买票
 */
class Ticket extends Thread{
     private int tick=100;
     public void run(){
           while( true){
               if( tick>0){
                    System.out.println(this .getName()+"...sale" +tick --);
               }
           }
     }
}
public class ThreadDemo3 {

     public static void main(String[] args) {
          Ticket t= new Ticket(); //
          t.start();
     }
}
没有办法实现多个窗口同时买票,即共享100张票。
六、创建线程的另一种方式:
声明实现Runnable接口,该类然后实现run方法().然后可以分配该类的一个实例,在创建Thread时作为参数传递并启动。
2、Runnable接口应该有那些打算通过某一线程执行其实例的类来实现 ,类必须定义一个称为run的无参方法。
3、步骤:
1)定义类实现Runnable接口
2)覆盖Runable接口中的run方法
将线程要运行的代码存放在run方法中。
3)通过Thread类建立线程对象
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么 将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
因为自定义run方法所属的对象是Runnable接口的子类对象,
所以要让接口去执行指定对象的run方法。就必须明确该方法所属的对象。
5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
4、实现方式和继承方式有什么区别?
图:
实现方式的好处是:避免了单继承的局限性。在定义线程时,建议使用实现方式
多态 的方式
两种方式的区别是:
继承Thread类:线程代码存放在Thread子类run方法中。
实现Runnable接口:线程代码存放在接口子类的run方法中。
七、线程安全问题:
如图:

看谁从操作共享数据
2、通过分析,发现:出现了打印0,-1,-2等错误--->多线程的运行出现了安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完。另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

3、java对于多线程的同步问题提供了专业的解决方案
就是同步代码块:
synchronized(对象){//可以是任意对象
需要被同步的代码;
}
对象如同锁,持有锁的线程看有在同步中执行,
没有持有锁的线程即使获得CPU的执行权,有进不去,因为没有获取锁。
示例:售票
package com.itheima.demo;
class Tick implements Runnable{
     private int tick=100;
     Object obj=new Object();
     public void run(){
           while( true){
               synchronized( obj){
                    if( tick>0){
                         try{Thread. sleep(10);}catch(Exception e){} //抛出异常只能处理
                        
                        System. out.println(Thread. currentThread().getName()+":sale"+ tick--);
                   }
             }
          }         
     }
}
public class TicketDemo {

     public static void main(String[] args) {
          Tick t= new Tick();
          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();
     }
}
例如:上火车上的 卫生间-----同步锁
4、同步的前提:
1)必须具有两个或者两个以上的线程。
2)必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在有运行。-->因此,有些代码需要同步,有些代码不需要同步。
5、优缺点?
优点:保证了 多线程的安全问题。
缺点:多个线程每次都会对锁就进行判断,较为消耗资源。
6、练习:
需求:银行有一个金库, 两个用户分别存300块钱,每次100,共3次。
目的:该程序是否有安全问题,如果有,该怎么解决?

怎么找问题:
1)明确哪些代码是多线程运行 的代码。
2)明确共享数据
3)明确多线程运行代码中哪些是操作共享数据的。
示例:
package com.itheima.demo;
//
class Bank{
     private int sum;//金库
     Object obj=new Object();
     public void add(int n){
           synchronized( obj){
               sum= sum+n;
               try{Thread. sleep(10);}catch(Exception e){}
              System. out.println( "sum="+ sum);
          }
     }    
}
class Cus implements Runnable{
     private Bank b= new Bank();
     public void run(){
           for( int i=0;i<3;i++){
               b.add(100);
          }         
     }
}
public class BankDemo {
     public static void main(String[] args){
          Cus c= new Cus();
          Thread t1= new Thread(c);
          Thread t2= new Thread(c);
          t1.start();
          t2.start();         
     }
}
7、同步代码块封装代码和函数封装代码有什么不同?
唯一区别是:同步代码块,带着同步的特性。那么,让函数具有同步性就好了。
8、同步有两种表现形式:一种是同步代码块,一种是同步函数
示例:
class Bank{
     private int sum;//金库
     //Object obj=new Object();
     public synchronized void add(int n){
           //synchronized(obj ){
               sum= sum+n;
               try{Thread. sleep(10);}catch(Exception e){}
              System. out.println( "sum="+ sum);
           //}
     }     
}
示例:
class Tick implements Runnable{
     private int tick=100;
     //Object obj=new Object();
     public void run(){ //这样不靠谱,只有0线程进去,就锁起来。对于哪些是同步的,哪些是不要同步的没有搞清楚。
           while( true){
              show();
            }
           }              
     public synchronized void show(){  
           if( tick>0){
               try{Thread. sleep(10);}catch(Exception e){}//抛出异常只能处理             
               System. out.println(Thread. currentThread().getName()+":sale:"+ tick--);  
          }    
     }
}
9、同步函数用的是哪一个锁呢?-------上面调用show方法的一定是对象,相当于this.show()
函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。
所以:同步函数使用的锁是this
10、通过该程序进行验证
使用两个线程来买票:一个线程在同步代码块中,一个线程在同步函数在中。都在执行卖票动作。
示例:
//
class Ticket implements Runnable{
    private int tick =100;
    boolean flag= true;
    Object obj= new Object();
    public void run(){    
          if(flag ){
              while(true ){
                  synchronized(this){//obj--->this
                       if(tick >0){
                           try{Thread.sleep(10);} catch(Exception e){}
                          System. out.println(Thread.currentThread().getName()+ "--code--"+tick --);
                      }
                 }
             }
         }
          else{
              while(true ){
                 show();
             }
         }
    }
    public synchronized void show(){
          if(tick >0){
              try{Thread.sleep(10);} catch(Exception e){}
             System. out.println(Thread.currentThread().getName()+ ":show:"+tick --);
         }
    }
}
public class TicketDemo {
    public static void main(String[] args) {
         Ticket t= new Ticket();
         Thread t1= new Thread(t);
         Thread t2= new Thread(t);
         t1.start();
         t. flag= false;         
         t2.start();
    }
}
发现:
11  、静态函数的同步:
1)同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不再是this了。因为静态方法中也不可以定义this.
2)静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class
该对象对应的类型是Class,可以通过getClass()获取某一对象运行的实例。

总结:静态的同步方法使用的锁是,该方法所在类的字节码文件对象。类名.class(这个对象子内存中是唯一的,因为字节码文件是唯一的。)






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值