黑马程序员 多线程

来源:互联网 发布:麦兜 知乎 编辑:程序博客网 时间:2024/06/07 22:18
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

1.  进程和线程

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

线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享数据。

多进程:操作系统中同时运行的多个程序;

多线程:在同一个进程中同时运行的多个任务;

一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元。 

并发运行。如:多线程下载软件。 

可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一致。 

因为多线程存在一个特性:随机性。 

可以理解成多个线程在抢cpu资源。 

进程与线程的区别:

       1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。

       2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。

 

2.  线程的四种状态

Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:
     NEW-------新建状态,至今尚未启动的线程处于这种状态。
     RUNNABLE-------运行状态,正在 Java 虚拟机中执行的线程处于这种状态。
     BLOCKED-------阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。
     WAITING-------冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
     TIMED_WAITING-------等待状态,等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
     TERMINATED-------已退出的线程处于这种状态。

 

3.  创建线程的方式一

创建线程的第一种方式:继承Thread ,由子类复写run方法。

步骤:

1,定义类继承Thread类;

2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;

3,通过创建Thread类的子类对象,创建线程对象;

4,调用线程的start方法,开启线程,并执行run方法。

作用:

1.启动线程

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

运行

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

class demo extends Thread{

  public voidrun(){

  }

}

run方法用于存储线程要运行的代码。

demo demo=new demo();创建对象就创建了一个线程。

run方法和 start方法

run方法  仅仅是对象调用方法,并没有运行线程

start方法 是开启线程并且执行线程中的run方法

 

4.  创建线程的方式二

创建线程的第二种方式:实现一个接口Runnable

步骤:

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类。

实现方式就变面了单继承的局限性。

       Eg

              线程卖票

              //第一种线程创建方式

                     class SellTicketextends Thread{

               private Stringname;

               private int num = 50;

               public SellTicket(String name) {

                   super();

                   this.name = name;

               }

               public void run(){

                   for (int i = 1; i <=num; i++) {

System.out.println(name+"卖出了第"+i+"张票!");

                   }

               }

}

//第二种线程创建方式

class MySell implements Runnable{

               private int num = 50;

               @Override

               public void run() {

                   for (int i = 1; i <=num; i++) {

                              System.out.println(Thread.currentThread().getName()+"卖出了第"+i+"张票!");

                   }

               }

}

public class Demo2 {

public static void main(String[] args)throws Exception {

                   new SellTicket("A").start();

                   new SellTicket("B").start();

                   new SellTicket("C").start();

       

                   new Thread(new MySell(),"D").start();

                   new Thread(new MySell(),"E").start();

                   new Thread(new MySell(),"F").start();

       

                   for (int i = 10; i > 0; i--) {

                        System.out.println(i);

                       Thread.sleep(1000);

                   }

               }

}

 

5.  控制线程

join方法:调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以运行。

有人也把这种方式成为联合线程

join方法的重载方法:

join(longmillis):

join(longmillis,int nanos):

通常很少使用第三个方法:

程序无须精确到一纳秒;

计算机硬件和操作系统也无法精确到一纳秒;

 

Eg:

class MyThreadDemo implements Runnable{

@Override

public void run() {

   for (int i = 0; i < 50; i++) {

       System.out.println(Thread.currentThread().getName()+"正在运行!"+i);

       if(i == 25){

         try {

new Thread(new MyThreadDemo(),"刘昭").join();

         } catch (InterruptedException e) {

            e.printStackTrace();

         }

      }

   }

}

}

public class DemoRe10 {

public static void main(String[] args) {

   new Thread(new MyThreadDemo(),"刘昭").start();

   new Thread(new MyThreadDemo(),"章泽天").start();

}

}

Daemon后台线程

处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。JVM的垃圾回收就是典型的后台线程。

特点:若所有的前台线程都死亡,后台线程自动死亡。

设置后台线程:Thread对象setDaemon(true);

setDaemon(true)必须在start()调用前。否则出现IllegalThreadStateException异常;

前台线程创建的线程默认是前台线程;

判断是否是后台线程:使用Thread对象的isDaemon()方法;

并且当且仅当创建线程是后台线程时,新线程才是后台线程。

sleep线程休眠

让执行的线程暂停一段时间,进入阻塞状态。

sleep(longmilllis) throws InterruptedException:毫秒

sleep(longmillis,int nanos) throws       InterruptedException:毫秒,纳秒

调用sleep()后,在指定时间段之内,该线程不会获得执行的机会。

控制线程之优先级

每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。

并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;

默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。

Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。

MAX_PRIORITY   :      值是10

MIN_PRIORITY    :      值是1

NORM_PRIORITY       :      值是5(主方法默认优先级)

yield线程礼让

暂停当前正在执行的线程对象,并执行其他线程;

Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。所以完全有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。

 

6.  线程安全

多线程安全问题的原因

发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。

解决安全问题的原理

只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

如何进行多句操作共享数据代码的封装呢?

java中提供了一个解决方式:就是同步代码块。

格式:

synchronized(对象){  // 任意对象都可以。这个对象就是锁。

需要被同步的代码;

}

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

同步的前提:

1.必须要有两个或者两个以上的线程

2.必须多个线程必须使用同一个锁。

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

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

弊端:消耗了资源,多个线程需要判断锁。

 

7.  同步函数

public synchronized void show()

{

}

如何找问题?

1.明确哪些代码是多线程运行代码

2.明确共享数据

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

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

如果同步函数被静态修饰后,使用的锁是 class

synchronized (对象名.class)

 

8.  死锁

是由于两个线程相互等待对方已被锁定的资源

       循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。

避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,

保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。

 

9.  线程通信

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

       1.是不是两个或两个以上的线程。解决办法 两个线程都要被同步。

       2.是不是同一个锁。解决办法 找同一个对象作为锁。

      3.等待唤醒机制。

wait后,线程就会存在线程池中,notify后就会将线程池中的线程唤醒。

notifyAll();唤醒线程池中所有的线程。

实现方法 :

给资源加个标记 flag  

synchronized(r)

{

 while(r.flag)//多个生产者和消费者   if(r.flag)//一个生产者和消费者

  r.wait();

  代码

   r.flag=true;

  r.notify();

  r.notifyAll();

}

上面三种方法都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义在object类中呢?

因为这些方法在操作同步中线程的是偶,都必须要表示它们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被

同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁,而锁可以是特意对象,可以被任意对象调用的方法定义在Object类中。

 

10.      Lock接口

Lock 替代了synchronized

 Condition 替代了 Object监视器方法

好处:

    将同步synchronized 替换成了 Lock

 将object中的waitnotify notifyAll 替换成了 Condition对象

    该对象可以Lock锁进行获取。一个锁可以对应多个Condition对象

Eg

private Lock lock=new ReentrantLock();

private Condition condition =lock.newCondition();

public void cet(String name ) throws

{

   lock.lock();

   try

   {

     while(flag)

      contidition.await();

      this.name=name+"--"+count++;

   sop(Thread.currentThread().getName()+"...生产者..."+this.name)

       flag=true;

       condition.signalAll();

    }

    finally

    {

      lock.unlock();

        

   }

}

 

总结:

a)      Java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)

b)     多线程存在一个特性:随机性,造成的原因:CPU在瞬间不断切换去处理各个线程而导致的。 

c)       实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

d)      在创建线程时要考虑安全问题,使用同步代码块synchronized(自己能拼写出来)

e)       要注意线程中的死锁

f)       解决死锁可以用等待唤醒机制

g)       使用Lock lock方法后,一定要释放锁


---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
详细请查看:www.itheima.com
0 0
原创粉丝点击