黑马程序员java基础之多线程

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

进程:是一个正在执行的程序。每个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫做一个控制单元

线程:就是进程中的一个独立的控制单元。线程控制着进程的执行。

一个进程中 至少有一个线程

java VM启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。

其实更多细节说明jvm启动不止一个线程,还有负责垃圾回收机制的线程。

Thread类就是对线程这类事物的描述。

1.创建线程的第一种方法

继承Thread类;

        复写run方法;

覆盖run方法的原因:Thread类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法

目的:将自定义的代码存储在run方法中,让I线程运行

使用线程的start方法启动线程,调用run方法

class Demo extends Thread	//继承Thread类,创建线程
{
   public void run()	//复写run方法
   {
       for(int x=0;x<10;x++)
       System.out.println("demo run----"+x);
    } 
}

class ThreadDemo
{
   public static void main(String[] args)
   {
       Demo d=new Demo();	//建立一个对象,同时也就创建了一个线程
       d.start();	//调用start()方法,启动线程,调用run方法

       for(int x=0;x<10;x++)
       {
           System.out.println("Hello Word----------"+x);
        }
    }
}
运行结果:

发现运行结果每次都不一致,因为多个线程都在获取cpu的执行权,cpu执行到哪个线程,就运行哪个线程。在某一时刻,只能有一个程序在运行(多核除外)。

以上体现出了线程的一个特性,即随机性,谁抢到谁运行,运行时间由cpu决定


练习:创建两个现场,和主线程交替运行

class Demo extends Thread	//继承Thread类
{
  
  Demo(String name)	//初始化构造函数,传递线程名称
   {
      super(name);	//自定义线程名的方法,调用父类线程名
    }
   public void run() 
   {
    for(int x=0;x<60;x++)
           {
              System.out.println(Thread.currentThread().getName()+"T run---"+x);	//获取当前线程名
            }   
    }
}
  

class ThreadTest
{
   public static void main(String[] args)
   {
       Demo d=new Demo("zhangsan");	//创建线程,并传递线程名
       Demo e=new Demo("lisi");
       d.start();
       e.start();
       for(int x=0;x<60;x++)
       {
            System.out.println("main run-------------"+x);
         }
    }
}


线程都有自己的默认名;格式:Thread-数字,数字从0开始

发现Thread构造方法初始化的时候就有名称Thread(String name);线程Thread中有getName(),setName()方法来获取线程名,所以只需要调用super(name)就可以获取父类Thread来获取线程名

static Thread currentThread():获取当前线程对象

设置线程名称:setName()或者构造函数

练习:简单的买票程序:多个窗口同时买票

class Ticket implements Runnable//extends Thread	//继承Thread类
{
   private <span style="color:#ff0000;">static</span> int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   public void run()	//复写run方法
   {
      while(true)
       {
           if(tick>0)	//当还有票的时候
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
         }
    }
}

class TicketDemo
{
    public static void main(String[] args)
    {
          
	//创建线程
          Ticket t1=new Thread();
          Ticket t2=new Thread();
          Ticket t3=new Thread();
          Ticket t4=new Thread();

	//启动线程,调用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();
       }
}

但是使用静态让票数固定,会浪费资源,由此引入线程的第二种创建方法:

声明实现Runnable接口;

覆盖Runnable接口中的run方法;

将线程要运行的代码放在该方法中

通过Thread类建立线程对象;

将Runnable接口的子类对象作为实际参数传入Thread类;

实现此步骤的原因:自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定某个对象的run方法,就必须明确该run方法所属对象

调用Thread类的start方法开启线程,并调用Runnable接口的run方法

还是买票的例子:

class Ticket implements Runnable	//实现Runnable接口
{
   private int tick=100;	//初始化成员变量:票的总数
   public void run()	//复写run方法
   {
      while(true)
       {
           if(tick>0)	//当还有票的时候
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
         }
    }
}

class TicketDemo
{
    public static void main(String[] args)
    {
         /*
 
	//创建线程
          Ticket t1=new Ticket();
          Ticket t2=new Ticket();
          Ticket t3=new Ticket();
          Ticket t4=new Ticket();

	//启动线程,调用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();

	*/

	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联,将实现Runnable接口的类对象作为实际参数传入线程对象中
	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();	
	
       }
}
运行结果:


线程也运行起来了

那么继承类与实现类有什么区别呢?

实现方式避免了单继承的局限性。类可以在继承的基础上实现接口,功能得以拓展。在定义线程时,最好使用实现方式

线程代码存在实现Runnable接口的子类的run方法中;而在继承中,线程代码存在于Thread子类的run方法中

在上面卖票的例子中通过传入对象作为参数,使各个线程运行的同时还可以保持票的总数不变

在售票的例子中,让线程停顿10毫秒,发现结果出现了负数。多线程的运行出现了安全问题

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   public void run()	//复写run方法
   {
      while(true)
       {
           if(tick>0)	//当还有票的时候
		try
		{
			Thread.sleep(10);
		}
		catch(Exception e)
		{
			
		}		
           System.out.println(Thread.currentThread().getName()+"sale:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
         }
    }
}

class TicketDemo1
{
    public static void main(String[] args)
    {
         /*
 
	//创建线程
          Ticket t1=new Ticket();
          Ticket t2=new Ticket();
          Ticket t3=new Ticket();
          Ticket t4=new Ticket();

	//启动线程,调用run方法
          t1.start();

          t2.start();

          t3.start();

          t4.start();

	*/

	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	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();
	
	
	
       }
}

问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,倒是共享数据的错误

解决办法:对于多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与

对于次安全问题,使用同步代码块处理:

synchronized(对象)

{

需要被同步的代码块

}

之后票数就正常了

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

同步的前提:1.必须要有两个或两个以上的线程;2.必须是多个线程使用同一个锁

同步的好处:可以解决多线程的安全问题

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

练习:银行金库,两个客户分别存入300元,分3次,每次存100

如何找问题:明确那些代码是多线程运行代码;明确共享数据;明确多线程运行代码中哪些语句是操作共享数据

引入同步函数:把同步作为修饰符传给函数

class Bank	//对银行进行描述
{
	Object obj=new Object();
	private int sum;	//引用成员变量,银行金库钱的总数
	public synchronized void add(int n)	//存入钱之后,金库内钱增加,计算总和,同步函数
	{	
		//synchronized(obj)
		//{
			sum=sum+n;
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
		
			}
			System.out.println("sum="+sum);
		//}
	}
}

class Cus implements Runnable	//实现Runnable接口
{
	private Bank b=new Bank();	//引用Bank对象作为成员变量
	public void run()	//复写run方法
	{
		for(int x=0;x<3;x++)	//分为3次存钱
		{
			b.add(100);	//银行金库总存款增加
		}
	}
}

class BankDemo
{
	public static void main(String[] args)
	{
		Cus c=new Cus();	//新建实现Runnable接口的类对象
		//创建线程对象,并将实现Runnable接口的类对象作为参数传入
		Thread t1=new Thread(c);
		Thread t2=new Thread(c);

		//启动线程,调用Runnable接口子类的run方法
		t1.start();
		t2.start();
	}
}


那么同步函数用的是哪个锁呢?函数需要被对象调用,那么函数都有一个所属对象调用,即this.所以同步函数使用的锁是this

接下来就买票的例子进行验证

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   Object obj=new Object();
   boolean flag=true;
   public void run()	//复写run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(obj)	//传入对象设为上帝类Object
	   {	//这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
          	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()<span style="white-space:pre">	</span>//同步函数调用的是使用该方法的对象的锁,即this
   {
	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
    }
}

class TicketDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//运行线程
	t1.start();

	//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
	try
	{
		Thread.sleep(10);
	}
	catch(Exception e)
	{}

	t.flag=false;	//将标志更改,使得t2运行同步函数
	t2.start();
	
       }
}

发现结果中有出售0号票的


原因是共享数据用的不是同一个锁,第一个线程用的是obj的锁,第二个线程用的是this锁

将代码块的锁也改为this

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   Object obj=new Object();
   boolean flag=true;
   public void run()	//复写run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">this</span>)	
	   {	//这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
          	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()
   {
	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
    }
}

class TicketDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//运行线程
	t1.start();

	//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
	try
	{
		Thread.sleep(10);
	}
	catch(Exception e)
	{}

	t.flag=false;	//将标志更改,使得t2运行同步函数
	t2.start();
	
       }
}


同步函数和同步代码块都使用的是this锁,如此就没有0号票了


静态同步函数:如果同步函数被静态修饰后,使用的锁是什么呢?

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   Object obj=new Object();
   boolean flag=true;
   public void run()	//复写run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">this</span>)	
	   {	//这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
          	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public static synchronized void show()
   {
	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
    }
}

class TicketDemo3
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//运行线程
	t1.start();

	//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//将标志更改,使得t2运行同步函数
	t2.start();
	
       }
}

运行结果有0号票,说明用的不是同一个锁,同步函数的锁不在是this了,因为静态中不存在this

静态在内存是,内存中没有本类对象,但是一定有该类对象对应的字节码文件对象

类名.class,该对象的类型是Class

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   Object obj=new Object();
   boolean flag=true;
   public void run()	//复写run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(<span style="color:#ff0000;">Ticket.class</span>)	
	   {	//这里tick就是共享数据,所以在操作tick的代码上使用同步代码块
          	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"  code-"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public static synchronized void show()
   {
	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
    }
}

class TicketDemo3
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//运行线程
	t1.start();

	//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//将标志更改,使得t2运行同步函数
	t2.start();
	
       }
}


发现程序又安全了,所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,类名.class


单例设计模式:懒汉式的同步

先复习下饿汉式

class Single
{
	private static final Single s=new Single();<span style="white-space:pre">	</span>//一开始就初始化对象值
	private Single()
	{}
	public static  Single getInstance()
	{
		return s;
	}
}

//懒汉式
class Single
{
	private static Single s=null;
	private Single()	
	{}
	public static Single getInstance()
	{
		if(s==null)
			s=new Single();
		return s;
	}
}

对于懒汉式,如果同时有多个线程在调用该类,就会出现安全问题,一个在判断,一个在赋值,不能保证对象的唯一性,所以引入同步,

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;
		}
	}
}
使用双重判断,却比较麻烦,所以平常使用最好使用饿汉式,饿汉式只有一句话,不存在同步问题


死锁:通常情况下是同步中嵌套同步,而锁不同

class Ticket implements Runnable	//实现Runnable接口
{
   private static int tick=100;	//初始化成员变量:票的总数,在此使用静态,使票的总数保持在100张
   Object obj=new Object();
   boolean flag=true;
   public void run()	//复写run方法
   {
	if(flag)
	{
      while(true)
       {
	   synchronized(obj)	//同步代码块中调用同步函数
	   {	
          	show();
	    }
       }
	}
	else
	{
		while(true)
		{
			show();
		}
	}
    }
   public synchronized void show()	//同步函数中包含同步代码块,调用的是this锁
   {
	synchronized(obj)
	{
	 if(tick>0)	//当还有票的时候
		{
			try
			{
				Thread.sleep(10);
			}
			catch(Exception e)
			{
			
			}		
           		System.out.println(Thread.currentThread().getName()+"show===:"+tick--);	//获取当前线程名,并且票越卖越少,所以递减,记录卖的哪一张票
		}
	}
    }
}

class DeadLockDemo2
{
    public static void main(String[] args)
    {
	Ticket t=new Ticket();	//创建一个Ticket对象

	//创建线程,并将线程与run()方法相关联
	Thread t1=new Thread(t);
	Thread t2=new Thread(t);
	
	//运行线程
	t1.start();

	//主函数瞬间先执行完,如此标志位是false,再执行另外两个线程,此时两个线程都会执行run方法中的else语句那么就只有同步函数在运行了,为了避免这种情况,在此处让主线程停10毫秒,从而能使其中一个线程运行同步代码块,而另一个则运行同步函数
	try
	{
		Thread.sleep(1);
	}
	catch(Exception e)
	{}

	t.flag=false;	//将标志更改,使得t2运行同步函数
	t2.start();
	
       }
}


发现没运行一会儿变没有了,发生了死锁


写出一个死锁程序

class Test implements Runnable	//实现Runnable接口
{
   private boolean flag;
   Test(boolean flag)
   {
      this.flag=flag;
    }
    public void run()	//复写run方法
    {
      
        if(flag)
        {
          while(true)
          {
           synchronized(MyLock.locka)	//locka锁嵌套lockb锁
           {
             
               System.out.println("if locka");
               synchronized(MyLock.lockb)
               {
                  System.out.println("if lockb");
                }
            }
        }
       }
        else
        { 
         while(true)
         {
          synchronized(MyLock.lockb)	//lockb中嵌套locka锁
           {
               System.out.println("else lockb");
               synchronized(MyLock.locka)
               {
                   System.out.println("else locka");
                }
            }
         }
       }
     }
}


class MyLock	//创建一个锁对象
{
   static Object locka=new Object();
   static Object lockb=new Object();
}

class DeadLockDemo
{
   public static void main(String[] args)
   {
	//创建线程,传入实现Runnable接口的类作为参数
      Thread t1=new Thread(new Test(true));
      Thread t2=new Thread(new Test(false));
	//启动线程
      t1.start();
      t2.start();
   }
}

只循环了一次便终止了,发生了死锁

线程间通讯

建立一个共享资源,不断交替传入两个姓名和性别,然后打印出来

/*
练习:不断的存入两个人名,不断的输出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立资源对象
		//创建线程第二步,创建Runnable接口的子类对象
		Input in=new Input(r);	//建立输入对象,并将资源对象作为参数传入
		Output out=new Output(r);	//建立输出对象,并将新建的资源对象传入
		//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有资源类
{
	//包含姓名和性别
	 String name;	
	 String  sex;
}
class Input implements Runnable		//存入线程,将姓名和性别存入资源中
{
	private Res r;	//创建资源类型的成员变量
	Input(Res r)	//构造函数初始化时就获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		int x=0;
		while(true)
		{
			synchronized(r)<span style="white-space:pre">		</span>//加入同步,避免第一个性别还没打印,第二个性别就先打印了,此处寻找同一个锁,发现r就是共享对象
			{
				if(x==0)
				{
					r.name="mike";
					r.sex="男";
				}
				else
				{
					r.name="lili";
					r.sex="女女女女女女女女女";
				}
				x=(x+1)%2;	//通过x的值的变换,来切换两个名字互相切换
			}
		}
	}
}

class Output implements Runnable
{
	private Res r;	//与传入线程类似,创建资源型成员变量
	Output(Res r)	//初始化构造函数的时候获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		while(true)
		{
			synchronized(r)<span style="white-space:pre">		</span><span style="font-family: Arial, Helvetica, sans-serif;">//加入同步,避免第一个性别还没打印,第二个性别就先打印了,此处寻找同一个锁,发现r就是共享对象</span><span style="white-space:pre">
</span>
			{
				System.out.println(r.name+"=="+r.sex);
			}
		}
	}
}
注意,在上面中,对姓名和性别赋值,打印都在不断操作共同数据,所以在输出语句中也要加入锁

此时虽然不会有性别和姓名错乱,但是希望的结果是两个姓名和年龄交替打印,而这里可能输入线程出了同步之后,没有被输出县城抢到执行权,继续被输入线程抢到执行权,然后继续覆盖之前的姓名,不断重复打印一个姓名和性别

此处引入等待唤醒机制

/*
练习:不断的存入两个人名,不断的输出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立资源对象
		//创建线程第二步,创建Runnable接口的子类对象
		Input in=new Input(r);	//建立输入对象,并将资源对象作为参数传入
		Output out=new Output(r);	//建立输出对象,并将新建的资源对象传入
		//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有资源类
{
	//包含姓名和性别
	 String name;	
	 String  sex;
	 boolean flag=false;	//添加一个标志位,来维持两个线程分别进行
}
class Input implements Runnable		//存入线程,将姓名和性别存入资源中
{
	private Res r;	//创建资源类型的成员变量
	Input(Res r)	//构造函数初始化时就获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		int x=0;
		while(true)
		{
			synchronized(r)
			{
				if(r.flag)
				{
					try{
						r.wait();	//如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
						}
					catch(Exception e){}
				}
				if(x==0)
				{
					r.name="mike";
					r.sex="男";
				}
				else
				{
					r.name="lili";
					r.sex="女女女女女女女女女";
				}
				x=(x+1)%2;	//通过x的值的变换,来切换两个名字互相切换
				r.flag=true;	//赋值之后,将标志位修改为真
				r.notify();	//唤醒等待的线程,两个线程都有执行权,如果此线程抢到执行权,会返回上面判断,然后就会等待
			}
		}
	}
}

class Output implements Runnable
{
	private Res r;	//与传入线程类似,创建资源型成员变量
	Output(Res r)	//初始化构造函数的时候获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		while(true)
		{
			synchronized(r)
			{				
				if(!r.flag)		//如果标志位为假,则是姓名性别的值已经打印过了为空了
				{
					try{
					r.wait();		//就让打印线程等待,输入线程运行
						}
					catch(Exception e){}
				}
				System.out.println(r.name+"=="+r.sex);
				r.flag=false;	//打印完之后,将标志位的值修改,
				r.notify();	//将等待的线程唤醒,即唤醒输入线程;如果输出线程再抢到执行权,就会再回到上面判断,发现为假,就会等待,然后只有输入线程执行了

			}
		}
	}
}
wait();notify();notifyAll() 都使用在同步中,因为要对持有监视器的线程操作,所以都使用在同步中,因为只有监视器中才有锁   在上面,因为线程存在嵌套,所以使用wait,notify方法的时候需要标明是调用对象锁r,即r.wart,r.notify;

上述方法都在Object中,因为等待和唤醒必须是同一个锁,而锁可以是任意一个对象,所以是定义在Object中的

下面可以对代码进行优化

/*
练习:不断的存入两个人名,不断的输出打印
*/
class  InputOutputDemo1
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//建立资源对象
		//创建线程第二步,创建Runnable接口的子类对象
		Input in=new Input(r);	//建立输入对象,并将资源对象作为参数传入
		Output out=new Output(r);	//建立输出对象,并将新建的资源对象传入
		//创建线程第三步,新建线程,并将实现Runnable的子类对象传入
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();


	}
}
class Res	//建立共有资源类
{
	//包含姓名和性别
	 private String name;	
	 private String  sex;
	 private boolean flag=false;	//添加一个标志位,来维持两个线程分别进行
	 public synchronized void set(String name,String sex)
	{
		 if(flag)
		{
			try{
				this.wait();	//如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
				}
			catch(Exception e){}
		}
	 this.name=name;
	 this.sex=sex;
	 flag=true;
	 this.notify();<span style="white-space:pre">	</span>//唤醒锁对象监视的等待线程,此处即输出线程
	 }
	 public synchronized void printout()
	{
		 if(!flag)
		{
				try{
					this.wait();	//如果标志位为真,就代表资源中姓名和年龄有值,让线程进入等待状态,让输出线程获得执行权
					}
				catch(Exception e){}
		}
	 System.out.println(this.name+"=="+this.sex);
	 flag=false;
	 this.notify();<span style="white-space:pre">	</span>//唤醒锁对象监视的等待的线程,此处即唤醒输入线程
	 }
}
class Input implements Runnable		//存入线程,将姓名和性别存入资源中
{
	private Res r;	//创建资源类型的成员变量
	Input(Res r)	//构造函数初始化时就获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		int x=0;
		while(true)
		{
				if(x==0)
					r.set("mike","男");
				else
					r.set("丽丽","女女女女女女女女女");
				x=(x+1)%2;	//通过x的值的变换,来切换两个名字互相切换
		}
	}
}

class Output implements Runnable
{
	private Res r;	//与传入线程类似,创建资源型成员变量
	Output(Res r)	//初始化构造函数的时候获取资源对象
	{
		this.r=r;
	}
	public void run()	//复写run方法
	{
		while(true)
		{				
			r.printout();
		}
	}
}

练习:多个生产者和消费者同时运行,但要保持生产一个产品就要消费一个产品

class BCTest 
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//创建资源对象
		//创建生产消费者对象,并把资源对象作为参数传入
		Pro p=new Pro(r);
		Consumersumersumer c=new Consumersumersumer(r);
		//创建线程
		Thread t1=new Thread(p);
		Thread t2=new Thread(p);
		Thread t3=new Thread(c);
		Thread t4=new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Res
{
	private String name;	//创建产品名称变量
	private int count=1;	//产品编号变量
	private boolean flag;	//标志位
	public synchronized void set(String name)	//构造生产者线程的调用方法
	{
		while(flag)	//使用whil循环,使得被唤醒的线程每次都要先判断标志,避免生产一个还没消费就被覆盖掉了
		{
			try
			{
				this.wait();	//如果标志位为真,则让生产者等待,让消费者消费
			}
			catch(Exception e)
			{
			}
		}
		this.name=name+"=="+count++;	//产品一有名称就自带编号

		System.out.println(Thread.currentThread().getName()+"生产者=========="+this.name);
		flag=true;	//修改标志位
		this.notifyAll();	//唤醒所有等待中的线程
	}
	public synchronized void out()	//构造消费者线程的调用方法
	{
		while(!flag)
		{
			try
			{
				this.wait();
			}
			catch(Exception e)
			{

			}
		}
		System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
		flag=false;	//打印完成后修改标志位
		this.notifyAll();	//唤醒处于等待状态的所有线程
	}
}
class Pro implements Runnable	//创建生产者类
{
	private Res r;	//创建私有变量为资源类型的
	Pro(Res r)	//初始化构造函数
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			r.set("商品");	//调用生产者方法
	}
}
class Consumersumersumer implements Runnable	//创建消费者类
{
	private Res r;	//创建私有资源类型的变量
	Consumersumersumer(Res r)	//初始化构造函数获取资源对象
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			r.out();	//调用消费者方法
	}
}
JDK5.0之后,synchronized 被Lock(接口)替代;调用其方法newCondition()可以新建一个Condition对象,包含notify,wait,notifyAll方法

并且可以建立多个Condition对象,对于上述例子,可以新建两个分别代表生产者和消费者对应的Condition对象

import java.util.concurrent.locks.*;
class CPTest 
{
	public static void main(String[] args) 
	{
		Res r=new Res();	//创建资源对象
		//创建生产消费者对象,并把资源对象作为参数传入
		Pro p=new Pro(r);
		Consumersumersumer c=new Consumersumersumer(r);
		//创建线程
		Thread t1=new Thread(p);
		Thread t2=new Thread(p);
		Thread t3=new Thread(c);
		Thread t4=new Thread(c);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Res
{
	private String name;	//创建产品名称变量
	private int count=1;	//产品编号变量
	private boolean flag;	//标志位
	private Lock lock=new ReentrantLock();	//建立锁的对象,取代了旧版中的同步
	private Condition cond_con=lock.newCondition();		//condition中封装了wait,notify,notifyAll方法,所以建立此对象可以调用其中的方法
	private Condition cond_pro=lock.newCondition();		//建立消费者对应的Condition对象

	public void set(String name) throws InterruptedException	//构造生产者线程的调用方法
	{
		lock.lock();	//加上锁
		try
		{
			while(flag)	//使用whil循环,使得被唤醒的线程每次都要先判断标志,避免生产一个还没消费就被覆盖掉了
			{
				
				cond_con.await();	//如果标志位为真,则让生产者等待,让消费者消费
			}
			this.name=name+"=="+count++;	//产品一有名称就自带编号

			System.out.println(Thread.currentThread().getName()+"生产者=========="+this.name);
			flag=true;	//修改标志位
			cond_pro.signal();		//唤醒等待的线程
		}
		finally
		{
			lock.unlock();	//解锁
		}
		
	}
	public void out() throws InterruptedException	//构造消费者线程的调用方法
	{
		lock.lock();
		try{
		while(!flag)
		{
				cond_pro.await();
		}
		System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
		flag=false;	//打印完成后修改标志位
		cond_con.signal();		//唤醒等待线程
		}
		finally
		{
			lock.unlock();	//解除锁,释放资源
		}
	}
}
class Pro implements Runnable	//创建生产者类
{
	private Res r;	//创建私有变量为资源类型的
	Pro(Res r)	//初始化构造函数
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
			try
			{
				r.set("商品");	//调用生产者方法
			}
			catch (InterruptedException e)
			{
			}
		
	}
}
class Consumersumersumer implements Runnable	//创建消费者类
{
	private Res r;	//创建私有资源类型的变量
	Consumersumersumer(Res r)	//初始化构造函数获取资源对象
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
		try
		{
			r.out();	//调用消费者方法
		}
		catch (InterruptedException e)
		{
		}
			
	}
}
同时,新版本之后,要想停止线程只有一种方法,就是结束run方法

在开启多线程运行的时候,通常会在运行代码中使用循环结构,只要控制住循环结构就可以终止线程





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值