黑马程序员_Java基础之多线程

来源:互联网 发布:js判断是否是数组 编辑:程序博客网 时间:2024/05/23 22:04

------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------

一:概念讲解

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

1、进程

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

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。   

一个进程中至少有一个线程。

2、线程

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

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

3、多线程

在Java中,并发机制非常重要,但并不是所有的程序语言都支持线程。在以往的的程序中,多以一个任务完成后再进行下一个项目的模式进行开发,这样下一个任务的开始必须等待前一个任务的结束。Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。

扩展:其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。

4、多线程存在的意义: 程序运行中至少有两个线程在运行,一个是主函数的主线程,另一个是垃圾回收的线程。

二:创建线程的方式

1、继承thread类

通过对API的查找,java已经提供了对线程这类事物的描述即为thread类。所以创建线程的第一种方式为继承thread类



打印结果为:


运行的结果每一次都不同,因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行,明确一点,在某个时刻,只能有一个程序在运行。(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果,我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。

cpu每次只执行一个程序,只是在快速的不同线程间切换,表现了多线程的随机性


创建的步骤:

(1)定义类继承thread

(2)复写thread类中的run方法(目的:定义线程要运行的自定义代码<将自定义代码存储在run方法,让线程运行>)

(3)调用该线程的start方法。该方法有两个作用:

作用:1.启动线程      2.运行run方法。目的是将自定义的代码存储在run方法中,让线程运行

class demo extends Thread{  public void run(){  }}run方法用于存储线程要运行的代码。demo demo=new demo();创建对象就创建了一个线程。run方法和 start方法run方法  仅仅是对象调用方法,并没有运行线程start方法  是开启线程并且执行线程中的run方法

2、创建线程的第二种方式:实现Runnable接口。

创建线程 Thread t=new Thread(new 对象名());步骤:1,定义类实现Runnable接口。2,覆盖接口中的run方法(用于封装线程要运行的代码)。3,通过Thread类创建线程对象;4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。      为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。?5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的

run方法。

实现Ticket?t?=?new?Ticket();?/*?直接创建Ticket对象,并不是创建线程对象。?因为创建对象只能通过new?Thread类,或者new?Thread类的子类才可以。?所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。?*/?Thread?t1?=?new?Thread(t);?//创建线程。?/*?只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联?为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。?*/?实现方式和继承方式有什么区别?继承Thread类:线程代码块存放在Thread子类的run方法中实现Runnable,线程代码存放在接口的子类的run方法中,可以被多实现。继承方式有局限性。要被实现多线程的一个类 如果继承了父类 就不能再继承Thread类。实现方式就变面了单继承的局限性。

 

三:线程的运行状态


新建:start()运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒; 线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop() run方法结束

当从冻结状态唤醒的先返回临时状态(阻塞状态),先获得执行资格;或者直接返回运行状态。

四:获取线程名称和对象

1、线程都有自己默认的名称:2、获取线程名称的方法。

Thread.currentThread().getName()currentThread() 获取当前线程对象getName()   获取线程名称 

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

五:多线程的安全问题

1、线程安全问题产生的原因

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

2、线程安全问题的解决方案

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

方式:同步代码块:synchronized(对象)

{  需要被同步的代码。(共享数据)}

同步的前提:1.必须要有两个或者两个以上的线程2.必须多个线程必须使用同一个锁。  必须保证同步中只能有一个线程在运行。

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

解决线程安全的利与弊:

好处:解决了线程的安全问题弊端:消耗了资源,多个线程需要判断锁。

实例讲解:


打印结果如下:


3、同步函数:public synchronized void show(){ }如何找问题?1.明确哪些代码是多线程运行代码2.明确共享数据3.明确多线程运行代码中哪些语句是操作共享数据的?同步函数的锁是 this。函数需要被对象调用,那么函数都有一个所属对象引用。想让线程停一下  Thread.sleep(10);如果同步函数被静态修饰后,使用的锁是 classsynchronized (对象名.class)

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

4、单里设计模式:

饿汉式:
 class Single{
 private static final Single s = new Single( ) ;
 private Single( ) { }
 public static Single getInstance( ) {
 return s ;
 }
 }

P.S.
饿汉式不存在安全问题, 因为不存在多个线程共同操作数据的情况。
懒汉式:懒汉式的特点在于延迟加载,懒汉式也存在问题,解决方式采用加同步, 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 ;
 }
 }
P.S.
懒汉式存在安全问题, 可以使用同步函数解决。
但若直接使用同步函数, 则效率较低, 因为每次都需要判断。
但若采取如下方式, 即可提升效率。
原因在于任何一个线程在执行到第一个if判断语句时, 如果Single对象已经创建, 则直接获取即可, 而不用判
断是否能够获取锁, 相对于上面使用同步函数的方法就提升了效率。 如果当前线程发现Single对象尚未创建, 则
再判断是否能够获取锁。
1. 如果能够获取锁, 那么就通过第二个if判断语句判断是否需要创建Single对象。 因为可能当此线程获取到锁
之前, 已经有一个线程创建完Single对象, 并且放弃了锁。 此时它便没有必要再去创建, 可以直接跳出同步代码
块, 放弃锁, 获取Single对象即可。 如果有必要, 则再创建。

5、死锁

同步中嵌套同步,可能会发生,该怎么解决是由于 

两个线程相互等待 对方已被锁定的资源循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。示例:

class Ticket implements Runnable{
       private static int num = 100;
       Object obj = new Object();
       boolean flag = true;
       public void run(){
     if(flag ){
   while(true ) {
   synchronized(obj ){
  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 DeadLockDemo{
  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();
   }

  }

六:线程间通信问题


思考1:wait();notify();notifyAll();用来操作线程为什么定义在了Object类中。

1、这些方法存于同步中。

2、使用这些方法是必须要标识所属的同步的锁。

3、锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

思考2:wait();sleep()有什么区别?

wait();释放资源,释放锁;

sleep();释放资源,不释放锁。

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

1.是不是两个或两个以上的线程。解决办法 两个线程都要被同步。2.是不是同一个锁。解决办法 找同一个对象作为锁。


打印结果如下:



七:等待唤醒机制。

wait后,线程就会存在线程池中,notify后就会将线程池中的线程唤醒。notifyAll();唤醒线程池中所有的线程。实现方法 :给资源加个标记 flag   synchronized(r){ while(r.flag)//多个生产者和消费者   if(r.flag)//一个生产者和消费者  r.wait();  代码   r.flag=true;  r.notify();  r.notifyAll();}

上面三种方法都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。为什么这些操作线程的方法要定义在object类中呢?因为这些方法在操作同步中线程的是偶,都必须要表示它们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁,而锁可以是特意对象,可以被任意对象调用的方法定义在Object类中。

八:停止线程:run方法结束,就会停止线程,开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让线程结束。方法:改变标记。特殊情况,改变标记也不会停止的情况。将处于冻结状态的线程恢复到运行状态。interrupt();  中断线程。

九:守护线程:SetDaemon将线程标记为守护线程或用户线程。在启动线程前调用 。当线程都为守护线程后,JVM退出。十:JOin方法:t.join();抢过cpu执行权。当A线程执行到了B线程的join方法时,A就会等待,等B线程执行完,A才会执行。Join可以用来临时加入线程执行。优先级:SetPriority(1-10)设置优先级。Thread.MAX_PRIORITY 10Thread.MIN_PRIORITY 1Thread.NORM_PRIORITY 5yield方法:暂停当前正在执行的线程对象,并执行其他线程。开发中应用::保证以下三个代码同时运行。案例

new Thread(){      for(int x=0;x<100;x++)    

  {         sop(Thread.currentThread().getName())     

  }}.start(); for(int x=0;x<100;x++){   sop(Thread.currentThread().getName())}

Runnable r=new Runnable(){   public voud run()

{     for(int x=0;x<100;x++)   

  {      sop(Thread.currentThread().getName())     } 

   }};new Thread(r).start(); 

 

  

 







0 0