第十章 多线程

进程:正在进行中的程序。其实进程就是一个应用程序运行时内存分配的空间。
线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序,并发多任务执行的前提。
     
1、一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法、自己的变量。
2、jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。
3、当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。
4、线程执行完代码和任务就会消亡。

随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。
返回当前线程的名称:Thread.currentThread().getName()
线程的名称是由:Thread-编号定义的。编号从0开始。
1、线程要运行的代码都统一存放在了run方法中。
2、线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)
start方法:1)、启动了线程;2)、让jvm调用了run方法。

创建线程的第一种方式:继承Thread 或者实现一个接口Runnable(单继承的局限性、解耦合),由子类复写run方法。
步骤:
1,定义类继承Thread类或者实现Runnable接口;
2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;
3,通过创建Thread类的子类对象,创建线程对象 ;或将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数,作为线程任务。
4,调用线程的start方法,开启线程,并执行run方法。

Thread t = new Thread(new Runnable(){
	@over
	public void run(){
		···	
	}	
}).start();//开启线程,并让虚拟机调用Runnable子类的run方法

在这里插入图片描述
线程状态
被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
在这里插入图片描述

new Thread(new Runnable(){  //匿名内部类,多态指向子类对象
	public void run(){
		System.out.println("runnable run");	
	}	
		 })	
	{//子类重写的run方法
	public void run(){	
		System.out.println("subthread run");	
	}	
}.start();  //结果:subthread run
---------------------------------------------------------
Try {
	Thread.sleep(10);
}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况。

多线程安全问题两个因素:可见性、有序性、原子性
1,多个线程在操作共享数据。
2,有多条语句对共享数据进行运算。


同步:多少个同步看有多少个锁
好处:解决了线程安全问题。
弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

**解决同步的方式**:(保证是同一个锁)
同步代码块:
		synchronized(对象)
		 {  // 任意对象都可以。这个对象就是锁。
				需要被同步的代码;
		}
		在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
同步函数:(锁是this对象)

静态同步函数:(锁是该类的字节码文件对象  类名.class)运行静态方法是就会在堆内存开辟空间,创建一个字节码文件对象。
/*死锁:常见情景之一:同步的嵌套。*/
class Ticket implements Runnable
{
	private int num = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if(flag)
		while(true)
		{
			synchronized(obj)
			{
				show();
			}
		}else
			while(true)
			this.show();
	}
	public synchronized void show()
	{
		synchronized(obj)
		{
			if(num>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}
				System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
			}
		}
	}
}
	class DeadLockDemo
	{
		public static void main(String[] args)
		{
			Ticket t = new Ticket();
			// System.out.println("t:"+t);
			Thread t1 = new Thread(t);
			Thread t2 = new Thread(t);
			t1.start();
			try{Thread.sleep(10);}catch(InterruptedException e){}
			t.flag = false;
			t2.start();
		}
}

等待唤醒机制:涉及的方法:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象根据所不同存储到对应锁的线程池中。
notify:唤醒该锁线程池中某一个等待线程,获取到执行资格,不一定有执行权。
notifyAll:唤醒的是所有线程池中的所有线程。

注意
1:这些方法都需要定义在同步中。
2:因为这些方法必须要标示所属的锁。
你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?
因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

wait和sleep区别
wait:
1、可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
2、线程会释放执行权,而且线程会释放锁。
sleep:
1、必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
2、线程会释放执行权,但不是不释放锁。

/*多线程中最为常见的应用案例:
生产者消费者问题。
生产和消费同时执行,需要多线程。
但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。
1,描述一下资源。
2,描述生产者,因为具备着自己的任务。
3,描述消费者,因为具备着自己的任务。

问题1:数据错误:已经被生产很早期的商品,才被消费到。
出现线程安全问题,加入了同步解决。使用同步函数。
问题已解决:不会在消费到之前很早期的商品。

问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。
希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。
搞清楚几个问题?
问题3:生产者什么时候生产呢?消费者什么时候应该消费呢?
当盘子中没有面包时,就生产,如果有了面包,就不要生产。
当盘子中已有面包时,就消费,如果没有面包,就不要消费。

生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。
消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。

等待:wait();
告诉:notify();//唤醒

问题解决:实现生产一个消费一个。

=====================
等待/唤醒机制。
wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。
notify():会唤醒线程池中任意一个等待的线程。
notifyAll():会唤醒线程池中所有的等待线程。

记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。
同一个锁上的notify,只能唤醒该锁上的被wait的线程。

为什么这些方法定义在Object类中呢?
因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。

举例:小朋友抓人游戏。*/

//1,描述资源。属性:商品名称和编号,  行为:对商品名称赋值,获取商品。
class Resource
{
	private String name;
	private int count = 1;
	//定义标记。
	private boolean flag = false;	
	//1,提供设置的方法。
	public synchronized void set(String name)
	{
		if(flag)
			try{this.wait();}catch(InterruptedException e){}
		//给成员变量赋值并加上编号。
		this.name = name + count;
		//编号自增。
		count++;
		//打印生产了哪个商品。
		System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);

		//将标记改为true。
		flag = true;
		//唤醒消费者。
		this.notify();
	}
	public synchronized void out()
	{
		if(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);
		//将标记该为false。
		flag = false;
		//唤醒生产者。
		this.notify();
	}
}

//2,描述生产者。
class Producer implements Runnable
{
	private Resource r ;
	// 生产者一初始化就要有资源,需要将资源传递到构造函数中。
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.set("面包");
		}
	}
}

//3,描述消费者。
class Consumer implements Runnable
{
	private Resource r ;
	// 消费者一初始化就要有资源,需要将资源传递到构造函数中。
	Consumer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}

class ThreadDemo9
{
	public static void main(String[] args) 
	{
		//1,创建资源对象。
		Resource r = new Resource();
		//2,创建线程任务。
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		//3,创建线程。
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		t1.start();
		t2.start();
	}
}
/*需求:资源有姓名和性别。
两个线程,
	一个负责给姓名和性别赋值,
	一个负责获取姓名和性别的值。
参阅ThreadTest2.java文件
要求1,运行一下,解决程序的 "妖"的问题。
	分析过程:
	加入同步,必须保证同一个锁,解决妖的问题。
要求2,实现正确数据的间隔输出 如 
张飞--男
rose--女女女
张飞--男 
rose--女女女
使用等待唤醒机制。
wait(),notify(),notifyAll();

对于等待都需要判断,定义条件。

要求3,对代码进行重构。
	将name,sex私有化,资源类提供对其访问的方法。
要求4,将程序改成JDK1.5的Lock Condition接口。
Lock替换了 同步函数或者同步代码块。

Condition替代了 监视器方法,将监视器方法从锁上分离出来,单独封装成Condition对象。*/

//描述资源。
class Resource
{
	private String name;
	private String sex;
	//定义标记,
	private boolean flag = false;

	//赋值功能。
	public synchronized void set(String name,String sex)
	{
		if(flag)
			try{this.wait();}catch(InterruptedException e){}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}

	//获取值。
	public synchronized void out()
	{
		if(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(name+"------"+sex);
		flag = false;
		this.notify();
	}

}
//赋值线程任务
class Input implements Runnable
{
	private Resource r;
	Input(Resource r)//任务一初始化就必须有要处理的资源。
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while(true)
		{
			if(x==0)
			{
				r.set("张飞","男");
			}
			else
			{
				r.set("rose","女女女女");
			}
			x = (x+1)%2;//实现切换。
		}
	}
}
//获取值线程任务
class Output implements Runnable
{
	private Resource r ;
	Output(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
				r.out();
		}
	}
}

class ThreadTest2_3
{
	public static void main(String[] args)
	{
		Resource r = new Resource();
		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方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

//演示停止线程。
class Demo implements Runnable
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(flag)
		{
			try
			{
				wait();//t1  t2
			}
			catch (InterruptedException e)
			{
				System.out.println(Thread.currentThread().toString()+"....."+e.toString());
				changeFlag();
			}
			System.out.println(Thread.currentThread().getName()+"----->");
		}
	}
	//对标记的修改方法。
	public void changeFlag()
	{
		flag = false;
	}
}


class StopThreadDemo 
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d,"旺财");
		Thread t2 = new Thread(d,"小强");
		t1.start();
		//将t2标记为后台线程,守护线程。
//		t2.setDaemon(true);
		t2.start();
		int x = 0;
		while(true)
		{
			if(++x == 50)//条件满足。
			{
//				d.changeFlag();//改变线程任务代码的标记,让其他线程也结束。
				//对t1线程对象进行中断状态的清除,强制让其恢复到运行状态。
				t1.interrupt();
				//对t2线程对象进行中断状态的清除,强制让其恢复到运行状态。
				t2.interrupt();
				break;//跳出循环,主线程可以结束。
			}
			System.out.println("main-------->"+x);
		}
		System.out.println("over");
	}
}

---------< java.lang.Thread >----------:

interrupt():中断线程,用于清除中断状态的的线程(wait变换成临时阻塞)会抛出异常。
setPriority(int newPriority):更改线程的优先级,高优先级的线程获得cpu的执行权概率更高。
getPriority():返回线程的优先级。
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程;当非守护线程全部运行结束或者停止时,无论守护线程什么状态就会随着Java 虚拟机退出而结束。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用join方法,并等待该线程结束,才执行其他线程。

当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。


Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。

1、到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
2、在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
3、所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
4、在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
5、而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。


< java.util.concurrent.locks >监视器 Condition接口:await()、signal()、signalAll();

class BoundedBuffer 
{
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition();//同一个锁,产生多个监视器对象
   final Condition notEmpty = lock.newCondition();
   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException 
   {
     	lock.lock();
    	 try {
		       while (count == items.length)
		         notFull.await();
		       items[putptr] = x;
		       if (++putptr == items.length) putptr = 0;
		       ++count;
		       notEmpty.signal();
		     }finally {
       					lock.unlock();
    				 }
   }

   public Object take() throws InterruptedException
    {
	     lock.lock();
	     try {
		       while (count == 0)
		         notEmpty.await();
		       Object x = items[takeptr];
		       if (++takeptr == items.length) takeptr = 0;
		       --count;
		       notFull.signal();
		       return x;
	     }finally {
	       		lock.unlock();
	     		  }
	   }
 }

并发编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值