黑马程序员_Java基础_多线程_11

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

导读:多线程、创建线程、runstart特点、线程运行状态、获取线程对象及名称、Rannable接口、同步安全码块、同步函数、this锁、class锁、单例-懒汉式、死锁

 

1、多线程

l  何为进程?进程就是正在进行中的程序

一个进程中可以能会出现多条执行路径,如100M的数据,迅雷会默认的分成五个部分,一个部分一个部分的发送请求到服务端下载数据。可是如果你同时五个同时请求的话,会快一点。但是都是在一个进程中完成,线程是进程中的内容。每一个应用程序(进程)至少都有一个线程,因为线程是程序中的控制单元或者执行路径。

无论腾讯还是迅雷程序一启动都会在内存中分配一块内存空间,进程就是用于定义空间,标识空间的。它(进程)用于封装里面的控制单元。

编译的时候,会启动一个javac.exe的进程。接下来Java虚拟机负责运行class文件。这时会产生一个java.exe的进程(该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。)

 

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

l  线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。

(有多个执行路径的情况下,我们就对这个程序称之为多线程程序,如下载)

l  扩展

Ø  其实更细节说明jvmjvm启动不止一个线程,还有负责垃圾回收机制的线程(自动进行的)。

Ø  多线程存在的意义:多个代码同进执行(提高的执行的效率)

Ø  如果没有垃圾回收机制的话,主线程执行,执行到一段时间,因堆中的垃圾太多,主线程必须停止把垃圾干掉之后,再来执行主线程。而如果用多线程,主线程只管处理,另有一个线程回收垃圾,就可以优化程序。

 

2、创建线程

l  线程的创建是谁在创建?线程存在于某一个进程当中。进程是谁创建的?是系统创建的。创建进程之后,windows中的线程也是由windows给你创建的。因此JVM只用调用系统中的内容就可以帮你完成这个动作。

l  如何在自定义的代码中,自定义一个线程(控制单元)呢?

通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。

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

创建步骤:

Ø  定义类继承Thread。(Demo d = newDemo()创建一个子类对象就是创建了一个线程)

Ø  复写Thread类中的run方法。

n  目的:将自定义代码存储在run方法。让线程运行。

Ø  调用线程的start方法,(使该线程开始执行;Java 虚拟机调用该线程的 run 方法。)该方法两个作用:启动线程,调用run方法。(当调用d.start方法后创建的对象d才是执行了起来,这时除了有main这个主线程外,又多了一个线程。它们交替执行)

 

l  进程里真正在执行的是线程,更详细的说cpu切换的是每一个进程中的线程。而一个线程中如果多个线程的话,cpu也要做切换。因些程序开的越多越慢。主线程内容如果先执行完了,只要还有其他的线程还在,进程就不会消失。

l  多核的话,可以达到同进性效果。多核的话,内存就成为了瓶颈。

l  为什么运行结果每一次都不同。

因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行形容为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

3、创建线程-runstart特点

l  为什么要覆盖run方法呢?

Ø  Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。(主线程要执行的代码放在哪里?放在Main方法中。这是虚拟机定义的)

Ø  Thread t = newThread(); t.start();

start()调用run(),可是run()里面没有东西,你调的话,就没有意义。你开户线程的目的是让你运行指定的代码,父类提供了空间,你怎么做才能执行到呢?要沿袭父亲功能,覆写父类内容。Demo d = newDemo(); 调用d.start()的时候,它编译的时候,它调的是父类的的方法。但在运行的时候,因为子类覆写了父类的方法,所以要调用子类的方法。

 

l  为什么要开启线程?让线程执行某些代码,让代码达到同时执行的效果。

 

l  为什么用d.start而用不直接用d.run();

Ø  因为用d.run();就和一般的方法调用一样,和多线程就没有关系了。

Ø  d.start();//开启线程并执行该线程的run方法。(调用底层,让控制单元执行的动作)

Ø  d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。(仅仅是封装要运行的代码)

 

4、线程练习

       创建两个线程,和主线程交替运行。

 

5、线程运行状态

l  了解线程的几种状态才知道线程到底是怎么运作。

l  冻结:睡眠[sleep()]和等待[wait()](这两个是放弃了执行资格)。线程先不运行了,一会再运行。但是没有死。

Ø  如果一线程,它等待在这了,进程结束了没有?没有,虽然还等着,但是还活着。进程如果结束了空间就没有了。空间没有了,线程就挂了。(程序死了,进程还在,这时候要用资源管理器结束进程,强制把里面的进程都给干掉)

Ø  冻结状态中让stop()也是可以的。

l  线程被start()之后,一定会运行吗,不一定。因为有一个就绪状态(临时状态,具备运行资格,但是没的执行权。)


 

6、获取线程对象以及名称

l  你想拿线程的名称,当然是拿线程对象的方法,线程名称应该定义在线程这类事物里面,线程最熟悉。

l  /*

创建两个线程,和主线程交替运行。

原来线程都有自己默认的名称。Thread-编号 该编号从0开始。

static ThreadcurrentThread():获取当前线程对象。静态的无特有数据。有类名就可能访问。

getName(): 获取线程名称。

设置线程名称:setName或者构造函数。

*/

class Testextends Thread

{

       //private String name;

       Test(String name) 

       {

              //this.name = name;

              super(name);  //父类描述完了一个私有的name,并通过setName()getName()方法对其进行访问,我们只要用就行了。

       }

       public void run()

       {

              for(int x=0; x<60; x++) //线程0运行的时候,栈内存中就会给线程0分配一个空间,这个空间就有一个run()方法,这个方法就有一个x。线程1也是一样。局部的变量,在栈内存中都有独立的一份。

              {

          System.out.println((Thread.currentThread().getName()+"…"+"run..."+x);

              }

       }

 

}

class ThreadTest

{

       public static void main(String[] args)

       {

              Test t1 = newTest("one---");  //线程初始化的时候就可以有名称

              Test t2 = newTest("two+++");

              t1.start();

              t2.start();

//            t1.run();  //注意区别runstart()的区别。

//            t2.run();

              for(int x=0; x<60; x++)

              {

                     System.out.println("main....."+x);

              }

       }

}

l  售票的例子

可以用static共享100张票,来实现票的不重复打印。

IllegalThreadStateExceptionTread:状态出问题了。

例:运动员跑圈,发令人,第一圈一枪,第二圈一枪,每三圈又一枪。运动员就不跑了。怎么回事呢,我都已经开始了(创建对象,并且运行了。已经从创建状态到运行状态,你又给它start一次没有意义),怎么老让开始。

l  真正创建对象的是Thread这个对象或者它的子类对象。

 

7、创建线程实现Runnable接口(代码见第8

l  需求:简单的卖票程序。多个窗口同时买票。

l  创建线程的第二种方式:实现Runable接口

步骤:

Ø  定义类实现Runnable接口

Ø  覆盖Runnable接口中的run方法。

n  将线程要运行的代码存放在该run方法中。

Ø  通过Thread类建立线程对象。

Ø  Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

n  为什么要将Runnable接口的子类对象传递给Thread的构造函数。

n  因为,自定义的run方法所属的对象是Runnable接口的子类对象。

n  所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

Ø  调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

l  实现方式和继承方式有什么区别呢?

实现方式好处:避免了单继承的局限性。

在定义线程时,建立使用实现方式。

l  两种方式区别:(面试经常考)

Ø  继承Thread:线程代码存放Thread子类run方法中。

Ø  实现Runnable:线程代码存在接口的子类的run方法。(可以避免单继承的局限性)

l  Java是单继承的,如Student继承自Person,那么就不能继承自Tread 。因此Java工程师给你提供了一个规则,你可以不是我的儿子,你的代码我还能帮你执行,但是有一个前提你要符合我的前提,我才能给我你去办。Student可以实现Rannable接口,重写其中的run()方法使问题得以解决。学生在继承了Person的同时,还可以实现接口。

 

8、多线程安全问题(同步代码块)

l  通过分析,发现,打印出0-1-2等错票。

l  多线程的运行出现了安全问题。(玩多线程我们最害怕的就是安全性问题,你测试的时候可能测不出来,但运行的过程中在复杂的环境下,就可能出错)

l  问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

l  解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

l  Java对于多线程的安全问题提供了专业的解决方式。就是同步代码块。

synchronized(对象)爱放什么对象就放什么对象,只要是对象就行。也可以用Object obj = newobject();这时称obj为锁。这里有两个标志位01。默认是1,即开状态,线程进来之后,不是去执行if,而是先给我关上。关上之后标志位变为0,线程再判断ifIf判断完成后,再去把标志位置为1.

synchronized(对象)

{

       需要被同步的代码(哪些代码要同步,要看哪些代码在操作共享的数据)

}

l  对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

l  火车上的卫生间---经典同步。

l  同步的前提:

Ø  必须要有两个或者两个以上的线程。

Ø  必须是多个线程使用同一个锁。

l  必须保证同步中只能有一个线程在运行。

 

l  好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源。资源消耗是在允许的范围内。

l  */

class Ticketimplements Runnable

{

       private int tick = 1000;

       Object obj = new Object();

       public void run()

       {

              while(true)

              {

                     synchronized(obj) //不能把run()方法中的代码都放到同步中去。有此代码需要同步,有些代码不需要同步。

                     {

                            if(tick>0)

                            {

                                   //try{Thread.sleep(10);}catch(Exceptione){}

                                   System.out.println(Thread.currentThread().getName()+"....sale: "+ tick--);

                            }

                     }

              }

       }

}

class  TicketDemo2

{

       public static void main(String[] args)

       {

 

              Ticket t = new Ticket();

 

              Thread t1 = new Thread(t); //创建了一个线程;//在创建线程对象的时候就要明确创建什么类形的代码。Thread有一个比较特殊的构造对象:TreadRunnable target)。运用多态的形式,建立了接口的引用,你只要符合规则的我都能用。Tread类本身也实现runnableRunnable接口的定义,实际就是在确立线程要运行代码要存放的位置,就是run方法。

              Thread t2 = new Thread(t);

              Thread t3 = new Thread(t);

              Thread t4 = new Thread(t);

              t1.start();

              t2.start();

              t3.start();

              t4.start();

       }

}

结果可能有线程1、线程2、线程3、可是就是没有线程0,有123说明0线程一定启动了,有运行资格但是就是没有抢到运行权。还没有等到时自己抢到的时候标卖光了。

 

9、多线程-同步函数

l  函数封装代码和同步代码块封装代码有什么不一样?

同步代码块封装代码,它带着同步的特性。可以不可以让函数有同步性,这是可以的。如,publicsynchronized void add(int n){}; 这个时候就不用写Object obj = new Object(); 中的obj了。

l  函数有两种表现形式,一、同步代码块。二、同步函数。

l  需求:银行有一个金库。有两个储户分别存300员,每次存100,存3次。

l  目的:该程序是否有安全问题,如果有,如何解决?

l  如何找问题:

Ø  明确哪些代码是多线程运行代码。

Ø  明确共享数据。

Ø  明确多线程运行代码中哪些语句是操作共享数据的。

 

10、同步函数的锁是this

/*

同步函数用的是哪一个锁呢?

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

通过该程序进行验证。使用两个线程来买票。一个线程在同步代码块中。一个线程在同步函数中。都在执行买票动作。

*/

class Ticketimplements Runnable

{

       private int tick = 100;

       Object obj = new Object();

       boolean flag = true;

       public void run()

       {

              if(flag)

              {

                     while(true)

                     {

                            synchronized(this) //同步代码块,这里用对象this,而不用obj。否则会出现问题。加了synchronized但是还出现安全问题,一定是两个前提中有一个不有满足。在这是如是用的是obj就是用的不是同一把锁。会有0这张票。

                            {

                                   if(tick>0)

                                   {

                                          try{Thread.sleep(10);}catch(Exceptione){}

                                          System.out.println(Thread.currentThread().getName()+"....code: "+ tick--);

                                   }

                            }

                     }

              }

              else

                     while(true)

                            show();

       }

       public synchronized void show()  //在这里的锁是this。将多线程运行代码中的共享数据封装在一个函数中,构成同步函数

       {

              if(tick>0)

              {

                     try{Thread.sleep(10);}catch(Exceptione){}

                     System.out.println(Thread.currentThread().getName()+"....show....: "+ tick--);

              }

       }

}

class  ThisLockDemo

{

       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(Exceptione){}

              t.flag = false;

              t2.start();

       }

}

 

11、静态同步函数的锁是Class对象

/*

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不在是this。因为静态方法中也不可以定义this

静态在进内存的时候没有本类对象,有类调用。类进内存这时,有对象因为类要先封装成一个class的对象(此时内存中字节码文件对象)。类名.class  该对象的类型是Class

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class

*/  

上面函数的三处改动:

privatestatic  int tick = 100;

synchronized(Ticket.class)

public staticsynchronized void show()

 

12、单例设计模式-懒汉式

l  懒汉式在加了同步,每次都要判断这个锁,会比较低效。用双重判断会提高一些懒汉式的效率。

l  懒汉式和饿汉式有什么不同?(面试)

懒汉式的特点在于延时加载。延时加载有没有问题?有多线程访问会出现安全问题。怎么解决?可以加同步业解决。加同步的方式?用同步代码块和同步函数都行,但是稍微有一点低效。用双重判断的形式,可以解决判断的问题。加同步的时候加的锁是哪一个?该类所属的字节码文件对象。

l  题:请给我我写一个延时加载的单例设计模式示例。

//懒汉式

class Single

{

       private static Single s = null;

       private Single(){}

       public static  Single getInstance()

       {

              if(s==null)

              {

                     synchronized(Single.class)

                     {

                            if(s==null)

                                   //--->A;

                                   s = newSingle();

                     }

              }

              return s;

       }

}

class SingleDemo

{

       public static void main(String[] args)

       {

              System.out.println("HelloWorld!");

       }

}


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值