黑马程序员_ JAVA学习日记—JAVA中的多线程

时间:2023-02-20 10:35:43

 

黑马程序员-学习日记

 

黑马程序员_ JAVA中的多线程

 

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

 

一:线程的概述

   A:进程和线程的区别:

进程:正在执行的程序,代表着一个应用程序对应的内存空间。

线程:用于控制进程中运算执行流程的控制单元,或者称为执行路径。

一个进程中至少有一个线程负责该应用程序的执行。当一个进程中有了多个线程时,就成为多线程。多线程的出现的对CPU的提高了效率,提高了CPU的使用率。但是线程过多,会导致CPU的资源耗费,降低性能。

B:Java中 JVM本身也是一个多线程的应用程序。其中,有一个主线程负责java程序运行,至少还有另一个线程垃圾回收线程,负责堆内存的内存管理。主线程要运行的代码都存放在main方法中。

     C:线程中的几个方法:

多线程的创建,为了对各个线程进行标识,他们有一个自己默认的名称。

格式:Thread-编号,编号从0开始。

static Thread .currentThread():获取当前线程对象;

String getName():获取线程名称;

void setName():设置 线程的名称;

Thread(String name):构造函数,线程对象一建立就可以指定名称。

二:什么时候使用多线程呢?

当多部分代码需要同时执行时。这时就要开辟多条执行路径,来完成多部分代码的同时执行。

常见的多线程程序: 下载工具,聊天工具。

三:线程的创建。

       1 继承Thread类:

              a,定义类继承Thread类。

              b,覆盖Thread类中的run方法,将需要被多线程执行的代码定义到该run方法当中。

              c,建立Thread类的子类创建线程对象。

              d,调用start方法,开启线程并调用该线程的run方法。

      特点:

             1.当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程*享该数据。加上静态,虽然实现了共享但是生命周期过长。

                 2.如果一个类明确了自己的父类,那么很遗憾,它就不可以在继承Thread。因为java不允许类的多继承。

       2.实现Runnable接口:

              a,定义类实现Runnable接口。

              b,覆盖Runnable接口中的run方法,将需要被多线程执行的代码定义到该run方法当中。

              c,通过Thread类创建线程对象。

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

                     为什么这么做呢?因为,要被多线程执行的代码都存放了Runnable接口的子类中。所以必须要明确线程对象要执行的run方法所属的对象。

              e,调用Thread类的start方法,开启线程并调用Runnable接口子类对象的run方法。

   特点:

     1.描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行。同时还在操作属性。那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作;实现了数据的共享。

2.实现了Runnable接口的好处,避免了单继承的局限性。也就说,一个类如果已经有了自己的父类是不可以继承Thread类的。但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的形式。实现一个Runnable接口

代码体现:

class Testimplements Runnable{

       private boolean flag;

       Test(boolean flag){

              this.flag = flag;

       }

       public void run(){

              if(flag){

                     while(true){

                            synchronized(MyLock.locka){

                                   System.out.println(Thread.currentThread().getName()+"...if......locka");

                            synchronized(MyLock.lockb){

                                   System.out.println(Thread.currentThread().getName()+"...if......lockb");

                                   }

                            }

                     }

              }

              else{

                     while(true){

                            synchronized(MyLock.lockb){

                            System.out.println(Thread.currentThread().getName()+"...else..........lockb");

                            synchronized(MyLock.locka){

                            System.out.println(Thread.currentThread().getName()+"...else..........locka");

                                   }

                            }

                     }

              }

       }

}

class MyLock{

       public static Object locka = newObject();

       public static Object lockb = newObject();

}

class DeadLockTest{

       public static void main(String[] args) {

              Test t1 = new Test(true);

              Test t2 = new Test(false);

              Thread th1 = new Thread(t1,"张三");

              Thread th2 = new Thread(t2,"李四");

              th1.start();

              th2.start();

       }

}

三:这两种方式的区别:

              A.实现Runnable接口是可以将资源共享。

              B.实现Runnable接口避免了单继承的局限性 。

          所以建议使用实现Runnable接口的方式。

四:线程的状态:

       A.创建:

通过建立Thread类的对象或者Thread类的子类对象,来完成线程创建。

         当调用了start方法后,线程就具备了执行资格,但是不一定立刻具备执行权。

         所以这时处于的状态是临时阻塞状态,就是在等CPU过来对其进行执行。

       B. 运行:

线程如果具备了执行资格的同时,还具备了执行权,那么该线程就是在当前正在运行的线程。

              这时它就处于运行状态。

       C. 冻结:

              释放了线程执行权,而且释放了线程的执行资格。

              当线程执行到 sleep方法,或wait方法时,线程就处于这种状态。

              当sleep时间到,或者被wait的线程,被notify后,

              线程又重新获取到了执行资格,但是不一定获取到执行权,所以这时会从冻结状态转到临时阻塞状态。

       D.消亡:

              当线程调用了stop方法(过时),或者线程执行的代码已经结束了,这时该线程结束,该执行路径在进程中消失。

       E. 临时阻塞:

         线程具备了执行资格,但是没有执行权。

       D.线程具备一个随机性:是因为cpu做着快速的切换造成的。

五:线程中安全问题:

       A.安全问题产生的原因:

              当线程代码中有多条语句在操作线程共享的数据。

              这多条语句被多个线程分开执行时,容易产生数据的错误。

       B.安全问题涉及的要素:

              a,在线程代码中有共享数据。

              b,在线程代码中有多条操作共享数据的语句。

              只要这两个要素存在,就容易产生安全问题,这也是判断是否有安全问题的依据。

       C.安全问题的解决原理:

              在某一个时刻或者时间段,对于多条操作共享数据的代码,只能有一个线程进行执行;在执行期间不可以有其他线程进行参与;简单说就是将部分代码加锁。

    举例:在火车上上厕所,要上锁!

       D.安全问题的解决代码:

              java中对这种问题,提供了具体的解决代码:同步。

              1,同步代码块:

                     synchronized(对象){

                            需要被同步的代码。

                     }

              2,同步函数:

                     将同步关键字修饰在函数上即可;其实同步就是使用了一个锁机制。

六:同步:

       A:前提:

              a,必须是两个或者两个以上的线程。

              b,必须保证多个线程使用的是同一个锁。

              注意:如果你发现多线程存在安全问题,而且加上同步后,安全问题没有解决,

                     那么要查看一下这两个前提是否符合。

       B: 好处:

              解决了线程安全问题。

       C: 弊端:

              a,会降低性能,因为每次都要判断锁。

              b,同步出现,有可能出现死锁。

       D: 同步函数和同步代码块具体体现区别:

              同步函数使用的锁是this。

              同步代码块使用的锁可以是任意对象。

              特殊:静态同步函数使用的锁是 该函数所属类对象字节码文件对象。 类名.class

E:单例设计模式的懒汉式(延迟加载方式)

class Single{

       int num = 100;

       private static Single s = null;

       private Single (){}

       public static Single getInstance(){

              if(s==null){

                     synchronized(Single.class){

                            if(s==null){

                                   s = newSingle();

                            }

                     }

              }

              return s;

       }

}

七:sleep和wait有什么区别?

对时间的指定。

1,sleep方法必须指定时间。

2,wait方法有重载形式,可以指定时间,也可以不指定时间。

对于执行权和锁的操作.

1,sleep():释放执行权,不释放锁,因为肯定能醒,肯定可以恢复到临时阻塞状态。

2,wait():释放执行权,释放锁,因为wait不释放锁,如果没有时间指定,那么其他线程都进行不了同步中,无法将其唤醒。

记住:同步中可以有多个存活的线程,但是只能有一个执行同步的代码。因为只有一个线程会持有同步的锁。

只有当该线程释放了锁,其他线程才会有机会获取到锁,而且只能用一个线程获取到锁,继续执行。

publicsynchronized void show(){

       if()

              wait();

       code....;

       notify();

       code....;

}

class  {

       public static void main(String[] args) {

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

       }

}

八:多线程间的通讯:

A:等待/唤醒机制

1,同步。

2,wait,notify,notifyAll方法。

wait,notify,notifyAll这样的方法都属于监视器方法。

监视器你可以理解成就是那个锁。

这些方法用于操作持有该锁的线程。

一个锁对应一组监视器方法。

这些方法必须定义在同步中,并明确所在同步的锁。

简单说,这些方法必须要被锁对象调用。

如果只有一个生产者,一个消费者的,

那么可以通过 if判断标记,同时使用wait notify方法来完成等待唤醒。

当有多个生产者和消费者时,

如果通过这种方式,会出现,数据错误。 比如:一个生产者生产了两次,而只被消费一次。

问题产生的原因:本方被本方唤醒后,没有判断标记,也不清楚本方是否有执行,就进行了一次执行;会导致之前的执行的有可能无效。

解决:必须让每次被唤醒的线程都判断一次标记。所以通过while循环来判断标记。

当循环判断标记后:发现,程序居然死锁了(程序没结束当无法继续执行。)

问题原因: 还是本方唤醒本方造成的,被唤醒的本方判断完标记后,有可能继续等待。导致了所有线程都等待。

解决:为了本方唤醒对象,而又没有直接方法完成,所以就使用过了notifyAll,将所有等待线程唤醒,

如果本方线程被唤醒,继续等,但是对方也被唤醒了,这就有了执行的机会。

所以多个生产者和消费者的解决方案就是 循环判断标记while,和notifyAll。

B:到了JDK1.5的时候,有了对锁和监视器的升级对象:

在java.util.concurrent.locks包中;提供了两个对象 Lock Condition.

1.Lock接口替换了synchronized

同步函数或者同步代码块,对锁的操作是隐式的,并不直观。

而Lock将锁封装成了一个单独的对象,该对象具备获取锁和释放锁的方法。

也就是对锁的操作是显示。

Lock

       lock():获取锁。

       unlock():释放锁。

注意:锁本身也是一种资源。释放锁动作必须要执行。所以一般定义在finally代码块中。

2.Condition:以前监视器方法封装到了Object对象中,任意锁对象都可以使用。

              现在将监视器方法封装到了Condition对象中。而Condition是通过Lock对象来获取的;Lock对象可以绑定Condition。

       监视器方法:

              await();

              signal():

              signalAll():

这样就可以把原来的代码都用新对象来表示;而且新对象提供了一个对多生产者消费者的解决方案。

Lock对象上,可以绑定多组监视器对象;就可以实现本方只唤醒对象的操作。

publicsynchronized void set(){

       while(b)

              wait();

       code....;

       b = true;

       notifyAll();

}

publicsynchronized void out(){

       while(!b)

              wait();

       code....;

       b = false;

       notifyAll();

}

以上代码用jdk1.5版本的改写如下:

 

//明确锁对象,而且明确监视器对象。和以前不同的是,

//Lock对象可以有多组监视器。

Lock lock = newReentrantLock();

//生产者的监视器。

Condition con1 =lock.newCondition();

//消费者的监视器。

Condition con2 =lock.newCondition();

Thread-0   Thread-1

public void set(){

       lock.lock();

       try{

              while(b)

                     con1.await();

              code....;

              b = true;

              con2.signal();

       }

       finally{

              lock.unlock();

       }

}

Thread-2  Thread-3

public void out(){

       lock.lock();

       try  {

              while(!b)

                     con2.await();

              code....;

              b = false;

              con1.signal();

       }

       finally{

              lock.unlock();

       }

}

sleep和wait的区别:

sleep:释放执行权,不释放锁。

wait:释放执行权,释放锁。

九:多线程中的其他线程:

A.停止线程:

       1,定义标记。控制住run中的循环。

       2,如果线程冻结,无法执行标记,强制将其恢复到运行状态,interrupt():

              该动作会放生异常。

B.守护线程:

       setDaemon(boolean):当前台线程运行都结束了,后台线程无论是否执行完代码都会自动结束。

C.加入线程:

       join():A线程执行到B线程的join方法是,A线程会释放执行权,等到B线程执行结束后,A在执行;B线程执行时,A线程处于冻结状态。

 

------- android培训java培训、期待与您交流! ----------  详细请查看:http://edu.csdn.net/heima/