黑马程序员Java基础__多线程

来源:互联网 发布:bt天堂新域名 编辑:程序博客网 时间:2024/04/28 17:38
---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------

 

 

一、多线程

一、线程和进程的区别

进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。

线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。

 

二、自定义线程:

线程有如此的好处,那要如何才能通过代码自定义一个线程呢?其实,线程是通过系统创建和分配的,java是不能独立创建线程的;但是,java是可以通过调用系统,来实现对进程的创建和分配的。java作为一种面向对象的编程语言,是可以将任何事物描述为对象,从而进行操作的,进程也不例外。我们通过查阅API文档,知道java提供了对线程这类事物的描述,即Thread类。创建新执行线程有两种方法

一)创建线程方式一:继承Thread类。

1、步骤:

第一、定义类继承Thread。

第二、复写Thread类中的run方法。

第三、调用线程的start方法。分配并启动该子类的实例。

          start方法的作用:启动线程,并调用run方法。

 

示例1:

class Demo extends Thread {public void run() {for (int i = 0; i < 60; i++)System.out.println(Thread.currentThread().getName() + "demo run---"+ i);}}class Test2 {public static void main(String[] args) {Demo d1 = new Demo();// 创建一个对象就创建好了一个线程Demo d2 = new Demo();d1.start();// 开启线程并执行run方法d2.start();for (int i = 0; i < 60; i++)System.out.println("Hello World!---" + i);}}


二)创建线程方式二:实现Runnable接口

1、步骤:

第一、定义类实现Runnable接口。

第二、覆盖Runnable接口中的run方法。

第三、通过Thread类建立线程对象。要运行几个线程,就创建几个对象。

第四、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

第五、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。

 

示例2:

//多个窗口同时卖票   
class Ticket implements Runnable {private int tic = 20;public void run() {while (true) {if (tic > 0)System.out.println(Thread.currentThread().getName() + "sale:"+ tic--);}}}class TicketDemo {public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);// 创建一个线程Thread t2 = new Thread(t);// 创建一个线程Thread t3 = new Thread(t);// 创建一个线程Thread t4 = new Thread(t);// 创建一个线程t1.start();t2.start();t3.start();t4.start();}}


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

 

四)为什么要覆盖run方法呢?

1)Thread类用于描述线程。该类定义了一个功能:用于存储线程要运行的代码,该存储功能即为run方法。也就是说,Thread类中的run方法用于存储线程要运行的代码,就如同main方法存放的代码一样。

2)复写run的目的:将自定义代码存储在run方法中,让线程运行要执行的代码。直接调用run,就是对象在调用方法。调用start(),开启线程并执行该线程的run方法。如果直接调用run方法,只是将线程创建了,但未运行。

 

三、多线程的安全问题:

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

涉及到两个因素:

1,多个线程在操作共享数据。

2,有多条语句对共享数据进行运算。

原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。

解决安全问题的原理:

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

 

四、关于线程同步问题:

一)同步的好处及弊端:

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

弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

二)定义同步的前提:

1,必须要有两个或者两个以上的线程,才需要同步。

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

 

示例3:

{      private int tic = 100;      Object obj = new Object();      public void run()      {          while(true)          {              synchronized(obj)//任意的一个对象              {                 //此两句为共享语句                   if (tic > 0)                      System.out.println(Thread.currentThread().getName() + "sale:" + tic--);              }             }      }  }    class  TicketDemo  {      public static void main(String[] args)       {          Ticket t = new Ticket();          Thread t1 = new Thread(t,"1");//创建第一个线程           Thread t2 = new Thread(t,"2");//创建第二个线程           //开启线程           t1.start();          t2.start();      }  }  


三)同步代码块和的同步函数的区别

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

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

 

五、等待唤醒机制:

一)涉及的方法:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

 

二)注意事项:

1:这些方法都需要定义在同步中。 

2:因为这些方法必须要标示所属的锁。

你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

 

六、关于多线程单利设计模式:
 懒汉式的特点在于延时加载,但是在多线程访问的时候会出现安全问题。
 解决问题:加同步(同步函数,和同步代码块都行)来解决但是有些低效, 用双重判断的方法解决效率的问题。

示例4:

class Single  {   private Single(){};   private Single s=null;   public static Singtle getInstance()   {    if(s==null)//使用双重判断的形式.    {     synchronized(Single.class)//锁是该类所属的字节码文件对象     {      if(s==null)       s=new Single();     }    }    return s;   }  }  

---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------
原创粉丝点击