黑马程序员---java基础知识之多线程

java基础知识之多线程
 
------- android培训java培训、期待与您交流! ----------

1.多线程的概述

进程:简而言之就是一个正在进行的程序,每一个进程执行都有一个执行的顺序,该顺序是一个执行的路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元。线程控制着程序的进行,一个进程中至少有一个线程。

多线程:简而言之就是一个进程中有多个线程在控制着程序的运行。

    例如:jvm启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的运行,
               而且这个线程运行的代码存在于main方法中,该线程称之为主线程,但是jvm启动的时候不止
                 一个线程,还有负责垃圾回收机制的线程。

2.创建线程---继承Thread类

如何在代码中自定义一个线程
通过对api帮助文档的查找,java已经提供了对线程这类事物的描述,就是Thread类。

创建线程的第一种方式:继承Thread类

1.定义 类继承Tread
2.复写Tread类中的run方法
3.调用线程的start方法,该方法有两个作用:启动线程,调用run方法。
例如:
class Demo extends Thread
{
         //复写Tread类中的run方法
         public void  run() {
               for(int i=0;i<50;i++){
                  System.out.println("Demo run");
               }
          }
}

class   DemoTest{
    
               public static void main(String [] args){

                      Demo  d=new Demo();//创建好一个线程
                      d.start();//.调用线程的start方法,该方法有两个作用:启动线程,调用run方法

                       for(int i=0;i<50;i++){
                                 System.out.println("Hello word!");
                      }
               }
}

输出的结果是50个hello word!和50个Demo run,但是每一次输出的结果的顺序都不一样。

分析结果:从结果可以体现出多线程的一个特性:随机性。这是因为多个线程都在获取cpu的执行权,当cpu执行到该线程时就执行,在某一个时间段,只能有一个程序在运行,(多核除外),cpu在做着快速的切换,以达到看上去是同时运行的结果,我们可以形象的把多线程的运行理解为抢夺xpu的执行权。

3.创建线程---run和start之间的区别

run方法:run方法是Tread定义的一个功能,用于存储线程要运行的代码,复写run方法的目的在于将自定义
                的代码存储在run方法中,让线程运行。
start方法:开启线程并执行该线程的run方法 。
注:当建立好线程的对象之后,应该调用的是start方法,而不是run方法,因为run方法仅仅是对象调用方法
        而线程创建了之后并没有运行。

4.线程运行状态

        1、新建状态(New):新创建了一个线程对象。
  2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
  (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
  5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

        

       解析:
1.当我们new了这个对象后,线程就进入了初始状态; 
2、当该对象调用了start()方法,就进入可运行状态; 
3、进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态; 
4、进入运行状态后情况就比较复杂了 
    4.1、run()方法或main()方法结束后,线程就进入终止状态; 
    4.2、当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后,该线程进入可运行状态,继续等待OS分配时间片; 
    4.3、线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可运行状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 
   4.4、当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配CPU时间片; 
   4.5、当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。  

5.获取线程对象以及名称

线程都有自己默认的名称:Thread---编号  该编号从0开始(在不赋值的情况下)
举例说明:
class Demo extends Thread
{
         Demo(String name){
             super(name);//---继承了Tread类中的构造方法Tread(String name);
         }
         //复写Tread类中的run方法
         public void  run() {

                  System.out.println(this.getName()+"  Demo run");
          }
}

class   DemoTest{
    
               public static void main(String [] args){

                      Demo  d1=new Demo("one");//创建好一个线程并设置线程的名称为one
                      Demo  d2=new Demo("two");//创建好一个线程并设置线程的名称为two
                      d1.start();//.调用线程的start方法,该方法有两个作用:启动线程,调用run方法
                       d2.start(); 
               }
}

输出的结果是:one  Demo run和two Demo run
代码中的this等同于Thread.currentTread()方法,作用是返回对当前正在执行的线程对象的引用。
getName():获取线程的名称
设置线程的名称可以使用setName()和构造函数。

6.创建线程---实现Runable接口

创建线程的第二种方式:实现Runable接口
创建步骤:
    .定义类实现Runnable接口
    .覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
    .通过Thread类建立线程对象
    .将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数(将Runnable接口的子类对象传递给
      Tread的构造函数时因为自定义的run方法所属的对象时Runnable接口的子类对象,所以要让线程去执行
       指定的对象的run方法,就必须明确该run方法所属的对象)
    .调用Tread类的start()方法开启线程并调用Runnable接口子类的run方法。
举例说明:售票

//定义类实现Runnable接口
class Ticket impliments Runnable
{
         private int tick =10;
         //覆盖Runnable接口中的run方法
         public void run(){
                while(true){
                       if(tick>0){
                            System.out.println(Thread.currentTread().getName()+"--------sale:"+tick--);
                        }
                }
         }
}
class TicketTest{
          public static void main(String[] args){
                  Ticket  t=new Ticket();//通过Tread类建立线程对象
                  Thread t1=new Thread(t);//将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                  Thread t2=new Thread(t);
                  Thread t3=new Thread(t);
                  t1.start();//调用Tread类的start()方法开启线程并调用Runnable接口子类的run方法
                  t2.start();
                  t3.start();
          }
}

输出结果为1至10的数字(随机),一共有3个线程在运行。

使用实现Runnable接口的方式创建多线程的好处:避免了单继承的局限性。

创建线程两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable :线程代码存放在接口的子类的run方法中。


7.多线程的安全问题

安全问题:
上述例子极有可能会产生安全问题,输出结果可能为负数,问题产生的原因是因为当多条语句在操作同一个对象共享数据时
一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行,导致了共享数据的错误。
解决思路:
对多条操作共享数据的语句,只能让一个线程都执行完毕,在执行过程中,其他线程不可以参加运行。
解决方式:使用同步代码块
格式:synchronized(对象)
           {
               需要被同步的代码块
           }
对象如同锁,持有锁的线程可以再同步中执行,没有持有锁的线程及时获取了cpu 的执行权,也进不去,因为没有获取锁。
例子修改如下:

//定义类实现Runnable接口
class Ticket impliments Runnable
{
         private int tick =10;
         Object obj=new Object();
         //覆盖Runnable接口中的run方法
         public void run(){
                while(true){
                       //使用synchronized(对象)方法将需要同步的代码块包起来
                       synchronized(obj){
                                   if(tick>0){
                                             try{Tread.sleep(10);}catch(Exception e){}//异常处理
                                            System.out.println(Thread.currentTread().getName()+"--------sale:"+tick--);
                                    }
                       }
                      
                }
         }
}

class TicketTest{
          public static void main(String[] args){
                  Ticket  t=new Ticket();//通过Tread类建立线程对象
                  Thread t1=new Thread(t);//将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                  Thread t2=new Thread(t);
                  Thread t3=new Thread(t);
                  t1.start();//调用Tread类的start()方法开启线程并调用Runnable接口子类的run方法
                  t2.start();
                  t3.start();
          }
}

同步的前提:
   .必须要有两个或者两个以上的线程
   .必须是多个线程使用同一个锁
   .必须保证同步中只能有一个线程在运行
同步的好处:解决了多线程的安全问题
同步的弊端:多个线程需要判断锁,较为消耗资源。
如何找出程序中是否有安全问题:
1.明确哪些代码是多线程的运行代码
2.明确哪些是共享数据
3.明确多线程运行代码中哪些语句是操作共享数据的。
同步有两种表现形式:同步代码块和同步函数

8.多线程---同步函数

同步函数:就是对某个函数加锁,在一个对象访问这个方法的时候,其他方法如果得到权限,要访问这个函数(这样可能会造成数据库的错误值),就碰到所的检测,因为已经有对象访问这个方法了,所以不允许其他对象访问这个函数 

银行存款例子:
class Bank{
       private int balance;
       publicsynchronized void add(int n){
              
                       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);
               }
       }
}
class BankTest{
        public static void main(String []args){
              Cus  cus=new Cus();
              Tread t1=new Thread(cus);
              Tread t2=new Thread(cus);
               t1.start();
               t2.start();
       }  
}
输出结果是:100  200 300 400 500 600

同步函数的锁是this
函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this.

举例说明:
//定义类实现Runnable接口
class Ticket impliments Runnable
{
         private int tick =10;
         //覆盖Runnable接口中的run方法
         public void run(){
                while(true){
                    this.show();                              
                }
         }
         public synchronized void show(){
                
                    if(tick>0){
                           try{Tread.sleep(10);}catch(Exception e){}       //异常处理
                           System.out.println(Thread.currentTread().getName()+"--------sale:"+tick--);
                    }
        }
}

class TicketTest{
          public static void main(String[] args){
                  Ticket  t=new Ticket();            //通过Tread类建立线程对象
                  Thread t1=new Thread(t);       //将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                  Thread t2=new Thread(t);
                  Thread t3=new Thread(t);
                  t1.start();                                 //调用Tread类的start()方法开启线程并调用Runnable接口子类的run方法
                  t2.start();
                  t3.start();
          }
}


static修饰的同步函数的锁是Class对象

如果函数被静态修饰后,函数的锁就不是this了,因为静态函数中不可以定义this.
(静态进内存时,在内存中并没有本类的对象,但是一定有该类对应的字节码文件对象,静态
 的同步方法使用的是锁是该方法所在类的字节码文件对象即类名.class,该对象的类型是class)
举例说明:
//定义类实现Runnable接口
class Ticket impliments Runnable
{
           //静态修饰的变量
         private static int tick =10;
         //覆盖Runnable接口中的run方法
         public void run(){
                while(true){
               
                          show();                                     
                }
         }
         //静态修饰的同步函数
         public static  synchronized void show(){
                
                    if(tick>0){
                           try{Tread.sleep(10);}catch(Exception e){}       //异常处理
                           System.out.println(Thread.currentTread().getName()+"--------sale:"+tick--);
                    }
        }
}

class TicketTest{
          public static void main(String[] args){
                  Ticket  t=new Ticket();            //通过Tread类建立线程对象
                   Thread t1=new Thread(t);       //将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                  Thread t2=new Thread(t);
                  Thread t3=new Thread(t);
                  t1.start();                                 //调用Tread类的start()方法开启线程并调用Runnable接口子类的run方法
                  t2.start();
                  t3.start();
          }
}


9.多线程---单例设计模式

懒汉式单例设计模式
class Single{
      private static Single  s=null;          //创建一个静态的私有的空对象
      private Single(){}              //将构造函数私有化
      public static Single getInstance(){
              
               if(s==null){
                     synchronized(Single.class){
                        if(s==null){
                                         s=new Single();
                               }
                }
                    
               }
               return s;
      }
}
懒汉式用于实例的延迟加载 ,但是当多个线程访问时会发生安全问题,解决的办法是加同步进行,同步代码块和
同步函数都可以解决,但效率比较低效,因为增加了线程访问判断锁的次数,所以使用双重判断的形式来解决效率的问题。
加同步的锁是该类所属的字节码文件对象。


10.多线程---死锁
在编程过程中应尽可能的避免出现死锁的情况,出现死锁的情况往往是因为在同步中嵌套同步
例如:

//定义类实现Runnable接口
class Test implements Runnable{
         private boolean flag;
        Test(boolean flag){
              this flag=flag;
        }
        public voic run(){
           if(flag){
                      synchronized(Lock.obj1){
                              System.out.println("if-----obj1");   
                               synchronized(Lock.obj2){
                                         System.out.println("if-----obj2");   
                             }
                      }
           }
         else{
                      synchronized(Lock.obj2){
                              System.out.println("else-----obj2");   
                                      
                              synchronized(Lock.obj2){
                                         System.out.println("else-----obj1");   
                             }
                      }
           }

      }
}

//定义的是两个锁的类
 class Lock{
        static Object obj1=new Object();
        static Object obj2=new Object();
}
//测试代码
class main{
       public static void main(String []args){
             Thread t1=new Thread(new Test(true));
             Thread t2=new Thread(new Test(false));
              t1.start();
              t2.start();
        }
}

输出结果是:if-----obj1和else-----obj2(两者先后顺序不定)
因为程序出现了死锁的现象,始终没有办法执行完代码。

11.线程间通信---解决安全问题

线程间通信:其实就是多个线程在操作同一个资源,但是操作的动作不同,

例子:
//定义操作的类
class Res{
        String name;
        String sex;
}

//定义类实现Runnable接口(输入即赋值操作)
class  Input implements  Runnable{
        private  Res rs;
        Input(Res rs){
             this.rs=rs;
        }
        public void run(){
              int x=0;
              while(true){
                  //线程同步解决安全问题
                  synchronized(rs){
                          
                            if(x==0){
                        
                                   rs.name="张三";
                                    rs.sex="男";

                            }else{
                      
                                    rs.name="丽丽";
                                    rs.sex="女";
                            }
                              x=(x+1)%2;    //相当于布尔类型,是一个死循环
                  }
                 
              }
        }
}


//定义类实现Runnable接口(输出即取值操作)
class  Output implements  Runnable{
       
        private  Res rs;
        Output(Res rs){
             this.rs=rs;
        }
        public void run(){
                   while(true){
                                //线程同步解决安全问题
                             synchronized(rs){
                                      System.out.println(rs.name+"-------------"+sex); 
                               }
                     }
        }
}

//定义测试类
class  InputOutputDemo{
        
        public static void main(String []args){

               //创建操作资源对象
               Res rs=new Res();
               
               //建立线程对象
               Input intput=new Input(rs);
               Output outtput=new Output(rs);

              //将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                Thread t1=new Thread(intput);
                Thread t2=new Thread(output);
 
                 t1.start();       //启用线程
                 t2.start();
         }
}

输出的结果: 一连串的张三  男和一连串的丽丽  女

Input和Output两个线程操作的是同一资源Res,但是操作的动作即功能不一样。

12.线程间通信---等待唤醒机制

wait()-----等待
notify()----唤醒
notifyAll----唤醒全部
都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有在同步中才有锁,都定义在object类中

关于以上方法都定义在Object类中的原因:
因为这些方法在操作同步线程时,都必须要标识它们所操作线程持有的锁,
只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒,即等待和唤醒必须
是同一个锁,而锁可以使任意的对象,所以可以被任意对象调用的方法定义在Object类中。

通过11的例子的输出结果可以得知这不是我们想要的结果,应该是存入一条数据后打印该数据,再存入下一条数据,这时就需要用到等待呼唤机制

代码修改如下(红色部分 ):

//定义操作的类
class Res{
        String name;
        String sex;
         boolean flag=false;
}

//定义类实现Runnable接口(输入即赋值操作)
class  Input implements  Runnable{
        private  Res rs;
        Input(Res rs){
             this.rs=rs;
        }
        public void run(){
              int x=0;
              while(true){
                  //线程同步解决安全问题
                  synchronized(rs){
                            if(rs.flag)
                             try{ rs.wait();}catch(Exception 3){}
                            if(x==0){
                        
                                   rs.name="张三";
                                    rs.sex="男";

                            }else{
                      
                                    rs.name="丽丽";
                                    rs.sex="女";
                            }
                             x=(x+1)%2;    //相当于布尔类型,是一个死循环
                             rs.flag=true;
                             rs.notify();
                  }
                 
              }
        }
}


//定义类实现Runnable接口(输出即取值操作)
class  Output implements  Runnable{
       
        private  Res rs;
        Output(Res rs){
             this.rs=rs;
        }
        public void run(){
                   while(true){
                                //线程同步解决安全问题
                             synchronized(rs){
                                      if(!rs.flag)
                                       try{ rs.wait();}catch(Exception 3){}
                                      System.out.println(rs.name+"-------------"+sex); 
                                       rs.flag=false;
                               }
                     }
        }
}

//定义测试类
class  InputOutputDemo{
        
        public static void main(String []args){

               //创建操作资源对象
               Res rs=new Res();
               
               //建立线程对象
               Input intput=new Input(rs);
               Output outtput=new Output(rs);

              //将Runnable接口的子类对象作为实际参数传递给Tread类的构造函数
                Thread t1=new Thread(intput);
                Thread t2=new Thread(output);
 
                 t1.start();       //启用线程
                 t2.start();
         }
}

输出的结果:交替打印    张三  男    和    丽丽  女

停止线程:

只有一种,run方法结束。
开启多线程运行,运行代码同常事循环结构。
只要控制住循环,就可以让run线程结束。也就是线程结束。

特殊情况,当线程处于了冻结状态,就不会读到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类提供该方法:interrupt();


守护线程:

也就是后台线程,当所有前台线程结束后,后台线程自动结束。写法:在线程启用前设置调用setDaemon(true);


join方法

例如说当A线程执行到了B线程的join()方法时,A就会等待,等B线程都执行完,A才会执行。join可以用来临时加入线程执行。


优先级&Yield方法

可以设置调用setPriority(int 值1到10),最大级别为10级,最小级别为1级,一般默认是5级。

 

 

------- android培训java培训、期待与您交流! ----------


 


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了python应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值