JavaSE学习笔记--多线程基础

 

多线程基础

 

多线程:
1,线程与进程
 进程:当前正在执行的程序。代表一个应用程序在内存中的执行区域。
 一个进程中可以有多个线程,线程是CPU调度和分派的基本单位。我们可以理解为线程就是程序运行中的一条路径。
 
一个进程中如果只有一个执行路径,这个程序称为单线程。
一个进程中有多个执行路径时,这个程序成为多线程。

2,举出一个日常生活中多线程的例子。
JVM启动是单线程,还是多线程的呢?

jvm的启动其实就是多线程程序。
其中有一个程序负责从主函数开始执行,并控制程序运行的流程。
同时为了提高效率,还启动了另一个控制单元(执行路径)专门负责堆内存中的垃圾回收。
在程序正常执行过程中,如果出现了垃圾,这是另一个负责收垃圾的线程会在不定时的时间进行垃圾的处理。
这两个程序是同时执行的。

负责执行正常代码的线程,称为主线程。该线程执行的代码都存放于主函数中。
负责收垃圾代码执行的线程,成为垃圾回收线程。该线程要执行的代码在finalize中。

3,两种创建线程的方式
通过API查阅,发现Thread类描述时,有两种创建线程的方式。
方式一:定义一个类继承Thread类,并覆盖Thread类中run方法。

  作为了解掌握:
   为什么要继承Thread,为什么要覆盖run
   其实直接建立Thread类对象即可。并开启线程执行就可以了。
   但是虽然线程执行了,可是执行的代码是该线程默认的代码,该代码就存放在run方法中。

   可是定义线程的目的是为了执行自定义的代码。
   而线程运行代码都存储在run方法中,所以只有覆盖了run方法,才可以运行自定义的内容,
   想要覆盖,必须先要继承。


主线程运行的代码都在main函数中,
自定义线程运行的代码都在run方法中。

直接创建Thread类的子类对象就是创建了一个线程。
在内存中其实:1,堆内存中产生了一个对象,2,需要调用了底层资源,去创建执行路径。

如果直接调用该对象的run方法。
 这时,底层资源并没有完成线程的创建和执行。
 仅仅是是简单的对象调用方法的过程。所以这时,执行控制流程的只有主线程 .


如果想要开启线程,需要去调用Thread类中另一个方法完成。
start方法完成:该方法做了两件事,1,开启线程,2,调用了线程的run方法。

4,线程的名称:
多线程的创建,为了对各个线程进行标识,他们有一个自己默认的名称。
格式:Thread-编号,编号从0开始。


static Thread currentThread():获取当前线程对象。
String getName():获取线程名称。
void  setName():设置 线程的名称。

Thread(String name):构造函数,线程对象一建立就可以指定名称。
例如构造函数的方法:
class Demo extends Thread
{
 private String name;
 Demo(String name)
 {
  super(name);
  //this.name = name;
 }
 public void run()
 {
  for(int x=0; x<10; x++)
  {
   //for(int y=-9999999; y<99999999; y++){}//为了减缓程序的执行。
   //因为Demo是Thread类的子类,所以可以直接使用Thread类中的getName方法。获取当前线程的名字。
   System.out.println(getName()+"....."+name);
  }
 }
}

class  ThreadDemo3
{
 public static void main(String[] args)
 {
  Demo d1 = new Demo("变形金刚");//创建了一个Thread类的子类对象,其就是在创建一个线程。
  Demo d2 = new Demo("哈利波特");
//  d1.setName("小强");
//  d2.setName("旺财");
  d1.start();
  d2.start();

  for(int x=0; x<20; x++)
  {
   //如何获取到主线程对象呢?
//   //通过Thread类中的一个方法。currentThread()返回当前线程对象。该方法是静态的。
   System.out.println(Thread.currentThread().getName()+"---"+x);
  }/**/
  
  //System.out.println("MAIN===="+4/0);

 }
}
5,线程对象的创建(理解)
创建线程的两种方式:
 1,继承Thread类。
 步骤:
  1,继承Thread类
  2,覆盖Thread类的run方法
  3,创建Thread类的子类对象创建线程
  4,调用Thread类中的start方法开启线程,并执行类中的run方法。
 
 特点:当类去描述事物,事物中有属性和行为。
 如果行为中有部分代码需要被多线程执行,同时还在操作属性。就需要该类继承Thread类,
 那么产生该类的对象作为线程对象,可是这样做会导致,每个对象都存储一份属性数据。
 无法在多个线程中共享该数据。加上静态,虽然实现了共享,但是生命周期太长。

 2,实现Runnalbe接口:
 步骤:
 1,定义类实现Runnalbe接口
 2,覆盖接口中的run方法。
 3,通过Thread类创建线程对象,并将实现了Runnable接口的子类对象
 作为实际参数传递给Thread类的构成函数。
 4,调用Thread类中start方法,开启线程,并执行Runnable接口子类中的run方法。
 
 特点:
  1,描述失误的类中封装了属性和行为,如果有部分代码被多线程所执行。同时还在操作属性,那么可以通过实现Runnable接口
 的方式。
  因为该方式是定义一个Runnable接口的子类独显,可以被多个相处所操作实现了数据的共享。
  2,实现Runnable的接口的好处,避免了单继承的局限性。
  如果一个类明确了自己的父类,那么很遗憾,他就不可以继承Thread。因为java不支持多继承。
  
  

6, 创建线程时,如果是通过继承Thread来实现多线程的话,可以通过创建Thread子类对象
 子类来实现多线程。但是你是通过实现Runnalbe的方式,需要创建Thread对象来实现,因为
 Runnable接口不是Thread的子类。使用时需要将实现Runnalbe的对象作为实参传入线程的
 参数列表来实现多线程执行指定的代码。
 
7,对Runnable的由来(掌握):
 实际上,Runnable是将多线程要运行的代码存储的位置抽取出来定义到了Runnable接口当中,同时该接口的初相避免单继承的局限性。
 
 
 接口中的方法没有抛过异常,只能处理。
8,线程安全问题问题:(Very重要)
 线程安全问题,因为线程的随机性,有可能导致多线程在操作数据时发生数据错误的产生。
 线程安全问题产生的原因:
  当线程中多条代码在操作同一个共享数据时,一个线程将部分执行完,还没有继续执行其他代码时,
  被另一个线程获取CPU执行权,这时共享数据有可能出现数据错误。
  
  简单说:多条操作共享数据的代码被多个线程分开执行造成的。
  
  安全问题涉及的内容:
  1,共享数据。
  2,是否被多条语句操作。
  这也是判断多线程程序是否存在安全隐患的依据。
  
 解决安全问题的方式:
  java中提供了一个同步机制。
  解决原理:让多条操作共享数据的代码在某一时间段,被一个线程执行完,在执行过程中,其他线程不可以参与运算。
  同步格式:
   同步代码块:
   synchronized(对象){
    需要同步的代码块;
   }
 同步的原理:通过一个对象锁,将多条操作数据的代码进行了封装并加锁。
  只有持有这个锁的线程才有机会进入同步中去执行,在执行期间,
  即使其他线程获取对到执行权,因为没有获取到锁,所以只能在外面等。
  只有同步中线程执行完同步中的代码时,才会释放这个锁,那么其他程序
  线程才有机会去获取这个锁,并只能有一个获取到而且进入到同步中。
  
  举例:火车上的卫生间,锁机制的最好体现。
  
  同步的好处:同步的出现解决了多线程的安全问题。
  
  同步弊端:
   因为多线程每次都要判断这个锁,所以效率会降低。
  
  以后写同步你会发现这样一个问题,如果出现了安全问题后:加入了同步,安全问题已让存在。
  因为同步是有前提的:
  同步前提:(Very important)
   1,必须是两个或者两个以上的线程才可以需要同步。
   2,必须要保证多个线程使用同一个线程,才可以实现多个线程被同步。
  如果出现加上同步安全问题依然存在,就按照两个前提来排查问题。

//实际问题分析:(银行存款问题)
实现同步中的锁可以有三种情况:
1,同步代码块: 可以使用任意的锁 (建议使用)
2,同步函数:  只能使用this关键字
3,静态同步函数: 该类的字节码文件: xxxooo.class

class TicketWin implements Runnable
{

 private int tickets = 100;
 Object obj = new Object();
 boolean flag = true;
 public void run()
 {
  if(flag)
   while(true)
   {
    synchronized(this)
    {
     if(tickets>0)
     {
      try{Thread.sleep(10);}catch(InterruptedException e){}
      
      System.out.println(Thread.currentThread().getName()+"....code...."+tickets--);
     }
    }
   }
  else
   while(true)
    show();
 }
 public synchronized void show()
 {
  if(tickets>0)
  {
   try{Thread.sleep(10);}catch(InterruptedException e){}
   
   System.out.println(Thread.currentThread().getName()+"....show...."+tickets--);
  }
 }
}

class ThisLockDemo
{
 public static void main(String[] args)
 {
  TicketWin t = new TicketWin();

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

  t1.start();
  try{Thread.sleep(10);}catch(Exception e){}//让主线程睡眠10毫秒。让t1有获取到cpu的执行权。去if中的代码块执行。
            //让10毫秒后,主线程在有执行资格,获取到执行权,将标记改为false。
            //并开启t2.这时t2一定去else中的同步函数执行。
  t.flag = false;
  t2.start();
 }
}

在单例设计模式中的线程同步问题:
/*
单例模式有两种体现形式:
1,饿汉式。

/*
class Single
{
 private static final Single s = new Single();
 private Single(){}

 public static Single getInstance()
 {
  return s;
 }
}
*/

//2,懒汉式。(延迟加载)

/*
当多个线程并发执行getInstance方法时,容易发生线程安全问题。
因为s是共享数据,有多条语句在操作共享数据。

解决方式很简单。只要让getInstance方法具备同步性即可.
这虽然解决了线程安全问题,但是多个线程每一次获取该实例,都要
调用这个方法,每次调用都判断一次锁,所以效率会比较低.

为了保证安全,同时为了提高效率.可以通过双重判断的形式来完成。
原理:就是减少线程判断的锁次数。

虽然解决安全问题,也解决了效率问题,但是代码过多。
所以建议使用饿汉式体现单例设计模式。

但是面试时,考的都是懒汉式。
*/

class Single
{
 private static Single s = null;

 private Single(){}

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

  
同步的弊端:
1,效率会降低。
2,容易引发死锁。
死锁经常出现的现状为:同步嵌套。  
死锁事例:


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()+"...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 = new Object();
 public static Object lockb = new Object();
}


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();
 }
}
  
  
9,线程通信和JDK1.5之后的同步方法(Very重要)
/*
在jdk1.5版本之后,
出现了一些新的特性,将原理的线程进行了改良。

在java.util.concurrent.locks包中提供了一个接口Lock。替代了synchronized。

synchronized。使用的是锁操作是隐式的。

Lock接口,使用的锁操作是显示的。
由两个方法来完成:
lock():获取锁。
unlock():释放锁。


还有一个对象,Condition.
该对象的出现替代了Object中的wait notify notifyAll这些操作监视器的方法。

替代后的方式:await  signal  signalAll.

 

 

接下来,把下列代码替换成JDK1.5版本只有的新对象。

 

新功能最大好处,就是在一个Lock锁上,可以添加多组监视器对象。

这样就可以实现本方只唤醒对方的线程

 

锁,是同步的机制.通过锁来控制同步.监视器是用于同步中对象的操作.
比如wait,notify  notifyAll.每一组监视器方法对应一个锁.
到了jdk1.5以后,将监视器的方式从Object中,封装到了Condition对象中,
每一个锁lock,可以对应多组监视器对象,这就可以实现本方只唤醒对方的操作。


*/
import java.util.concurrent.locks.*;
class Res
{
 private String name;
 private int count  = 0;
 private boolean b = false;

 //定义一个锁。
 Lock lock = new ReentrantLock();

 //通过指定的锁,创建了一个该锁上可以使用了监视器对象。
 Condition proCon = lock.newCondition();

 //升级后的lock可以对应多组监视器对象。
 Condition cusCon = lock.newCondition();


 public void set(String name)
 {
  //获取锁。
  lock.lock();
  try
  { 
   while(b)
    proCon.await();
   this.name = name+"--------"+count;
   count++;
   System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
   b = true;
   cusCon.signal();
  }
  catch(InterruptedException e)
  {
  
  }
  finally
  {
   //释放锁
   lock.unlock();
  }


 }


 public void out()
 {
  lock.lock();
  try
  {
   while(!b)
    cusCon.await();
   System.out.println(Thread.currentThread().getName()+"----消费者---"+this.name);
   b = false;
   proCon.signal();

  }
  catch (InterruptedException e)
  {
  }
  finally
  {
   lock.unlock();
  }
 }
 
}


class Pro implements Runnable
{
 private Res r;
 Pro(Res r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.set("产品");
  }
 }
}
class Cus implements Runnable
{
 private Res r;
 Cus(Res r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.out();
  }
 }
}

 

class ProCusDemo2
{
 public static void main(String[] args)
 {
  Res r = new Res();
  Pro p = new Pro(r);
  Cus c = new Cus(r);

  Thread t1 = new Thread(p);
  Thread t2 = new Thread(p);
  Thread t3 = new Thread(c);
  Thread t4 = new Thread(c);
  //t1,t2都是生产者。
  //t3,t3都是消费者。
  t1.start();
  t2.start();
  t3.start();
  t4.start();
 }
}
  
以上

多线程:
1,线程与进程
 进程:当前正在执行的程序。代表一个应用程序在内存中的执行区域。
 一个进程中可以有多个线程,线程是CPU调度和分派的基本单位。我们可以理解为线程就是程序运行中的一条路径。
 
一个进程中如果只有一个执行路径,这个程序称为单线程。
一个进程中有多个执行路径时,这个程序成为多线程。

2,举出一个日常生活中多线程的例子。
JVM启动是单线程,还是多线程的呢?

jvm的启动其实就是多线程程序。
其中有一个程序负责从主函数开始执行,并控制程序运行的流程。
同时为了提高效率,还启动了另一个控制单元(执行路径)专门负责堆内存中的垃圾回收。
在程序正常执行过程中,如果出现了垃圾,这是另一个负责收垃圾的线程会在不定时的时间进行垃圾的处理。
这两个程序是同时执行的。

负责执行正常代码的线程,称为主线程。该线程执行的代码都存放于主函数中。
负责收垃圾代码执行的线程,成为垃圾回收线程。该线程要执行的代码在finalize中。

3,两种创建线程的方式
通过API查阅,发现Thread类描述时,有两种创建线程的方式。
方式一:定义一个类继承Thread类,并覆盖Thread类中run方法。

  作为了解掌握:
   为什么要继承Thread,为什么要覆盖run
   其实直接建立Thread类对象即可。并开启线程执行就可以了。
   但是虽然线程执行了,可是执行的代码是该线程默认的代码,该代码就存放在run方法中。

   可是定义线程的目的是为了执行自定义的代码。
   而线程运行代码都存储在run方法中,所以只有覆盖了run方法,才可以运行自定义的内容,
   想要覆盖,必须先要继承。


主线程运行的代码都在main函数中,
自定义线程运行的代码都在run方法中。

直接创建Thread类的子类对象就是创建了一个线程。
在内存中其实:1,堆内存中产生了一个对象,2,需要调用了底层资源,去创建执行路径。

如果直接调用该对象的run方法。
 这时,底层资源并没有完成线程的创建和执行。
 仅仅是是简单的对象调用方法的过程。所以这时,执行控制流程的只有主线程 .


如果想要开启线程,需要去调用Thread类中另一个方法完成。
start方法完成:该方法做了两件事,1,开启线程,2,调用了线程的run方法。

4,线程的名称:
多线程的创建,为了对各个线程进行标识,他们有一个自己默认的名称。
格式:Thread-编号,编号从0开始。


static Thread currentThread():获取当前线程对象。
String getName():获取线程名称。
void  setName():设置 线程的名称。

Thread(String name):构造函数,线程对象一建立就可以指定名称。
例如构造函数的方法:
class Demo extends Thread
{
 private String name;
 Demo(String name)
 {
  super(name);
  //this.name = name;
 }
 public void run()
 {
  for(int x=0; x<10; x++)
  {
   //for(int y=-9999999; y<99999999; y++){}//为了减缓程序的执行。
   //因为Demo是Thread类的子类,所以可以直接使用Thread类中的getName方法。获取当前线程的名字。
   System.out.println(getName()+"....."+name);
  }
 }
}

class  ThreadDemo3
{
 public static void main(String[] args)
 {
  Demo d1 = new Demo("变形金刚");//创建了一个Thread类的子类对象,其就是在创建一个线程。
  Demo d2 = new Demo("哈利波特");
//  d1.setName("小强");
//  d2.setName("旺财");
  d1.start();
  d2.start();

  for(int x=0; x<20; x++)
  {
   //如何获取到主线程对象呢?
//   //通过Thread类中的一个方法。currentThread()返回当前线程对象。该方法是静态的。
   System.out.println(Thread.currentThread().getName()+"---"+x);
  }/**/
  
  //System.out.println("MAIN===="+4/0);

 }
}
5,线程对象的创建(理解)
创建线程的两种方式:
 1,继承Thread类。
 步骤:
  1,继承Thread类
  2,覆盖Thread类的run方法
  3,创建Thread类的子类对象创建线程
  4,调用Thread类中的start方法开启线程,并执行类中的run方法。
 
 特点:当类去描述事物,事物中有属性和行为。
 如果行为中有部分代码需要被多线程执行,同时还在操作属性。就需要该类继承Thread类,
 那么产生该类的对象作为线程对象,可是这样做会导致,每个对象都存储一份属性数据。
 无法在多个线程中共享该数据。加上静态,虽然实现了共享,但是生命周期太长。

 2,实现Runnalbe接口:
 步骤:
 1,定义类实现Runnalbe接口
 2,覆盖接口中的run方法。
 3,通过Thread类创建线程对象,并将实现了Runnable接口的子类对象
 作为实际参数传递给Thread类的构成函数。
 4,调用Thread类中start方法,开启线程,并执行Runnable接口子类中的run方法。
 
 特点:
  1,描述失误的类中封装了属性和行为,如果有部分代码被多线程所执行。同时还在操作属性,那么可以通过实现Runnable接口
 的方式。
  因为该方式是定义一个Runnable接口的子类独显,可以被多个相处所操作实现了数据的共享。
  2,实现Runnable的接口的好处,避免了单继承的局限性。
  如果一个类明确了自己的父类,那么很遗憾,他就不可以继承Thread。因为java不支持多继承。
  
  

6, 创建线程时,如果是通过继承Thread来实现多线程的话,可以通过创建Thread子类对象
 子类来实现多线程。但是你是通过实现Runnalbe的方式,需要创建Thread对象来实现,因为
 Runnable接口不是Thread的子类。使用时需要将实现Runnalbe的对象作为实参传入线程的
 参数列表来实现多线程执行指定的代码。
 
7,对Runnable的由来(掌握):
 实际上,Runnable是将多线程要运行的代码存储的位置抽取出来定义到了Runnable接口当中,同时该接口的初相避免单继承的局限性。
 
 
 接口中的方法没有抛过异常,只能处理。
8,线程安全问题问题:(Very重要)
 线程安全问题,因为线程的随机性,有可能导致多线程在操作数据时发生数据错误的产生。
 线程安全问题产生的原因:
  当线程中多条代码在操作同一个共享数据时,一个线程将部分执行完,还没有继续执行其他代码时,
  被另一个线程获取CPU执行权,这时共享数据有可能出现数据错误。
  
  简单说:多条操作共享数据的代码被多个线程分开执行造成的。
  
  安全问题涉及的内容:
  1,共享数据。
  2,是否被多条语句操作。
  这也是判断多线程程序是否存在安全隐患的依据。
  
 解决安全问题的方式:
  java中提供了一个同步机制。
  解决原理:让多条操作共享数据的代码在某一时间段,被一个线程执行完,在执行过程中,其他线程不可以参与运算。
  同步格式:
   同步代码块:
   synchronized(对象){
    需要同步的代码块;
   }
 同步的原理:通过一个对象锁,将多条操作数据的代码进行了封装并加锁。
  只有持有这个锁的线程才有机会进入同步中去执行,在执行期间,
  即使其他线程获取对到执行权,因为没有获取到锁,所以只能在外面等。
  只有同步中线程执行完同步中的代码时,才会释放这个锁,那么其他程序
  线程才有机会去获取这个锁,并只能有一个获取到而且进入到同步中。
  
  举例:火车上的卫生间,锁机制的最好体现。
  
  同步的好处:同步的出现解决了多线程的安全问题。
  
  同步弊端:
   因为多线程每次都要判断这个锁,所以效率会降低。
  
  以后写同步你会发现这样一个问题,如果出现了安全问题后:加入了同步,安全问题已让存在。
  因为同步是有前提的:
  同步前提:(Very important)
   1,必须是两个或者两个以上的线程才可以需要同步。
   2,必须要保证多个线程使用同一个线程,才可以实现多个线程被同步。
  如果出现加上同步安全问题依然存在,就按照两个前提来排查问题。

//实际问题分析:(银行存款问题)
实现同步中的锁可以有三种情况:
1,同步代码块: 可以使用任意的锁 (建议使用)
2,同步函数:  只能使用this关键字
3,静态同步函数: 该类的字节码文件: xxxooo.class

class TicketWin implements Runnable
{

 private int tickets = 100;
 Object obj = new Object();
 boolean flag = true;
 public void run()
 {
  if(flag)
   while(true)
   {
    synchronized(this)
    {
     if(tickets>0)
     {
      try{Thread.sleep(10);}catch(InterruptedException e){}
      
      System.out.println(Thread.currentThread().getName()+"....code...."+tickets--);
     }
    }
   }
  else
   while(true)
    show();
 }
 public synchronized void show()
 {
  if(tickets>0)
  {
   try{Thread.sleep(10);}catch(InterruptedException e){}
   
   System.out.println(Thread.currentThread().getName()+"....show...."+tickets--);
  }
 }
}

class ThisLockDemo
{
 public static void main(String[] args)
 {
  TicketWin t = new TicketWin();

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

  t1.start();
  try{Thread.sleep(10);}catch(Exception e){}//让主线程睡眠10毫秒。让t1有获取到cpu的执行权。去if中的代码块执行。
            //让10毫秒后,主线程在有执行资格,获取到执行权,将标记改为false。
            //并开启t2.这时t2一定去else中的同步函数执行。
  t.flag = false;
  t2.start();
 }
}

在单例设计模式中的线程同步问题:
/*
单例模式有两种体现形式:
1,饿汉式。

/*
class Single
{
 private static final Single s = new Single();
 private Single(){}

 public static Single getInstance()
 {
  return s;
 }
}
*/

//2,懒汉式。(延迟加载)

/*
当多个线程并发执行getInstance方法时,容易发生线程安全问题。
因为s是共享数据,有多条语句在操作共享数据。

解决方式很简单。只要让getInstance方法具备同步性即可.
这虽然解决了线程安全问题,但是多个线程每一次获取该实例,都要
调用这个方法,每次调用都判断一次锁,所以效率会比较低.

为了保证安全,同时为了提高效率.可以通过双重判断的形式来完成。
原理:就是减少线程判断的锁次数。

虽然解决安全问题,也解决了效率问题,但是代码过多。
所以建议使用饿汉式体现单例设计模式。

但是面试时,考的都是懒汉式。
*/

class Single
{
 private static Single s = null;

 private Single(){}

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

  
同步的弊端:
1,效率会降低。
2,容易引发死锁。
死锁经常出现的现状为:同步嵌套。  
死锁事例:


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()+"...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 = new Object();
 public static Object lockb = new Object();
}


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();
 }
}
  
  
9,线程通信和JDK1.5之后的同步方法(Very重要)
/*
在jdk1.5版本之后,
出现了一些新的特性,将原理的线程进行了改良。

在java.util.concurrent.locks包中提供了一个接口Lock。替代了synchronized。

synchronized。使用的是锁操作是隐式的。

Lock接口,使用的锁操作是显示的。
由两个方法来完成:
lock():获取锁。
unlock():释放锁。


还有一个对象,Condition.
该对象的出现替代了Object中的wait notify notifyAll这些操作监视器的方法。

替代后的方式:await  signal  signalAll.

 

 

接下来,把下列代码替换成JDK1.5版本只有的新对象。

 

新功能最大好处,就是在一个Lock锁上,可以添加多组监视器对象。

这样就可以实现本方只唤醒对方的线程

 

锁,是同步的机制.通过锁来控制同步.监视器是用于同步中对象的操作.
比如wait,notify  notifyAll.每一组监视器方法对应一个锁.
到了jdk1.5以后,将监视器的方式从Object中,封装到了Condition对象中,
每一个锁lock,可以对应多组监视器对象,这就可以实现本方只唤醒对方的操作。


*/
import java.util.concurrent.locks.*;
class Res
{
 private String name;
 private int count  = 0;
 private boolean b = false;

 //定义一个锁。
 Lock lock = new ReentrantLock();

 //通过指定的锁,创建了一个该锁上可以使用了监视器对象。
 Condition proCon = lock.newCondition();

 //升级后的lock可以对应多组监视器对象。
 Condition cusCon = lock.newCondition();


 public void set(String name)
 {
  //获取锁。
  lock.lock();
  try
  { 
   while(b)
    proCon.await();
   this.name = name+"--------"+count;
   count++;
   System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
   b = true;
   cusCon.signal();
  }
  catch(InterruptedException e)
  {
  
  }
  finally
  {
   //释放锁
   lock.unlock();
  }


 }


 public void out()
 {
  lock.lock();
  try
  {
   while(!b)
    cusCon.await();
   System.out.println(Thread.currentThread().getName()+"----消费者---"+this.name);
   b = false;
   proCon.signal();

  }
  catch (InterruptedException e)
  {
  }
  finally
  {
   lock.unlock();
  }
 }
 
}


class Pro implements Runnable
{
 private Res r;
 Pro(Res r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.set("产品");
  }
 }
}
class Cus implements Runnable
{
 private Res r;
 Cus(Res r)
 {
  this.r = r;
 }
 public void run()
 {
  while(true)
  {
   r.out();
  }
 }
}

 

class ProCusDemo2
{
 public static void main(String[] args)
 {
  Res r = new Res();
  Pro p = new Pro(r);
  Cus c = new Cus(r);

  Thread t1 = new Thread(p);
  Thread t2 = new Thread(p);
  Thread t3 = new Thread(c);
  Thread t4 = new Thread(c);
  //t1,t2都是生产者。
  //t3,t3都是消费者。
  t1.start();
  t2.start();
  t3.start();
  t4.start();
 }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值