黑马程序员----Java多线程

本文介绍了Java中的多线程概念,包括进程与线程的区别、线程的创建方式(继承Thread类和实现Runnable接口)、线程状态、线程同步与通信、线程安全问题及解决方法,以及线程的停止和相关方法。
摘要由CSDN通过智能技术生成
                                                     ------ Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


多线程

一、概述

           了解多线程前我们先明确一下线程的概念。

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

           线程:就是进程中的一个独立的控制单元,线程控制着进程的执行。由此可知,一个进程至少有一个线程。

           Java虚拟机启动时会有一个进程java.exe。该进程中至少有一个线程负责java程序的执行。而且这个程序运行的代码存在于与main方法中,这个线程称之为主线程。另外其实jvm启动不只一个线程,还有一个处理垃圾回收机制的线程,因为当产生垃圾时,主线程在执行自己的功能,不能通知去执行垃圾回收所以会有一个线程执行垃圾回收。

            多线程存在意义:可以使多部代码同时运行。

二、线程的创建方式

创建线程的第一种方式:继承Thread类,创建步骤;

            1.创建类继承Thread类。

            2.复写Thread类中的run方法。

            3.调用线程中的start方法,这个方法得作用启动线程,调用run方法。

下面就是一个简单的线程创建示例:

package xiaobing.xiancheng;
class Demo extends Thread //创建线程
{
	public void run(){
		for(int x=0;x<40;x++)
			System.out.println("demo run---"+x);
	}
}
public class ThreadDemo01 {
	public static void main(String[] arrgs)
	{
		Demo d=new Demo();//创建线程实例
		d.start();//启动线程
		for(int x=0;x<60;x++)
		{
			System.out.println("Hello World---"+x);
		}
	}
}

以上示例我们发现结果不规则,这个其实是2个线程在同时获取Cpu的执行权,哪个获取到了,哪个线程就执行。 在某一个时刻,只能有一个程序在运行(多核除外)。

            为什要覆盖run方法呢?因为Thread类用于描述线程,run方法中就存储了线程运行的代码。

线程的运行状态:

               被创建,调用start()方法。

               运行,此时该线程具备执行资格和执行权。

               冻结,等待被唤醒(wait),sleep时间到,此时已经放弃了执行资格。

                消亡,stop(),run方法结束。

特殊的状态:临时阻塞状态,具备运行资格,但没有执行权,冻结结束后先进入这个状态。

线程名称的获取,通过Thread类中的获取方法,Thread.currentThread.getName(),可以防发现线程都有自己默认的名称为Thread-编号,从0开始。

 创建线程的第二种的方式,实现Runable接口,创建步骤如下:

                 1.定义类实现Runnable接口

                 2.覆盖Runnble中的run方法,存放运行的线程代码。

                 3.通过Thread类建立线程对象

                 4.将Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。这是因为run所属的对象时接口的子类对象,所以要让线程去指定对象的run方法。

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

实现Runnble接口的好处避免了单继承的局限性,类可以多实现而不能多继承。

下面是一个简单的买票例子:

package xiaobing.xiancheng;
/*
 * 需求:简单的买票程序
 * 多个窗口同时买票
 * 
 */
class Ticket implements Runnable//extends Thread
{
	private static int tick=100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
			System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
			}
		}
	}
}
public class ThreaDemo03 {
	public static void main(String[] args)
	{
		Ticket t=new Ticket();//此时并没有创建线程
		Thread t1=new Thread(t);//将实现接口的类作为参数传入Thread类的构造函数中以便调用start方法 
		Thread t2=new Thread(t);
		Thread t3=new Thread(t);
		Thread t4=new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		//启动Start方法
		
		/*
		 * Ticket t1=new Ticket();
		 *
		Ticket t2=new Ticket();
		Ticket t3=new Ticket();
		Ticket t4=new Ticket();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
*/
	}
}

安全问题:

 以上例子我们发现如果在执行方法假如sleep时间时居然卖出了0,-1,-2的票,这时程序就出现了安全问题 。

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

解决办法:对操作共享数据的语句只能让一个线程都执行完,其他线程不可以参与进行。这时就用到同步代码块解决,格式如下:

synchronized(对象)

{

        需要被同步的代码(哪些语句在操作共享数据)

}

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

同步的弊端:操作数据需在进行一步判断,所以比较耗费系统资源。

同步锁所使用对象的描述验证:

package xiaobing.xiancheng;
/*
 * 验证同步函数的锁是this!
 * 函数需要被对象调用,那么函数都有一个所属对象引用,就是this
 * 验证方式:使用两个线程来卖票
 * 一个线程在同步代码块中,一个在同步函数中,都执行卖票动作,如果正常同步
 * 那卖票不会出现问题
 * 发现卖出了0号票
 * 将obj改为this后运行正常,证明锁为this
 * 》》》注意当函数被静态描述后同步函数的锁为程序的字节码文件Class对象。类名.Class
 */
class Ticket1 implements Runnable
{
	private static int tick=1000;
	Object obj=new Object();
	boolean flag=true;//用于判别进入同步函数还是同步代码块
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(this)//定义同步代码块
				{
					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--);
		}
	}
	
}
public class ThreadDemo05 {
	public static void main(String[] args)
	{
		Ticket1 t=new Ticket1();//此时并没有创建线程
		Thread t1=new Thread(t);//将实现接口的类作为参数传入Thread类的构造函数中以便调用start方法 
		Thread t2=new Thread(t);
		//Thread t3=new Thread(t);
		//Thread t4=new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}//让第一个线程sleep运行第二个线程
		t.flag=false;//用于切换
		t2.start();
		//t3.start();
		//t4.start();
	}
}


知识点:单例设计模式-懒汉式多线程注意:

package xiaobing.xiancheng;
/*
 * 单例设计模式
 * 当多个程序并发调用getInstance
 * 那么就有多个线程操作s,这就会出现创造多余的对象,不能保证内存的唯一性,就会产生安全隐患
 * 这时可以采取同步函数
 * 但是但是每个线程想要获取对象会判断锁的存在,程序会显得低效
 * 这时可以用双重判断解决这个问题
 */
//懒汉式设计模式
class Single
{
	private static Single s=null;
	private Single(){}
	public static /*synchronized*/Single getInstance()
	{
		if(s==null)
		{
			synchronized(Single.class)
			{
				if(s==null)
					s=new Single();
				}
			}
		return s;
	}
	
}
public class ThreadDemo06 {

}

三、线程间通信


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

实现方法:

        将操作的资源封装为对象,同时执行的任务也封装为对象,实现多线程是同步锁使用唯一的资源对象就可以了。

等待唤醒机制:

方法:wait()将同步中的线程处于冻结状态,放弃执行权和资格 ,notify()唤醒正在等待的线程notifyAll唤醒所有线程。

实例:

package xiaobing.xiancheng;
/*
 * wait
 * notify()
 * nitifyAll()
 * 这些方法都是用在同步中,因为要对持有监视器((锁)的线程操作
 * 所以要使用在同步中,因为只有同步才具有锁
 * 为什么操作线程的方法在Object类中呢?
 * 因为这些方法在操作同步中的线程时,都必须要标示所操作线程只有的锁
 * 只有同一个锁上的被等待线程才能被同一个锁上的notify锁唤醒,不可以
 * 对不同锁中的线程进行唤醒
 * 也就是说,等待和唤醒必须是同一个锁,而锁可以使任意对象,所以
 * 可以被任意对象调用的方法定义在Object类中。
 */
class Res//建立操作资源
{
	private String name;
	private String sex;
	boolean flag=false;
	public synchronized void set(String name,String sex)//将同步数据用同步函数描述
	{
		if(flag)
			try{this.wait();}catch(Exception e){}//为false时程序进入等待

		this.name=name;
		this.sex=sex;
		flag=true;//改变状态是其他的线程唤醒
        this.notify();
	}
	public synchronized void out(){
		if(!flag)
			try{this.wait();}catch(Exception e){}
		System.out.println(name+"...."+sex);
		flag=true;
        this.notify();
	}
}
class Input implements Runnable
{
		private Res r;
		//Object obj=new Object();
		Input(Res r)
		{
			this.r=r;
		}
	
		public void run()
		{
			int x=0;
			while(true)
			{
				if(x==0)
					r.set("mike", "man");
				else
					r.set("丽丽", "女");
				x=(x+1)%2;
			}
		}
}
class Output implements Runnable
{
	private Res r;
	//Object obj=new Object();
	Output(Res r)
	{
		this.r=r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}
class ThreadDemo07 {
	public static void main(String[] args)
	{
		Res r=new Res();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
		/*Input in=new Input(r);
		Output out=new Output(r);
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		t1.start();
		t2.start();
		*/
	}

线程中的其他方法:

线程的停止:通过stop方法就可以停止线程。这个方式过时了。

停止线程:原理就是:就是结束run方法。

怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

第一种方式:定义循环的结束标记。

第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

 

java.lang.Thread 

interrupt():中断线程。

setPriority(int newPriority):更改线程的优先级。

getPriority():返回线程的优先级。

toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Jvm退出。该方法必须在启动线程前调用



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值