黑马程序员_多线程

---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

一、进程与线程

      1,进程:就是正在执行的程序,所占有的内存的空间

      2,线程:其实就是进程中的一个子程序,就是一个独立的执行路径,独立的执行控制单元

二、创建线程的两种方式

        java中可以进行多线程的程序编程,其实线程是由操作系统开启,依靠java虚拟机来实现,线程也是对象,有了线程对象的描述类 java.lang.Thread,就可以进行多线程的编程。

        1,创建线程的第一种方式——继承Thread类

              继承Thread类的方式,数据是线程独享的,通过以下三步进行线程的创建

              1.1,定义类继承Thread

              1.2,复写Thread类的run方法

              1.3,创建线程子类对象,调用线程的start方法开启线程

class Demo extends Thread
{
	public void run(){
	   for(int x = 0 ; x < 20 ; x++){
	      System.out.println("run..."+x);
	   }
	}
}

class ThreadDemo 
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		d.start();
		for(int x = 0 ; x < 20 ; x++){
		System.out.println("main..."+x);
		}
	}
}

       程序在执行到Demo d = new Demo()时创建线程d,执行d.start()时开启这个线程,此时存在两个线程主线程与线程d,run方法与main方法中的for语句交替执行。

       2,创建线程的第二种方式——实现Runnable接口

             继承有局限性,java中只能单继承,接口可以多实现,Runnable接口方式,数据是程共享的,通过实现Runnable接口创建线程通过以下步骤完成

             2.1,定义类实现Runnable接口

             2.2,复写Runnable接口的run方法

             2.3,通过Thread类建立线程对象

             2.4,将Runnable接口子类对象作为实际参数传递给Thread类的构造函数

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

class Ticket implements Runnable
{
	private int tickets = 100;
	public void run(){
		while(true){
		   	if(tickets > 0){
				System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
			}
		}
	}
}
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.setName("一号窗口");
		t2.setName("二号窗口");
		t3.setName("三号窗口");
		t4.setName("四号窗口");
		t1.start();
                t2.start();
                t3.start();
                t4.start();
	}
}

三、多线程的安全问题

       1,原因

             多线程操作同一个共享数据的时候,会发生数据安全问题,以售票系统为例,if(tickets > 0)与System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--)这两句话操作共享数据tickets,这样就会出现当一个线程池判断tickets=1时,进入if语句,还未执行到tickets--),时第二个线程进入判断tickets > 0也进入if语句,这样就会导致出现负票的现象。下面程序中使用wait方法让线程还没有执行完所有操作共享数据的语句就停下来,然后让其它线程进来执行来造成打印负票的现象

class Ticket implements Runnable
{
	private int tickets = 100;
	public void run(){
		while(true){
			if(tickets > 0){
			   try{
			   Thread.sleep(10);
			   }catch(Exception e){}
			  System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
			}
		}
	}
}

        2,解决办法

              2.1,思路:让一个线程执行操作共享数据的多条语句时,其他线程执行不了,当这个线程执行完后其它线程才能执行

              2.2,办法:同步机制synchronized(对象){线程操作的共享数据 },同步中的对象,看成一把锁,线程进到同步代码块后,拥有了锁,没有锁的线程,就不会执行,当有锁的线程执行完毕后,会释放锁其他线程就开始抢锁。

class Ticket implements Runnable
{
	private int tickets = 100;
	public void run(){
		while(true){
		   synchronized(this){//同步机制
		     if(tickets > 0){
			   try{
			   Thread.sleep(10);
			   }catch(Exception e){}
			  System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
			}
		   }
		}
	}
}
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.setName("一号窗口");
		t2.setName("二号窗口");
		t3.setName("三号窗口");
		t4.setName("四号窗口");
		t1.start();
        t2.start();
        t3.start();
        t4.start();
			}
}

四、同步代码块与同步函数

       1,同步代码块是将多条操作共享数据的语句放到synchronized(锁对象){}的大括号中,实现同步

       2,同步函数是将同步放在函数上也叫方法,来达到同步,使用方式如下,Bank类为银行具有存钱的功能其内部sum为共享数据且有多条语句操作它,而且语句都需要同步因此把同步放在函数上

class Bank
{
	private int sum;
	public synchronized void add(int n )
	{                                                                                                			sum = sum+n;
		try{Thread.sleep(10);}catch(Exception e){}
		System.out.println("sum="+sum);
	}
}
class Cash implements Runnable
{
	private Bank b = new Bank();
	public void run()
	{
		for(int x=0;x<3;x++)
		{
			b.add(100);
		}
	}
}

class CashDemo 
{
	public static void main(String[] args) 
	{
		Cash c = new Cash();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();

	}
}

五、同步锁

       1,同步函数的锁是this,同步函数使用的锁对象是this

程序通过判断flag标记来决定执行同步函数show或同步代码块,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序运行正常。因此说明同步函数的锁是this。

class ThisTicket implements Runnable
{
	private int ticket = 100;
	boolean flag = true;
	Object obj = new Object();
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				//synchronized(obj)  //obj锁
				synchronized(this) //this锁
				{
					if(ticket>0)
					{
						try
						{
							Thread.sleep(10);
						}
						catch (Exception e)
						{
						}
						System.out.println(Thread.currentThread().getName()+"code sale:"+ticket--);
					}
				}
			}
		}else
		{
			while(true)
			show();
		}
		
	}
	public synchronized void show()//this锁
	{
		if(ticket>0)
		{
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
			}
			System.out.println(Thread.currentThread().getName()+"method sale:"+ticket--);
		}
	}
}      

 

class ThisLockThread
{
	public static void main(String[] args) 
	{
		ThisTicket tt = new ThisTicket();
		Thread t1 = new Thread(tt);
		Thread t2 = new Thread(tt);
		t1.start();
		try
		{
			Thread.sleep(10);
		}
		catch (Exception e)
		{
		}
		tt.flag=false;
		t2.start();	
	}
}

 2,静态同步函数的锁是所在类字节码文件

        上例程序中将show声明为static即public static synchronized void show()时,当同步代码块为的锁对象为obj即synchronized(obj)时程序出现负票现象,当同步代码块为的锁对象为this即synchronized(this)时程序出现负票现象。当同步代码块为的锁对象为StaticTicket.class即synchronized(StaticTicket.class)时程序运行正常,因此说明同步函数的锁是StaticTicket.class。因此静态同步函数的锁是所在类字节码文件。

六、总结同步的前提

        1,必须要有两个或以上个线程

        2,必须是多个线程使用同一个锁

七、多线程中生产者与消费者问题

       多线程不同方向操作数据,解决办法 1.,线程只要被唤醒,就必须判断标记。2,唤醒全部的线程

//定义产品
class Rescourc
{
	private String name ;//产品的名字
	private int count = 0 ;//产品的计数器
	private boolean flag = false;

	//提供一个赋值的方法,生产产品
	public synchronized void set(String name){
	   while(flag==true){//生产完了,没消费呢,不能生产了
	     try{ this.wait();}catch(Exception e){}
	   }
	   this.name = name + count;
	   count++;
	   System.out.println(Thread.currentThread().getName()+
		   "生产---"+this.name);
	   //标记改成true
	   flag = true;
	   this.notifyAll();
	   
	}
	//提供一个获取值的方法,消费产品
	public synchronized void get(){
	  while(flag==false){//消费完了,没生产了,不能再消费了
	     try{ this.wait();}catch(Exception e){}
	  }
	  System.out.println(Thread.currentThread().getName()+
		  "消费======="+this.name);
	  flag = false;
	  this.notifyAll();
	}
}

//定义生产者类,实现接口,覆盖run方法
class Pro implements Runnable
{
    private Rescourc r ;
	Pro(Rescourc r){ this.r = r;}
	public void run(){
	   while(true){
	       r.set("鼠标");
	   }
	}
}
//定义消费者类,实现接口,覆盖run方法
class Cus implements Runnable
{
	private Rescourc r ;
	Cus(Rescourc r){ this.r = r;}
	public void run(){
	   
	   while(true){
	      r.get();
	   }
	}
}
class ProCusDemo 
{
	public static void main(String[] args) 
	{
		Rescourc r = new Rescourc();//资源产品
		Pro p = new Pro(r);//生产者
		Cus c = new Cus(r);//消费者

		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		Thread t3 = new Thread(p);
		Thread t4 = new Thread(c);

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

          程序Rescourc类的run方法使用的是while(flag==true)语句而不是if语句,因为while语句要反复判断标记,而if不是。this.notifyAll()在唤醒线程时使用的是notifyAll()包括唤醒所有线程包括不同操作方的线程,而notify()只唤醒最先wait的线程容易造成所有线程进入wait。
八、停止线程

        1,由于stop方法已经过时,因此停止线程的方法只有一种就是让run方法结束,开启多线程运行,运行的代码通常都是循环结构,因此只要控制循环就可以让run方法结束,也就是线程结束。

class StopThread implements Runnable
{
	private boolean flag=true;

	public void run()
	{
		while(flag)
			System.out.println(Thread.currentThread().getName()+" run");
	}
	public void changeFlag()
	{
		flag=false;
	}
}
class StopThreadDemo 
{
	public static void main(String[] args) 
	{
		StopThread st= new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		
		t1.start();
		t2.start();
		int n=0;
		while(true)
		{
			if(n++==60)
			{
				st.changeFlag();
				break;
			}	
			System.out.println(Thread.currentThread().getName()+" run--------"+n);
		}
		
			
		System.out.println("over");
	}
}

        主线程执行if语句当n=60时st对象调用changeFlag方法将标记变为false从而使run方法中while语句的标记变为false,使while语句结束循环,结束线程。

        2,当线程进入冻结状态将读不到标记,线程就不会结束,使程序挂起,因此当没有指定方式让线程恢复到运行状态时,就需要对冻结进行清除,强转让线程恢复到运行状态,在对标记进行判断,从而使循环结束,线程结束。

              2.1,Thread类的interrupt()方法解决

class StopThread implements Runnable
{
	private boolean flag=true;

	public synchronized void run()
	{
		while(flag)
			try
			{
				wait();
			}
			catch (Exception e)
			{
				System.out.println(Thread.currentThread().getName()+" exception");
			}
			
			System.out.println(Thread.currentThread().getName()+" run");
	}
	public void changeFlag()
	{
		flag=false;
	}
}
class StopThreadDemo 
{
	public static void main(String[] args) 
	{
		StopThread st= new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		
		t1.start();
		t2.start();
		int n=0;
		while(true)
		{
			if(n++==60)
			{
				st.changeFlag();
				t1.interrupt();
				t2.interrupt();
				break;
			}	
			System.out.println(Thread.currentThread().getName()+" run--------"+n);
		}
		
			
		System.out.println("over");
	}
}

        线程t1和t2执行wait()后进入冻结状态,因而读不到flag,所以程序结束不了,t1和t2在调用interrupt方法后回复到运行状态,执行while语句判断标记为false,结束循环,结束线程。

 

      



 

 

 ---------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IOS开发</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

详细请查看:<a href="http://edu.csdn.net" target="blank">http://edu.csdn.net</a>


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值