黑马程序员 多线程

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

      多线程

class ThreadDemo {

 public static void main(String[] args) {
  Demo d = new Demo();
  d.start();
  
  for(int i=1;i<1000;i++)
  {
   System.out.println("哈哈。。。。");
  } 
  
 
         }
 }
class Demo extends Thread
{
 public void run()
 {
 for(int i=1;i<100;i++)
  {
   System.out.println("嘿嘿嘿嘿。。。。。。。。。。");
  }  
 }
}
   这是创建多线程的第一种方式:
   继承Thread类。
   步骤:
   1,定义一个Thread类的子类。
   2,复写Thread类中的run方法。将需要线程运行的代码存储到该run方法中。
 run方法的唯一功能是:存储线程要运行的代码。
   3,创建Thread类的子类对象。创建线程。
   4,调用Thread类中的start方法开启线程并运行子类的run方法。
   注意:开启线程时,是为了让线程去执行我们执行的代码,所以Thread类是给我们提供了一个线程要运行的代码存放的位置,但是要运行什么代码Thread类并不知道,所以我们需要将运行的代码存储到run方法里面。所以我们我们就沿袭父类中的方法,定义子类的特有方法即可,然后再调用覆盖父类的方法就好了。

   在运行结果中我们可以看到:
   原来每一个新建的线程都有自己的名称。
   这个名称是通过  "Thread-编号" 形式体现的。
   编号是从0开始的。
   而且可以通过Thread类的getName方法获取到当前的线程对象的名称(Thread.currentThread().getName())。
   主线程的名称就是:  main
   通过Thread.currentThread():获取到当前线程对象。
   线程创建的第二种方式:
   实现Runnable接口。
   步骤:
   1,定义一个Runnable接口的子类。
   2,覆盖Runnable接口的run方法。
   3,通过Thread类创建线程对象。
   4,将实现Runnable接口的子类对象作为参数传递给Thread类的构造函数。
      为什么要这么做呢?因为要告诉线程对象要运行哪个run方法。现在线程要执行的代码都存储到了Runnable接口的子类对象中。那么线程要运行这个run方法。必须要明确这个run方法所属的对象。所以将该对象传递给Thread构造函数。这样线程对象就可以去执行指定的run方法。 
   5,通过线程对象的start开启线程并执行。

 

   这种方式的好处:避免了单继承的局限性。

   所以通常情况下,创建线程,都使用实现Runnable接口的方式。

 


class Demo implements Runnable
{
 public void run()
 {
  for(int x=0; x<100; x++)
  {
   System.out.println(Thread.currentThread().getName()+"....qqqqqq....."+x);
  }
 }
}

 

class  ThreadDemo3
{
 public static void main(String[] args)
 {
  Demo d = new Demo();//创建一个对象,为了线面的线程实现资源共享

  Thread t1 = new Thread(d);//创建线程对象,传入线程对象
  Thread t2 = new Thread(d);//创建线程对象,传入线程对象 
  //开启两个线程
  t1.start();
  t2.start();

  /*
  创建了两个线程对象,都用start开启了,但是没有结果。
  那是因为,start方法开启线程,并执行了线程的run方法。

  */

 }
}

/*
//原始的Thread类
class Thread
{
 private Runnable target;

 Thread(Runnable target)
 {
  this.target = target;
 }

 public void run()
 {
  if(target!=null)
  target.run();
 }
}
*/
   演示:线程的安全问题。

   通过分析该程序,发现票有可能会出现错误的情况。

   我们用sleep模拟了一下这个情况,可以验证该情况是存在的。

   分析安全问题的原因:
   四个线程在同时处理同一个资源票。
   而且有两条语句在操作这个票数据。
   当A线程对操作语句中的一部分执行完,还没有执行剩下的操作语句
   就被B线程开始执行。如果B线程将数据改变。那么A线程在在执行时,
   就会出现数据的错误。

   简单说:多条操作共享数据的代码被多个线程分开执行导致的。
   解决线程安全隐患的解决方案:
   就是 同步代码块。

   格式:
   synchronized(对象)
   {
 需要被同步的代码;
   }
   同步代码块的对象可以是任意的对象。

   同步的原理:
   其实就是使用锁机制。
   将多条操作共享资源的代码进行同步的封装,并加了锁。
   只有获取到锁线程才可以进入到同步中,这时其他线程即使获取到执行权。
   因为没有了锁,它们都进不来。这样就保证了数据的安全。解决了线程安全问题。

   参考火车上的卫生间。
   注意:什么代码需要定义到同步中呢?
   只有操作了共享数据的多条代码才放到同步中。

   同步的好处:解决了线程安全问题。
 
   同步的弊端:对资源是一种耗费,相对降低效率。

   同步的前提:
   1,必须是两个或者两个以上的线程才需要同步。
   2,多个线程必须使用同一个锁,才可以成为这些线程被同步了。
   解决思想:在一个时间段,对于多条执行共享资源的语句,必须由一个线程执行完。
   在执行过程中,其他线程不可以参与。
   总结:
   造成安全隐患的因素:

   1,多个线程在操作共享资源。
   2,有多条操作共享资源的代码。

 

 


*/


class Ticket implements Runnable
{
 private  int ticket = 100;

 public void run()
 {
  while(true)
  {
   if(ticket>0)
   {
    try
    {
     Thread.sleep(10);
                                 }
    catch(Exception e){}
    System.out.println(Thread.currentThread().getName()+"...sale .......:"+ticket--);
   }
  }
 }
}

 


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();

 }
}

   同步函数和同步代码块的区别?

   同步函数书写较为简单。
   同步代码块书写需要多一层缩进。
   同步函数使用的锁是固定的 this。
   同步代码块使用的锁可以是任意的。
   在静态同步函数中的同步使用的锁是 类名.class
   开发时,建议使用同步代码块。
                                                             多线程在单例设计中的应用
   


饿汉式:

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


懒汉式:

class Single
{
 private static Single s = null;
 private Single(){}


 懒汉式:如果被多线程并发访问。会出现安全问题。不一定保证对象的唯一性。
 所以加入了同步机制。但是会减低效率。
 为了解决这个问题。
 通过对s的双重判断搞定这个问题。减少判断锁的次数。
 但是较饿汉式而言,代码相对复杂

 所以建议单例使用饿汉式。

 public static Single getInstance()
 {
  if(s==null)
  {
   synchronized(Single.class)
   {
    if(s==null)
    {
     s = new Single();
    }
   }
  }
  return s;
 }

 
 public static synchronized Single getInstance2()
 {

  if(s==null)
  {
   s = new Single();
  }
  return s;
 }
}

class  SingleDemo
{
 public static void main(String[] args)
 {
  System.out.println("Hello World!");
 }
}
  
  wait() 和 sleep() 的区别:

  通过 执行权和锁这两个方面来分析区别。

  sleep:释放执行权。不释放锁。同时自己定好时间自己醒来。
  wait:释放执行权,释放锁。需要被别人叫醒。
/*
在jdk1.5以后。
java提供了更新的锁操作。

在java.util.concurent.locks包中提供了,新的对象 Lock接口。
这个对象的产生替换了synchronized同步。

好处在于:
synchronized对锁的操作是隐式。获取锁和释放锁,都是隐式的。
只要线程执行到同步语句就获取到锁,执行完同步语句,就释放锁。
用起来呢并不明显。

而Lock的出现,是按照面向对象的思想将锁操作封装成 对象。
并提供了对锁显示操作。lock():获取锁。unlock():释放锁。

操作监视器的方法wait notify notifyAll。都是需要关联自己所属的同步锁的。

有了新的锁对象以后,应该去找和这个新的锁对象关联的操作监视器的方法。

JDK1.5以后,在有了一个java.util.concurent.locks包中提供了一个Condition对象。
它用来替代Object。并提供了替代wait notify noitfyAll的方法。
wait-->await();
notify-->signal();
notifyAll-->signalAll();

并将这些操作监视器的方法封装到了Condition对象中。

如何让这个操作监视器的新对象和新的锁对象关联呢?


替换完代码以后功能和老代码是一样。
仅仅是用新的对象替换了老的程序部分。
但是signalAll出现还是一样会唤醒本方。效率一样低。

Lock的出现好处就是一个Lock对象上可以绑定多个监视器对象。

而以前同步的监视器方法只能绑定到一个锁上。

通过两组监视器,就可以完成生产者只唤醒消费。
消费者只唤醒生产者。

而不会出现本方唤醒本方的情况。

 


*/

import java.util.concurrent.locks.*;
class Resource
{
 private String name;
 private int count;
 private boolean flag;
 //创建一个锁对象。
 private Lock lock = new ReentrantLock();//
 //创建一个和该锁对象关联的监视器方法所属的Condition对象。
 //private Condition con = lock.newCondition();


 //在一个锁上提供两个监视器对象。一个负责生产者,一个负责消费。

 private Condition proCon = lock.newCondition();
 private Condition cusCon = lock.newCondition();
 public  void set(String name)
 {
  lock.lock();
  try
  {
 
   while(flag)
    //try{this.wait();}catch(Exception e){}。
    try{proCon.await();}catch(Exception e){}//让生产者等待。
   this.name = name+"-----"+count;
   count++;

   System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);


   flag = true;
   //this.notifyAll();
   //con.signalAll();
   cusCon.signal();//让生产者线程去唤醒消费者监视器上的线程。
  }
  finally
  {
   lock.unlock();//锁是一个资源。必须要释放。定义在finally中。
  }
 }
 public  void out()
 {
  lock.lock();
  try
  {
  
   while(!flag)
   // try{this.wait();}catch(Exception e){}
    try{cusCon.await();}catch(Exception e){}
   System.out.println(Thread.currentThread().getName()+"...........消费者......"+this.name);
   flag = false;
   //this.notifyAll();
   //con.signalAll();
   proCon.signal();
  }
  finally
  {
   lock.unlock();
  }
 }
}


class Producter implements Runnable
{
 private Resource r;
 Producter(Resource r)
 {
  this.r = r;
 }

 public void run()
 {
  while(true)
  {
   r.set("产品");
  }
 }
}

 

class Customer implements Runnable
{
 private Resource r;
 Customer(Resource r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.out();
  }
 }
}

 

class  ProCusDemo3
{
 public static void main(String[] args)
 {
  Resource r = new Resource();
  Producter pro = new Producter(r);
  Customer  cus = new Customer(r);

  //t1 t2 是生产者线程。

  Thread t1 = new Thread(pro);
  Thread t2 = new Thread(pro);

  //t3,t4是消费者线程。
  Thread t3 = new Thread(cus);
  Thread t4 = new Thread(cus);

  t1.start();
  t2.start();
  t3.start();
  t4.start();
 }
}


      多线程

class ThreadDemo {

 public static void main(String[] args) {
  Demo d = new Demo();
  d.start();
  
  for(int i=1;i<1000;i++)
  {
   System.out.println("哈哈。。。。");
  } 
  
 
         }
 }
class Demo extends Thread
{
 public void run()
 {
 for(int i=1;i<100;i++)
  {
   System.out.println("嘿嘿嘿嘿。。。。。。。。。。");
  }  
 }
}
   这是创建多线程的第一种方式:
   继承Thread类。
   步骤:
   1,定义一个Thread类的子类。
   2,复写Thread类中的run方法。将需要线程运行的代码存储到该run方法中。
 run方法的唯一功能是:存储线程要运行的代码。
   3,创建Thread类的子类对象。创建线程。
   4,调用Thread类中的start方法开启线程并运行子类的run方法。
   注意:开启线程时,是为了让线程去执行我们执行的代码,所以Thread类是给我们提供了一个线程要运行的代码存放的位置,但是要运行什么代码Thread类并不知道,所以我们需要将运行的代码存储到run方法里面。所以我们我们就沿袭父类中的方法,定义子类的特有方法即可,然后再调用覆盖父类的方法就好了。

   在运行结果中我们可以看到:
   原来每一个新建的线程都有自己的名称。
   这个名称是通过  "Thread-编号" 形式体现的。
   编号是从0开始的。
   而且可以通过Thread类的getName方法获取到当前的线程对象的名称(Thread.currentThread().getName())。
   主线程的名称就是:  main
   通过Thread.currentThread():获取到当前线程对象。
   线程创建的第二种方式:
   实现Runnable接口。
   步骤:
   1,定义一个Runnable接口的子类。
   2,覆盖Runnable接口的run方法。
   3,通过Thread类创建线程对象。
   4,将实现Runnable接口的子类对象作为参数传递给Thread类的构造函数。
      为什么要这么做呢?因为要告诉线程对象要运行哪个run方法。现在线程要执行的代码都存储到了Runnable接口的子类对象中。那么线程要运行这个run方法。必须要明确这个run方法所属的对象。所以将该对象传递给Thread构造函数。这样线程对象就可以去执行指定的run方法。 
   5,通过线程对象的start开启线程并执行。

 

   这种方式的好处:避免了单继承的局限性。

   所以通常情况下,创建线程,都使用实现Runnable接口的方式。

 


class Demo implements Runnable
{
 public void run()
 {
  for(int x=0; x<100; x++)
  {
   System.out.println(Thread.currentThread().getName()+"....qqqqqq....."+x);
  }
 }
}

 

class  ThreadDemo3
{
 public static void main(String[] args)
 {
  Demo d = new Demo();//创建一个对象,为了线面的线程实现资源共享

  Thread t1 = new Thread(d);//创建线程对象,传入线程对象
  Thread t2 = new Thread(d);//创建线程对象,传入线程对象 
  //开启两个线程
  t1.start();
  t2.start();

  /*
  创建了两个线程对象,都用start开启了,但是没有结果。
  那是因为,start方法开启线程,并执行了线程的run方法。

  */

 }
}

/*
//原始的Thread类
class Thread
{
 private Runnable target;

 Thread(Runnable target)
 {
  this.target = target;
 }

 public void run()
 {
  if(target!=null)
  target.run();
 }
}
*/
   演示:线程的安全问题。

   通过分析该程序,发现票有可能会出现错误的情况。

   我们用sleep模拟了一下这个情况,可以验证该情况是存在的。

   分析安全问题的原因:
   四个线程在同时处理同一个资源票。
   而且有两条语句在操作这个票数据。
   当A线程对操作语句中的一部分执行完,还没有执行剩下的操作语句
   就被B线程开始执行。如果B线程将数据改变。那么A线程在在执行时,
   就会出现数据的错误。

   简单说:多条操作共享数据的代码被多个线程分开执行导致的。
   解决线程安全隐患的解决方案:
   就是 同步代码块。

   格式:
   synchronized(对象)
   {
 需要被同步的代码;
   }
   同步代码块的对象可以是任意的对象。

   同步的原理:
   其实就是使用锁机制。
   将多条操作共享资源的代码进行同步的封装,并加了锁。
   只有获取到锁线程才可以进入到同步中,这时其他线程即使获取到执行权。
   因为没有了锁,它们都进不来。这样就保证了数据的安全。解决了线程安全问题。

   参考火车上的卫生间。
   注意:什么代码需要定义到同步中呢?
   只有操作了共享数据的多条代码才放到同步中。

   同步的好处:解决了线程安全问题。
 
   同步的弊端:对资源是一种耗费,相对降低效率。

   同步的前提:
   1,必须是两个或者两个以上的线程才需要同步。
   2,多个线程必须使用同一个锁,才可以成为这些线程被同步了。
   解决思想:在一个时间段,对于多条执行共享资源的语句,必须由一个线程执行完。
   在执行过程中,其他线程不可以参与。
   总结:
   造成安全隐患的因素:

   1,多个线程在操作共享资源。
   2,有多条操作共享资源的代码。

 

 


*/


class Ticket implements Runnable
{
 private  int ticket = 100;

 public void run()
 {
  while(true)
  {
   if(ticket>0)
   {
    try
    {
     Thread.sleep(10);
                                 }
    catch(Exception e){}
    System.out.println(Thread.currentThread().getName()+"...sale .......:"+ticket--);
   }
  }
 }
}

 


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();

 }
}

   同步函数和同步代码块的区别?

   同步函数书写较为简单。
   同步代码块书写需要多一层缩进。
   同步函数使用的锁是固定的 this。
   同步代码块使用的锁可以是任意的。
   在静态同步函数中的同步使用的锁是 类名.class
   开发时,建议使用同步代码块。
                                                             多线程在单例设计中的应用
   


饿汉式:

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


懒汉式:

class Single
{
 private static Single s = null;
 private Single(){}


 懒汉式:如果被多线程并发访问。会出现安全问题。不一定保证对象的唯一性。
 所以加入了同步机制。但是会减低效率。
 为了解决这个问题。
 通过对s的双重判断搞定这个问题。减少判断锁的次数。
 但是较饿汉式而言,代码相对复杂

 所以建议单例使用饿汉式。

 public static Single getInstance()
 {
  if(s==null)
  {
   synchronized(Single.class)
   {
    if(s==null)
    {
     s = new Single();
    }
   }
  }
  return s;
 }

 
 public static synchronized Single getInstance2()
 {

  if(s==null)
  {
   s = new Single();
  }
  return s;
 }
}

class  SingleDemo
{
 public static void main(String[] args)
 {
  System.out.println("Hello World!");
 }
}
  
  wait() 和 sleep() 的区别:

  通过 执行权和锁这两个方面来分析区别。

  sleep:释放执行权。不释放锁。同时自己定好时间自己醒来。
  wait:释放执行权,释放锁。需要被别人叫醒。
/*
在jdk1.5以后。
java提供了更新的锁操作。

在java.util.concurent.locks包中提供了,新的对象 Lock接口。
这个对象的产生替换了synchronized同步。

好处在于:
synchronized对锁的操作是隐式。获取锁和释放锁,都是隐式的。
只要线程执行到同步语句就获取到锁,执行完同步语句,就释放锁。
用起来呢并不明显。

而Lock的出现,是按照面向对象的思想将锁操作封装成 对象。
并提供了对锁显示操作。lock():获取锁。unlock():释放锁。

操作监视器的方法wait notify notifyAll。都是需要关联自己所属的同步锁的。

有了新的锁对象以后,应该去找和这个新的锁对象关联的操作监视器的方法。

JDK1.5以后,在有了一个java.util.concurent.locks包中提供了一个Condition对象。
它用来替代Object。并提供了替代wait notify noitfyAll的方法。
wait-->await();
notify-->signal();
notifyAll-->signalAll();

并将这些操作监视器的方法封装到了Condition对象中。

如何让这个操作监视器的新对象和新的锁对象关联呢?


替换完代码以后功能和老代码是一样。
仅仅是用新的对象替换了老的程序部分。
但是signalAll出现还是一样会唤醒本方。效率一样低。

Lock的出现好处就是一个Lock对象上可以绑定多个监视器对象。

而以前同步的监视器方法只能绑定到一个锁上。

通过两组监视器,就可以完成生产者只唤醒消费。
消费者只唤醒生产者。

而不会出现本方唤醒本方的情况。

 


*/

import java.util.concurrent.locks.*;
class Resource
{
 private String name;
 private int count;
 private boolean flag;
 //创建一个锁对象。
 private Lock lock = new ReentrantLock();//
 //创建一个和该锁对象关联的监视器方法所属的Condition对象。
 //private Condition con = lock.newCondition();


 //在一个锁上提供两个监视器对象。一个负责生产者,一个负责消费。

 private Condition proCon = lock.newCondition();
 private Condition cusCon = lock.newCondition();
 public  void set(String name)
 {
  lock.lock();
  try
  {
 
   while(flag)
    //try{this.wait();}catch(Exception e){}。
    try{proCon.await();}catch(Exception e){}//让生产者等待。
   this.name = name+"-----"+count;
   count++;

   System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);


   flag = true;
   //this.notifyAll();
   //con.signalAll();
   cusCon.signal();//让生产者线程去唤醒消费者监视器上的线程。
  }
  finally
  {
   lock.unlock();//锁是一个资源。必须要释放。定义在finally中。
  }
 }
 public  void out()
 {
  lock.lock();
  try
  {
  
   while(!flag)
    //try{this.wait();}catch(Exception e){}
    try{cusCon.await();}catch(Exception e){}
   System.out.println(Thread.currentThread().getName()+"...........消费者......"+this.name);
   flag = false;
   //this.notifyAll();
   //con.signalAll();
   proCon.signal();
  }
  finally
  {
   lock.unlock();
  }
 }
}


class Producter implements Runnable
{
 private Resource r;
 Producter(Resource r)
 {
  this.r = r;
 }

 public void run()
 {
  while(true)
  {
   r.set("产品");
  }
 }
}

 

class Customer implements Runnable
{
 private Resource r;
 Customer(Resource r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.out();
  }
 }
}

 

class  ProCusDemo3
{
 public static void main(String[] args)
 {
  Resource r = new Resource();
  Producter pro = new Producter(r);
  Customer  cus = new Customer(r);

  //t1 t2 是生产者线程。

  Thread t1 = new Thread(pro);
  Thread t2 = new Thread(pro);

  //t3,t4是消费者线程。
  Thread t3 = new Thread(cus);
  Thread t4 = new Thread(cus);

  t1.start();
  t2.start();
  t3.start();
  t4.start();
 }
}

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


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值