黑马程序员——Java基础之多线程

------ Java培训、Android培训、iOS培训、.Net培训 、期待与您交流! -------


一、进程与线程
进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。

多进程操作系统能同时运行多个进程(程序),由于 CPU 具备分时机制,所以每个进程都能循环获得自己的 CPU 时间片。由于 CPU 执行速度非常快,使得所有程序好象是在“同时”运行一样。
线程是比进程更小的执行单位,线程是进程内部单一的一个顺序控制流。

所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,形成多条执行线索。一个进程可能包含了多个同时执行的线程。
多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。

线程和进程的主要差别体现在以下两个方面:
①、同样作为基本的执行单元,线程是划分得比进程更小的执行单位。
②、每个进程都有一段专用的内存区域。与此相反,线程却共享内存单元(包括代码和数据),通过共享的内存单元来实现数据交换、实时通信与必要的同步操作。

多线程好处解决了多部分同时运行的问题还可以提高效率,非主应用(下载)。

多线程的弊端:线程太多回收效率的降低。

其实应用程序的执行都是CPU在做着快速的切换完成的。但是这个切换是随机的。

CPU执行到谁,谁就运行。调用线程的start方法:该方法有两个作用--启动线程--调用run方法。


二、创建多线程

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

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

步骤:

①定义类继承Thread

②复写Thread类中的run方法

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

③调用线程的start方法

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


/*
 * 需求:创建两个线程,和主线程交替运行
 * 线程都有自己默认的名称,名称为:Thread-编号  (编号从0开始)
 * 
 * static Thread currentThread():获取当前线程对象
 * getName():获取线程名称
 * 设置线程名称:setName或者构造函数
 */
package thread;
//创建线程Test继承Thread类
class Test extends Thread
{
	private String name;
	Test(String name)
	{
		this.name=name;
		//super(name);
	}
	//复写run方法
	public void run()
	{
		for(int x=0;x<70;x++)
		{
			System.out.println(this.getName()+"---run---"+x);
			//System.out.println(Thread.currentThread().getName()+"---run---"+x);
		}
	}
}


public class ThreadTest1 extends Thread
{
	
	
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		Test t1=new Test("One");//创建线程t1
		Test t2=new Test("Two");
		t1.start();//开启线程t1
		t2.start();
		
		for (int x=0;x<70;x++)
		{
			System.out.println("main..."+x);
		}

	}

}

运行结果:




发现运行结果每一次都不一样

因为,多个线程都获取CPU的执行权,CPU执行到谁,谁就运行;

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

这就是多线程的一个特性:随机性

 

为什么覆盖run方法?

Thread类用于多线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法;也就是说,Thread类中的run方法,用于存储线程要运行的代码

 

Start()run()的特点:

Start()开启线程并执行该线程的run方法

run()仅仅是对象调用方法,而线程创建了,并没有执行 

 

 

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

步骤:

1,自定义类实现Runnable接口

2,覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中

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

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

为什么要将Runnable接口的子类对象作为实际参数传递给Thread的构造函数?

因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法,就必须明确该run方法的对象。

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


售票程序例子:

<pre name="code" class="java">/*
 * 需求:简单的多窗口同时售票程序
 */
package thread;
class Ticket implements Runnable
{
	private int tick=100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				//打印线程名及票数
				System.out.println(Thread.currentThread().getName()+".....sale: "+tick--);
			}
		}
	}
}
public class SaleTicket
{
	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		//创建Runnable接口子类的实例对象
		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.start();//启动线程
		t2.start();
		t3.start();
		t4.start();
	}
}


 

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

实现方式的好处:避免了单继承的局限性

在定义线程时,建立使用实现方式

两种方式的区别:

继承Thread:线程代码存放在Thread子类run方法中

实现Runnable,线程代码存放在接口的子类run方法

三、多线程的几种状态

       创建状态:等待启动,调用start启动。

         运行状态:具有执行资格和执行权。

         临时状态(阻塞):有执行资格,但是没有执行权。

         冻结状态:遇到sleeptime)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。

         消忙状态:stop()方法,或者run方法结束。

注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。

四、多线程的安全问题

出现问题的原因:

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

解决办法:

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

Java对于多线程的安全问题提供了专业的解决方式。

——同步代码块

synchronized(对象)

{

需要被同步的代码;

}

对象如同锁,持有锁的线程可以在同步中执行

没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有锁

 

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

同步的前提:

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

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

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

好处:解决了多线程的安全问题

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

 

——同步函数

访问控制符 synchronized 返回值类型 方法名称(参数)
{
…. ;
}

class TestThread implements Runnable{
	private int tickets=20;
	public void run(){
		while(true){
			<span style="white-space:pre">	</span>sale() ;
		}
	}
public synchronized void sale(){
	if(tickets>0){
		<span style="white-space:pre">	</span>try{
				Thread.sleep(100);
		   <span style="white-space:pre">	</span>}
		<span style="white-space:pre">	</span>catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"出售票"+tickets--);
		}
	}
}


同步函数的锁是this

因:函数需要被对象调用,那么函数都一个所属对象引用,就是this

 

静态同步函数

静态方法中不可以定义this,所以锁不可能是this

静态同步函数的锁是是class对象

静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型是class

故:静态同步方法是用的锁是该方法所在类的字节码文件对象:类名.class


多线程—单例设计模式—懒汉式

class Single
{
	private static Single s=null;
	private Single(){}
	public static Single getInstance()
	{
		if(s==null)//双重判断的方式能解决效率问题
		{
			synchronized(Single.class)//使用的锁是该类的字节码文件对象:类名d.class
			{
				if(s==null)
					s=new Single();
			}
		}
		return s;
	}
}

public class ThreadSingleLazy {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.println("多线程——单例设计模式——懒汉式");
	}


 

五、死锁

一旦有多个进程,且它们都要争用对多个锁的独占访问,那么就有可能发生死锁。如果有一组进程或线程,其中每个都在等待一个只有其它进程或线程才可以执行的操作,那么就称它们被死锁了。



<pre name="code" class="java">/*
 * 死锁程序
 */

package thread;

//定义一个类来实现Runnable,并复写run方法
class LockTest implements Runnable
{
	private boolean flag;
	LockTest(boolean flag)
	{
		this.flag=flag;
	}
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)//a锁
				{
					System.out.println(Thread.currentThread().getName()+"------if_locka");

					synchronized(MyLock.lockb)//b锁
					{
					System.out.println(Thread.currentThread().getName()+"------if_lockb");
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockb)//b锁
				{
				  System.out.println(Thread.currentThread().getName()+"------else_lockb");

					synchronized(MyLock.locka)//a锁
					{
				   System.out.println(Thread.currentThread().getName()+"------else_locka");
					}
				}
			}
		}
	}
}

//定义两个锁
class MyLock
{
	static Object locka = new Object();
	static Object lockb = new Object();
}

class DeadLock
{
	public static void main(String[] args)
	{
		//创建2个进程,并启动
		new Thread(new LockTest(true)).start();
		new Thread(new LockTest(false)).start();
	}
}


 

要避免死锁,应该确保在获取多个锁时,在所有的线程中都以相同的顺序获取锁。

六、多线程间通讯

多线程间通信就是:多个线程在操作同一个资源,但是操作的动作不一样。

等待唤醒机制

------>wait();将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

------>notify();唤醒线程池中某一个等待线程。

------>notifyAll();唤醒的是线程池中的所有线程。

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

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

为什么这些操作线程的方法要定义Object类中呢?唯一这些方法在操作同步中的线程时,都必须要标 识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。

不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

多线程间通讯范例:
/*
 * 线程间通讯:
 * 其实就是多个线程操作同一对象,但是操作的动作不同
 */

 package thread;
 
 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();//唤醒等待
	 }
	 public synchronized void out()
	 {
		 if(!flag)
			 try{this.wait();}catch(Exception e){}//如果木有资源,等待存入资源 
		 System.out.println(name+"------"+sex);//打印表示取出
		 flag=false;//已取出 资源
	     this.notify();
	 }
 }
//存储线程  
 class Input implements Runnable
 {
	 private Res r;
	 Input (Res r)
	 {
		 this.r=r;
	 }
	 public void run()
	 {
		 int x=0;
		 while(true)
		 {
			 if(x==0)
				 r.set("李雷Mike", "male");
			 else
				 r.set("韩梅梅Lily", "female");
			 x=(x+1)%2;//交替打印的控制语句
		 }
		 
	 }
	 
 }
 //取出线程
 class Output implements Runnable
 {
	 private Res r;
	 Output (Res r)
	 {
		 this.r=r;
	 }
	 public void run()
	 {
		 while(true)
		 {
			r.out();
		 }
		 
	 }
	 
 }

public class ThreadCommunication 
{

	public static void main(String[] args) 
	{
		// TODO Auto-generated method stub
		Res r=new Res();//建立资源,表示操作同一资源
		new Thread(new Input(r)).start();//新建并开启存储线程
		new Thread(new Output(r)).start();//新建并开启取出线程

	}

}


升级JDK1.5中提供了多线程升级解决方案。

将同步synchronized 替换成了显示的Lock操作。

Object中的wait()//notify()//notifyAll(),替换了Condition对象。

该对象可以Lock锁 进行获取。

停止线程:

Stop()过时了。

只有一种方法:run()方法结束。

① 开始多线程运行,运行代码通常都是循环结构。

只要控制住循环,就可以让run方法结束,也就是线程结束。

②特殊情况:

当线程处于了冻结状态。

就不会读取到标记,那么线程就不会结束。

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


lock范例:

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

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

import java.util.concurrent.locks.*;

class Resource
{
	private String name;
	private int count = 1;
	private boolean flag = false;

	private Lock lock = new ReentrantLock();

	private Condition condition_pro = lock.newCondition();//一个锁可对应多个condition
	private Condition condition_con = lock.newCondition();

	public void produce(String name)throws InterruptedException
	{
		lock.lock();//获取锁
		try
		{
			if(flag)
				condition_pro.await();//本方等待,需要抛出异常

			this.name = name+"---"+count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;

			condition_con.signal();//唤醒对方
		}
		finally
		{
			lock.unlock();//解锁,一定执行
		}	
	}

	public synchronized void consume()throws InterruptedException
	{
		lock.lock();
		try
		{
			if(!flag)
			condition_con.await();

			System.out.println(Thread.currentThread().getName()+".....消费者....."+this.name);
			flag = false;

			condition_pro.signal();
		}
		finally
		{
			lock.unlock();
		}
	}
}

class Producer implements Runnable
{
	private Resource r;
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{		
			try
			{
				r.produce("商品");
			}
			catch (Exception e)
			{
			}	
		}
	}
}

class Consumer implements Runnable
{
	private Resource r ;
	Consumer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			try
			{
				r.consume();
			}
			catch (Exception e)
			{
			}	
		}
	}
}

class  ProducerConsumerDemoPro
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();//建立资源

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

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


Join:

A线程执行到了B线程的。Jion()方法,A就会等待。等B线程都执行完,A才会执行。

Join可以用来临时加入线程执行。(比较少使用。Join跑出了异常,需要注意。)

优先级和yield

优先级表示的是,运行频率。常用:1510.对应的是:MAX_PRIORITYMINNORM

Yield:表示的是暂停当前正在执行的线程对象,并执行其他线程。

public static void yield()

独立运算,相互之间不相干的时候单独封装下即可。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值