黑马程序员_ 多线程

------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
多线程

进程

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

线程

就是进程中一个独立的控制单元,线程在控制着进程的执行
 
一个进程中至少有一个线程

java VM  启动时会有一个进程java.exe

    该程序中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程

如何在自定义的代码中,自定义一个线程呢

   通过对api的查找,java已经提供了对线程这类事物的描述。就是Thread类

创建线程的第一种类:继承Thread类

步骤:

1,定义类继承Thread

2,复写Thread类中的run方法

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

3,调用start方法

    该方法有两个作用:启动线程,调用run方法

    发现运行结果每一次都不同,因为多个线程都获取cpu的执行权,cpu执行到谁,就运行谁

    明确一点,在某一个时刻,只能有一个程序在运行(多核除外)

    cpu在做着快速的切换,以达到看上去是同时运行的结果

这就是多线程的一个特性:随机切换。谁抢到谁执行,至于执行多长,cpu说了算

为什么要覆盖run方法呢

    Thread类用于描述线程

    该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法

线程创建示例

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




class ThreadDemo
{
	public static void main(String[] args)
	{
		//for( int x=0; x<40000; x++)
			//System.out.println("hello java");
		Demo d = new Demo();//创建好一个线程
		//d.start();//开启线程并执行该线程的run方法
		d.run();//仅仅是对象调用方法。而线程创建了,并没有运行
		for( int x=0; x<60; x++)
			System.out.println("hello Thread "+x);

	}
}


线程创建练习

/*
练习:创建两个线程,和主线程交替运行。

原来线程都有自己默认的名称

Thread-编号 该编号从0开始

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

设置线程名称:使用setName或者构造函数

*/

class Test extends Thread
{
	Test(String name)
	{
		//调用父类的构造函数
		super(name);
	}
	public void run()
	{
		for( int x=0; x<60; x++)
			//打印当前线程的名称
			System.out.println((Thread.currentThread()==this)+" "+this.getName()+" test run "+x);
	}
}


class ThreadTest
{
	public static void main(String[] args)
	{
		//创建线程对象
		Test t1 = new Test("one");
		Test t2 = new Test("two");
		//开启线程
		t1.start();
		t2.start();
		//t1.run();
		//t2.run();
		
		for( int x=0; x<60; x++)
		{
			System.out.println("main "+x);
		}
	}
}


通过一个卖票程序来演示创建线程的第二种方法:实现Runnable接口

/*
 需求:简单的卖票程序
多个窗口买票

创建线程的第二种方式:实现Runnable借口

步骤:
1,定义类实现Runnable借口
2,覆盖Runnable接口中的run方法
	将线程要运行的代码存放在run方法中
3,通过Thread类建立线程对象
4,将Runnable类接口的子类对象作为实际参数传递给Thread类的构造函数
	为什么要将Runnable接口的子类对象传递给Thread的构造函数?
		因为自定义的run方法所属的对象是Runnable接口的子类对象
		所以要让线程去指定对象的run方法;就必须明确该方法所属的对象

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

实现方式和继承方式有什么区别?

实现方式好处在于:避免了单继承的局限性
在定义线程时,建议使用实现形式

两种方式的另一区别:
继承Thread类:线程代码存放在Thread子类run方法中
实现Runnable:线程代码存放在接口子类的run方法中


多线程的运行容易出现问题

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

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

java对于多线程的安全问题提供了专业的解决方式
就是同步代码块

synchronized(对象)
{
	需要被同步的代码;
}

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

必须保证同步中只能有一个线程运行

好处:解决多线程的安全问题
弊端:多个线程都需要判断锁,较为消耗资源

*/

package TicketDemoPack;

class Ticket implements Runnable //extends Thread
{
	private static int tick=100;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{	
			synchronized(obj)
			{
				if(tick>0)
				{
					
					try
					{
						Thread.sleep(10);
					}
					catch(Exception e)
					{}
					System.out.println(Thread.currentThread().getName()+" sale "+tick--);
				}
			}
		}
	}
}

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

		//Ticket t1 = new Ticket();
		//Ticket t2 = new Ticket();
		//Ticket t3 = new Ticket();
		//Ticket t4 = new Ticket();
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	
	}
}


 

检测该程序是否有安全问题

如何找到问题?

    1,明确哪些代码是多线程运行代码

    2,明确共享数据

    3,明确多线程运行代码中哪些语句是操作共享数据的

线程间通讯

其实就是多个线程在操作同一个资源,但是操作的动作不同

wait()

notify()

notifyAll()

都使用在同步中,因为要对持有监视器(锁)的线程进行操作

要使用在同步中,因为只有同步才具有锁

为什么这些操作线程的方法要定义在Object类中呢

    因为这些方法在操作同步中的线程时,都必须表示它们所操作的线程只有的锁

只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说等待和唤醒必须是同一把锁

锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中

线程通信代码示例

class Rec
{
	private String name;
	private String sex;
	boolean flag = false; //定义标志
	//定义设置变量的同步函数
	public synchronized void set(String name,String sex)
	{
		//如果flag==true,线程等待	
		if(flag)
			try{this.wait();} catch(Exception e){}
		//name和sex为多个线程的共享数据,不同的线程执行时会造成安全问题
		this.name = name;
		this.sex = sex;
		flag = true;
		//唤醒线程
		this.notify();
	}
	public synchronized void print()
	{	
			if(!flag)
				try{this.wait();} catch(Exception e){}
			System.out.println(name+"  "+sex);
			flag = false;
			this.notify();
	}
}

class Input implements Runnable
{
	private Rec r;
	Input(Rec r)
	{
		this.r = r;
	}
	
	public void run()
	{
		int x = 0;
		while(true)
		{					
				if(x==0)
					r.set("mike","man");
				else
					r.set("sherry","women");
				x = (x+1)%2;
		}
	}
}

class Output implements Runnable
{
	private Rec r;
	Output(Rec r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.print();
		}
	}
}



class Communicate 
{
	public static void main(String[] args) 
	{
		Rec r = new Rec();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
	}
}


生产者和消费者示例

 

//导入所需要的包
import java.util.concurrent.locks.*;

/*
对于多个生产者和消费者
为什么要使用while循环判断标记?
	为了让被唤醒的线程再一次判断标记

为什么定义notifyAll()?
	因为需要唤醒对方线程,使用notify容易出现只唤醒本方线程的情况,导致程序中所有的线程都等待
*/

//定义一个资源类
class Resource
{
	private String name;

	//定义一个计数器
	private int count = 1;

	//定义标志,作为判断等待和唤醒的条件
	private boolean flag = false;

	//创建一个 ReentrantLock 的实例
	private Lock lock = new ReentrantLock();

	//返回绑定到此ReentrantLock实例的新 Condition 实例,用来冻结本方线程和唤醒对方的线程
	private Condition condition_pro = lock.newCondition();
	private Condition condition_con = lock.newCondition();

	public void set(String name) throws InterruptedException
	{
		//获取锁
		lock.lock();
		try
		{
			while(flag)
				//当标志位真时,使当前生产者线程等待
				condition_pro.await();
			this.name = name+"  "+count++;
			System.out.println(Thread.currentThread().getName()+"  producer"+this.name);
			flag = true;

			//唤醒消费者线程
			condition_con.signal();
		}
		//避免某个线程抛出异常,功能结束后还占有锁,将释放锁的语句放到finally中
		finally
		{
			//释放锁
			lock.unlock();
		}
		
		
	}
	public void print()throws InterruptedException
	{
		lock.lock();
		try
		{
			while(!flag)
				condition_con.await();
			System.out.println(Thread.currentThread().getName()+"    cunsumer"+this.name);
			flag = false;
			condition_pro.signal();
		}
		finally
		{
			lock.unlock();
		}
		
	}
}

//定义生产者类,实现Runnable接口
class Producer implements Runnable
{
	private Resource r;
	//构造函数初始化
	Producer(Resource r)
	{
		this.r = r;
	}
	//覆盖run方法
	public void run()
	{
		while(true)
		{
			try
			{
				r.set("commodity");
			}
			catch (InterruptedException e)
			{
			}	
		}
	}

}

//定义消费者类,实现Runnable接口
class Consumer implements Runnable
{
	private Resource r;
	//构造函数对Resource对象进行初始化
	Consumer(Resource r)
	{
		this.r = r;
	}
	//覆盖run方法
	public void run()
	{
		while(true)
		{
			try
			{
				r.print();
			}
			catch (InterruptedException e)
			{
			}	
		}
	}

}



class ProConDemo 
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();
		new Thread(new Producer(r)).start();
		new Thread(new Producer(r)).start();

		new Thread(new Consumer(r)).start();
		new Thread(new Consumer(r)).start();
	}
}

/*
JDK1.5 提供了多线程升级解决方案
将同步synchronized替换成了Lock操作
将Object中的wait(),notify(),notifyAll()替换成了condition对象,该对象可以通过Lock锁获取

该示例中,实现了本方只唤醒对方的操作

*/


Thread类的其他方法

/*
stop方法已经过时
如何停止线程?
只有一种run方法结束
开启多线程运行,运行代码通常是循环结构
只要控制住循环就能让run方法结束

特殊情况:
当线程处于冻结状态
就不会读取到标记,那么线程就不会结束

当没有指定的方式让冻结的线程恢复到运行状态时,需要对冻结状态进行清除
强制让线程恢复到运行状态中来,这样就可以操作标记,来让循环结束

Thread类中提供了interrupt()方法


*/


class StopThread implements Runnable 
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(flag)
		{
			try
			{
				wait();
			}
			catch (InterruptedException e)
			{
				//中断导致抛出异常后,将标志改变,用于下次循环的判断
				System.out.println(Thread.currentThread().getName()+"  "+"Exception");
				flag = false;
			}
			System.out.println(Thread.currentThread().getName());
		}
		
	}
	public void changeFlag()
	{
		flag = false;
	}
}

class StopThreadDemo
{
	public static void main(String[] args) 
	{
		//定义StopThread对象
		StopThread st = new StopThread();

		//创建线程对象
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);

		//开启线程
		t1.start();
		t2.start();
		
		int num = 0;
		while(true)
		{
			//判断循环结束的条件,循环结束后主线程执行完毕
			if(num++ == 60)
			{
				//st.changeFlag();

				//中断t1线程
				t1.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"		"+num);
		}
	}
}



 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值