黑马程序员—多线程

----------------------   JavaEE+Android、Java培训、期待与您交流! ----------------------


进程

进程是处于运行过程中的程序,并且具有一定独立功能,进程是系统进行资源分配和调度的一个独立单位。一般而言,进程包含独立性,动态性和并发性。并行性是指同一时刻,有多条指令在多个处理器上同时执行;并发性是指同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果。


线程

线程也被称作轻量级进程,线程是进程的执行单元。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程是共享父进程中的共享变量及部分环境,相互之间协同来完成进程所要完成的任务。
注:在java中,JVM虚拟机启动时,会有一个进程为java.exe,该程序中至少有一个线程负责java程序的执行;而且该程序运行的代码存在于main方法中,该线程称之为主线程。其实,JVM启动时不止有一个线程(主线程),由于java是具有垃圾回收机制的,所以,在进程中,还有负责垃圾回收机制的线程。

线程的创建和启动


创建新执行线程有两种方法:

一种是继承Thread类创建线程类;另一种是实现Runnable接口创建线程类。


继承Thread类步骤:

a、定义类继承Thread。

b、复写Thread类中的run方法(run方法里存放的是线程运行的代码,因为Thread中的run方法原本是空的方法,所以要被复写)。

c、调用线程的start方法(start方法:启动线程,并调用run方法,如果直接调用run方法,仅仅是对象调用方法,线程并没有运行)。

<span style="font-family: Arial; ">class Demo extends Thread
{
	public void run()
	{
		for (int i=0;i<60;i++)
			System.out.println(Thread.currentThread().getName() + "demo run---" + i);
	}
}
class Test2
{
	public static void main(String[] args) 
	{
		Demo d1 = new Demo();//创建一个对象就创建好了一个线程
		Demo d2 = new Demo();
		d1.start();//开启线程并执行run方法
		d2.start();
		for (int i=0;i<60;i++)
			System.out.println("Hello World!---" + i);
	}
}</span>

运行特点:

A.并发性:我们看到的程序(或线程)并发执行,其实是一种假象。有一点需要明确:;在某一时刻,只有一个程序在运行(多核除外),此时cpu是在进行快速的切换,以达到看上去是同时运行的效果。由于切换时间是非常短的,所以我们可以认为是在并发进行。

B.随机性:在运行时,每次的结果不同。由于多个线程都在获取cpu的执行权,cpu执行到哪个线程,哪个线程就会执行。可以将多线程运行的行为形象的称为互相抢夺cpu的执行权。这就是多线程的特点,随机性。执行到哪个程序并不确定。


实现Runnable接口步骤:

a、定义类实现Runnable接口。

b、覆盖Runnable接口中的run方法。

c、通过Thread类建立线程对象。要运行几个线程,就创建几个对象。

d、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。(因为自定义的run方法所属对象为Runnable接口的子类对象,所以让线程指定对象的run方法,就必须明确该run方法所属的对象。)

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


<span style="font-family: Arial; ">
//多个窗口同时卖票
class Ticket implements Runnable
{
	private int tic = 20;
	public void run()
	{
		while(true)
		{
			if (tic > 0)
				System.out.println(Thread.currentThread().getName() + "sale:" + tic--);
		}
	}
}

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.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
</span>

实现方式与继承方式有何区


1、实现方式:避免了单继承的局限性。

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

2、区别:

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

实现Runnable:线程代码存在接口的子类run方法中。

注意:局部变量在每一个线程中都独有一份。


Thread类中的一些方法

1、获取线程名称:getName()
每个线程都有自己默认的名称,获取格式:对象.getName();打印后,显示为:Thread-编号(从0开始),也就是说,线程一为:Thread-0,线程二为:Thread-1。也可以获取当前线程对象的名称,通过currentThread().getName()。

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

可以通过setName()设置线程名称,或者通过含有参数的构造函数直接显式初始化线程的名称,如Test(String name)。


线程的运行状态

a、被创建      b、运行      c、阻塞       d、冻结     e、消亡     

多线程的安全问题

当存在共享数据时,由于多个线程访问时状态的不确定性导致共享数据出错。
解决方法:
a、同步代码块
<span style="font-family: Arial; ">
synchronized(对象)//对象称为锁旗标
{
	需要被同步的代码
}</span><span style="font-family: SimSun; font-size: 14px; ">
</span>
其中的对象如同锁,持有锁的线程可在同步中执行,没有锁的线程,即使获得cpu的执行权,也进不去,因为没有获取锁,是进不去代码块中执行共享数据语句的。
b、同步函数
同步函数就是将修饰符synchronized放在返回类型的前面
非静态同步函数中的锁---> this
<span style="font-family: Arial; ">
class Ticket implements Runnable
{
	private int tic = 100;
	boolean flog = true;
	public void run()
	{
		if (flog)
		{
			//线程一执行
			while(true)
			{
				//如果对象为obj,则是两个锁,是不安全的;换成this,为一个锁,会安全很多
				synchronized(this)
				{
					if (tic > 0)
						System.out.println(Thread.currentThread().getName() + "--cobe--:" + tic--);
				}
			}
		}
		//线程二执行
		else
			while(true)
				show();
	}
	public synchronized void show()
	{
		if (tic > 0)
			System.out.println(Thread.currentThread().getName() + "----show-----:" + tic--);
	}
}

class ThisLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);//创建一个线程
		Thread t2 = new Thread(t);//创建一个线程
		t1.start();
		t.flog = false;//开启线程一,即关闭if,让线程二执行else中语句
		t2.start();
	}
}</span>
c、静态同步函数

静态同步函数中的锁:

如果同步函数被静态修饰后,经验证,使用的锁不是this了,因为静态方法中不可定义this,所以,这个锁不再是this了。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class;该对象的类型是Class。


多线程间的通信

生产者和消费者的例子:
/*
线程间通信:

等待唤醒机制:升级版

生产者消费者  多个

*/
import java.util.concurrent.locks.*;

class ProducerConsumerDemo{
	public static void main(String[] args){
		Resouse r = new Resouse();
		Producer p = new Producer(r);
		Consumer c = new Consumer(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();
	}
}

class Resouse{
	private String name;
	private int count = 1;
	private boolean flag =  false; 
	private Lock lock = new ReentrantLock();
	private Condition condition_P = lock.newCondition();
	private Condition condition_C = lock.newCondition();
//要唤醒全部,否则都可能处于冻结状态,那么程序就会停止。这和死锁有区别的。
	public void set(String name)throws InterruptedException{
		lock.lock();
		try{
			while(flag)//循环判断,防止都冻结状态
				condition_P.await();
			this.name = name + "--" + count++;
			System.out.println(Thread.currentThread().getName() + "..生成者--" + this.name);
			flag = true;
			condition_C.signal();
		}finally{
			lock.unlock();//释放锁的机制一定要执行
		}		
	}
	public void out()throws InterruptedException{
		lock.lock();
		try{
			while(!flag)//循环判断,防止都冻结状态
				condition_C.await();
			System.out.println(Thread.currentThread().getName() + "..消费者." + this.name);
			flag = false;
			condition_P.signal();//唤醒全部
		}finally{
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Resouse r;
	Producer(Resouse r){
		this.r = r;
	}
	public void run(){
		while(true){
			try{
				r.set("--商品--");
			}catch (InterruptedException e){}
		}
	}
}

class Consumer implements Runnable{
	private Resouse r;
	Consumer(Resouse r){
		this.r = r;
	}
	public void run(){
		while(true){
			try{
				r.out();
			}catch (InterruptedException e){}
		}
	}
}

等待唤醒机制

一)等待唤醒机制:

1、显式锁机制和等待唤醒机制:

在JDK 1.5中,提供了改进synchronized的升级解决方案。将同步synchronized替换为显式的Lock操作,将Object中的wait,notify,notifyAll替换成Condition对象,该对象可对Lock锁进行获取。这就实现了本方唤醒对方的操作。在这里说明几点:

1)、对于wait,notify和notifyAll这些方法都是用在同步中,也就是等待唤醒机制,这是因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

2)、而这些方法都定义在Object中,是因为这些方法操作同步中的线程时,都必须表示自己所操作的线程的锁,就是说,等待和唤醒的必须是同一把锁。不可对不同锁中的线程进行唤醒。所以这就使得程序是不良的,因此,通过对锁机制的改良,使得程序得到优化。

3)、等待唤醒机制中,等待的线程处于冻结状态,是被放在线程池中,线程池中的线程已经放弃了执行资格,需要被唤醒后,才有被执行的资格。

2、对于上面的程序,有两点要说明:

1)、为何定义while判断标记:

原因是让被唤醒的线程再判断一次。

避免未经判断,线程不知是否应该执行,就执行本方的上一个已经执行的语句。如果用if,消费者在等着,两个生成着一起判断完flag后,cpu切换到其中一个如t1,另一个t3在wait,当t1唤醒冻结中的一个,是t3(因为它先被冻结的,就会先被唤醒),所以t3未经判断,又生产了一个。而没消费。

2)这里使用的是signal方法,而不是signalAll方法。是因为通过Condition的两个对象,分别唤醒对方,这就体现了Lock锁机制的灵活性。可以通过Contidition对象调用Lock接口中的方法,就可以保证多线程间通信的流畅性了。

二)Thread类中的方法简介:

在这简单介绍几个Thread中的方法:

1、停止线程:

在java 1.5之后,就不再使用stop方法停止线程了。那么该如何停止线程呢?只有一种方法,就是让run方法结束。

开启多线程运行,运行代码通常为循环结构,只要控制住循环,就可以让run方法结束,也就可以使线程结束。

注:

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

<span style="font-family: Arial; ">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() + "----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("Hello World!");
		}
	}
}</span>

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

在Thread类中提供了此种方法:interrupt()。此方法是为了让线程中断,但是并没有结束运行,让线程恢复到运行状态,再判断标记从而停止循环,run方法结束,线程结束。

<span style="font-family: Arial; ">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() + "----run");
		}
	}
}

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){
				t1.interrupt();
				t2.interrupt();
				break;
			}
			System.out.println("Hello World!");
		}
	}
}
</span>

2、守护线程:---setDaemon()

可将一个线程标记为守护线程,直接调用这个方法。此方法需要在启动前调用守护线程在这个线程结束后,会自动结束,则Jvm虚拟机也结束运行。

3、临时加入线程:--join()

特点:当A线程执行到B线程方法时,A线程就会等待,B线程都执行完,A才会执行。join可用来临时加入线程执行。

4、优先级:

setPriority():

在Thread中,存在着1~10这十个执行级别,最高的是 MAX_PRIORITY 为10,最低是 MIN_PRIORITY 为1,默认优先级是 NORM_PRIORITY 为5;但是并不是优先级越高,就会一直执行这个线程,只是说会优先执行到这个线程,此后还是有其他线程会和此线程抢夺cpu执行权的。

yield():

此方法可暂停当前线程,而执行其他线程。通过这个方法,可稍微减少线程执行频率,达到线程都有机会平均被执行的效果。

写出一个死锁程序

class Test implements Runnable
{
	private boolean flag; // 定义标记
	Test(boolean flag)
	{
		this.flag = flag;
	}

	public void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(MyLock.locka)
				{
					System.out.println(Thread.currentThread().getName()+"...if locka ");
					synchronized(MyLock.lockb)   //在locka中需要lockb锁
					{
						System.out.println(Thread.currentThread().getName()+"..if lockb");					
					}
				}
			}
		}
		else
		{
			while(true)
			{
				synchronized(MyLock.lockb)
				{
					System.out.println(Thread.currentThread().getName()+"..else lockb");
					synchronized(MyLock.locka)  在lockb中需要locka锁
					{
						System.out.println(Thread.currentThread().getName()+".....else locka");
					}
				}
			}
		}
	}
}


class MyLock
{
	static Object locka = new Object();
	static Object lockb = new Object();
}

class  DeadLockTest
{
	public static void main(String[] args) 
	{
		Thread t1 = new Thread(new Test(true));
		Thread t2 = new Thread(new Test(false));
		t1.start();
		t2.start();
	}
}




----------------------   JavaEE+Android、Java培训、期待与您交流! ----------------------

详细请查看: http://edu.csdn.net

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值