您的位置:首页 > 职场人生

黑马程序员——Java基础---多线程

2015-04-23 22:23 197 查看
进程正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。

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

 

一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。

jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

随机性的原理因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。也就是这个原因,引出了后面同步的概念!

           下面的代码是我学习过程中自己手写的,两次运行结果不一样,说明了cpu快速切换,每个线程获得执行权是不一样的!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

/*    

创建两个线程,和主线程交替运行。说明:几个线程共同抢夺CPU资源,

每次运行结果不一样,说明线程的一个特点:随机性,谁抢到谁运行,至于运行多长,

CPU说了算,演示代码如下:

*/

class ThreadDemo extends Thread

{

  ThreadDemo(String name)

  {

    super(name);

 

  }
public void run()                       //重写run方法
 {
  for(int x=1;x<60;x++)
  {
    System.out.println(this.getName()+"Thread在运行"+x+"次");

  }

 }

}

class ThreadTest

{

   public static void main(String[] args)

   {

    ThreadDemo d1 = new ThreadDemo("1号");//创建1号线程

    ThreadDemo d2 = new ThreadDemo("2号");//创建2号线程

    d1.start();                           //启动1号线程

    d2.start();                           //启动2号线程

    for(int x=1;x<60;x++)               //主线程运行CODE

    {

      System.out.println("主线程在运行"+x+"次");

    }

   

   }

}

/*

部分运行结果:

第一次运行结果:

G:\代码>javac ThreadTest.java

G:\代码>java ThreadTest

主线程在运行1次

2号Thread在运行1次       

1号Thread在运行2次

主线程在运行1次

1号Thread在运行3次

1号Thread在运行4次

1号Thread在运行5次

1号Thread在运行6次

1号Thread在运行7次

1号Thread在运行8次

主线程在运行2次

主线程在运行3次

主线程在运行4次

主线程在运行5次

主线程在运行6次

主线程在运行7次

主线程在运行8次

主线程在运行9次

主线程在运行10次

主线程在运行11次

主线程在运行12次

主线程在运行13次

1号Thread在运行1次

1号Thread在运行2次

1号Thread在运行3次

1号Thread在运行4次

1号Thread在运行5次

1号Thread在运行6次

1号Thread在运行7次

1号Thread在运行8次

1号Thread在运行9次

1号Thread在运行10次

1号Thread在运行11次

1号Thread在运行12次

1号Thread在运行13次

1号Thread在运行14次

1号Thread在运行15次

1号Thread在运行16次

1号Thread在运行17次

1号Thread在运行18次

第二次运行结果:

1号Thread在运行1次

1号Thread在运行2次

主线程在运行1次

1号Thread在运行3次

1号Thread在运行4次

1号Thread在运行5次

1号Thread在运行6次

1号Thread在运行7次

1号Thread在运行8次

主线程在运行2次

主线程在运行3次

主线程在运行4次

主线程在运行5次

主线程在运行6次

主线程在运行7次

主线程在运行8次

主线程在运行9次

主线程在运行10次

主线程在运行11次

主线程在运行12次

主线程在运行13次
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

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

步骤:

1,定义类继承Thread类;

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

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

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

 

线程状态:

被创建:start()

运行:具备执行资格,同时具备执行权;

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

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

消亡:stop()

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

步骤:

1,定义类实现Runnable接口。

2,覆盖接口中的run方法(用于封装线程要运行的代码)。

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

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

为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。

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

多线程安全问题的原因:

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

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

/*简单的卖票的例子*/

class Ticket extends Thread

{ private static int ticket = 60;

public void run()
{
 while(true)
 { 
  if(ticket>0)
  {
  try
  {
  Thread.sleep(10);  //前面的线程休息10毫秒,这时别的线程进来了,从而可能造成了负票的出现
     }
     catch(Exception e)
     {
      System.out.println(e.getMessage());
     }
  System.out.println(this.getName()+"正在卖"+ticket-- +"号票");

 
  }
 
 
 }

}

}

c
cb55
lass ThreadTest2

{
public static void main(String[] args)
{
Ticket t1 = new Ticket ();  //创建4个线程
Ticket t2 = new Ticket ();
Ticket t3 = new Ticket ();
Ticket t4 = new Ticket ();
t1.start();                         //分别启动4个线程
t2.start();
t3.start();
t4.start();

}

}

/*---------------------------------------------

运行结果如下:

G:\代码>javac ThreadTest2.java

G:\代码>java ThreadTest2

Thread-0正在卖59号票

Thread-2正在卖57号票

Thread-1正在卖58号票

Thread-3正在卖60号票

Thread-0正在卖56号票

Thread-2正在卖55号票

Thread-3正在卖53号票

Thread-1正在卖54号票

Thread-0正在卖52号票

Thread-3正在卖51号票

Thread-2正在卖49号票

Thread-1正在卖50号票

Thread-0正在卖48号票

Thread-3正在卖47号票

Thread-1正在卖46号票

Thread-2正在卖45号票

Thread-0正在卖44号票

Thread-3正在卖43号票

Thread-1正在卖42号票

Thread-2正在卖41号票

Thread-0正在卖40号票

Thread-3正在卖39号票

Thread-2正在卖38号票

Thread-1正在卖38号票

Thread-0正在卖37号票

Thread-3正在卖36号票

Thread-2正在卖35号票

Thread-1正在卖34号票

Thread-0正在卖33号票

Thread-3正在卖32号票

Thread-1正在卖31号票

Thread-2正在卖31号票

Thread-0正在卖30号票

Thread-3正在卖29号票

Thread-2正在卖28号票

Thread-1正在卖28号票

Thread-0正在卖27号票

Thread-3正在卖26号票

Thread-2正在卖25号票

Thread-1正在卖24号票

Thread-0正在卖23号票

Thread-3正在卖22号票

Thread-2正在卖21号票

Thread-1正在卖20号票

Thread-0正在卖19号票

Thread-3正在卖18号票

Thread-2正在卖17号票

Thread-1正在卖16号票

Thread-0正在卖15号票

Thread-3正在卖14号票

Thread-2正在卖13号票

Thread-1正在卖12号票

Thread-0正在卖11号票

Thread-3正在卖10号票

Thread-2正在卖9号票

Thread-1正在卖8号票

Thread-0正在卖7号票

Thread-3正在卖6号票

Thread-1正在卖5号票

Thread-2正在卖4号票

Thread-0正在卖3号票

Thread-3正在卖2号票

Thread-1正在卖1号票

Thread-2正在卖1号票

Thread-0正在卖0号票

Thread-3正在卖-1号票

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

如何进行多句操作共享数据代码的封装呢?java中提供了一个解决方式:同步代码块

格式:

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

{  

需要被同步的代码;

}

定义同步是有前提的:

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

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

 

同步的第二种表现形式:

同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。

         函数都有自所属的对象this,所以同步函数所使用的锁就是this锁。静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是
类名.class

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

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

同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

 自己手打死锁程序代码如下:

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

class Test implements 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()+"aaa");

  synchronized(MyLock.lockb)
   {
  System.out.println(Thread.currentThread().getName()+"bbb");

   }
 }
}

}
else

{
while(true)

   {
synchronized(MyLock.lockb)
{
System.out.println(Thread.currentThread().getName()+"ccc");

  synchronized(MyLock.locka)
   {
  System.out.println(Thread.currentThread().getName()+"ddd");

 
   }

   

     }

   

  }

}

}

}

class MyLock

{

 static Object locka = new Object (); //创建两个锁a

 static Object lockb = new Object (); //创建两个锁b

}

class DeadLockTestlt

{

  public static void main(String[] args)

  

  {

    Thread t1 = new Thread(new Test(true) );

    Thread t2 = new Thread(new Test(false) );

    t1.start();

    t2.start();

  }

}

/*-------------------------------------------------------

G:\代码>javac DeadLockTestlt.java

G:\代码>java DeadLockTestlt

Thread-1ccc

Thread-0aaa

G:\代码>

-------------------------------------------------------*/

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

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

1:将资源封装成对象。

2:将线程执行的任务(任务其实就是run方法。)也封装成对象。

等待唤醒机制:

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

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

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

wait和sleep区别: 

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但不是不释放锁。

 

线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。

停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。

怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

第一种方式:定义循环的结束标记。

第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

Lock接口:多线程在JDK1.5版本升级时,推出一个接口。

解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。

 

到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。

在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。

在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

 

而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法
await()、signal()、signalAll()体现新版本对象的好处。

< java.util.concurrent.locks >Condition接口:await()、signal()、signalAll();

--------------------------------------------------------

class BoundedBuffer {

   final Lock lock = new ReentrantLock();

   final Condition notFull  = lock.newCondition();

   final Condition notEmpty = lock.newCondition();

   final Object[] items = new Object[100];

   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {

     lock.lock();

     try {

       while (count == items.length)

         notFull.await();

       items[putptr] = x;

       if (++putptr == items.length) putptr = 0;

       ++count;

       notEmpty.signal();

     }

finally {

       lock.unlock();

     }

   }

   public Object take() throws InterruptedException {

     lock.lock();

     try {

       while (count == 0)

         notEmpty.await();

       Object x = items[takeptr];

       if (++takeptr == items.length) takeptr = 0;

       --count;

       notFull.signal();

       return x;

     }

finally {

       lock.unlock();

     }

   } 

 }

------------------------------------------------------------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: