黑马程序员java之多线程



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


多线程


进程:正在进行中的程序。是一个应用程序运行时的内存分配空间。

线程:其实就是进程中一个程序执行控制单元,一条执行路径。是应用程序的空间的标示。负责应用程序的执行顺序。


创建线程的两种方式:继承Thread类。实现Runnable接口

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

步骤:

  1,定义类继承Thread

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

 3,调用线程的start方法,

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


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

步骤:

1,定义类实现Runnable接口

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

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

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

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


package pract;

//Thread currentThread():获取当前线程对象。
//Thread currentThread().getName():获取线程名称:setName或者构造函数。

class ExtendsThread extends Thread{
	public void run(){
		System.out.println(Thread.currentThread()+"你好,这是继承线程run");
		System.out.println(Thread.currentThread().getName()+"你好,这是继承线程run");
	}
}
class ImplementRun implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread()+"你好,这是实现线程run");
		System.out.println(Thread.currentThread().getName()+"你好,这是实现线程run");
	}
}
public class ThreadDemo {
	public static void main(String[] args) {
		//创建好一个线程。
		ExtendsThread et = new ExtendsThread();
		//仅仅是对象调用方法。而线程创建了,并没有运行
		et.run();
		//开启线程并执行该线程的run方法。
		et.start();
		//匿名调用开启多线程调用了run方法
		new Thread(new ImplementRun()).start();
     //将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
     //调用Thread对象的start方法。
	}

}

//代码运行结果:

Thread[main,5,main]你好,这是继承线程run

main你好,这是继承线程run

Thread[Thread-0,5,main]你好,这是继承线程run

Thread-0你好,这是继承线程run

Thread[Thread-1,5,main]你好,这是实现线程run

Thread-1你好,这是实现线程run


//运行结果分析

通过前两句发现没有开启多线程,因为看到了主线程在运行

其余的都创建了新的线程。


为什么要有Runnable接口的出现?

 通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。

只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。

所以,通常创建线程都用第二种方式。因为实现Runnable接口可以避免单继承的局限性。


两种方式区别:

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

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


练习:编写一个多窗口卖票程序

package pract;

class Ticket implements Runnable{
	private  int tick = 100;
	public void run(){
		while(true){
			if(tick>0){
				System.out.println(Thread.currentThread().getName()+
						"....sale : "+ tick--);
			}
		}
	}
}
//多窗口卖票体现了实现Runnable接口的经典案例

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

	}
}

线程状态:

被创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()run结束



多线程的运行出现了安全问题

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


解决安全问题的原理

只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

java中提供了一个解决方式:就是同步代码块。

格式:

synchronized(对象){ // 任意对象都可以。这个对象就是锁。

  需要被同步的代码;

}

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

弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

package pract;

public class Single {
	//懒汉式:延迟加载方式
	private static Single s = null;
	private Single(){}

	public static  Single getInstance(){
		//为了提高效率,加入双重判断
		if(s==null){
			synchronized(Single.class){//锁是字节码文件
				if(s==null)
					s = new Single();
			}
		}
		return s;
	}

}

定义同步是有前提的

1,必须要有两个或者两个以上的线程,才需要同步。

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

同步除了同步代码块外,还有同步函数和被static修饰的同步函数,他们的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this

静态同步函数的锁是该类的字节码文件对象。


package pract;

class Ticket implements Runnable{
	private  int tick = 100;
	public void run(){
		while(true)
		{
			synchronized(TicketDemo.class){//同步代码块,为了防止线程不安全
			
				if(tick>0){
				
					try{
						Thread.sleep(10);
						}catch(Exception e){
							throw new RuntimeException("程序异常");
						}
					System.out.println(Thread.currentThread().getName()+
							"....sale : "+ tick--);
				}
					
			}
		}
	}
}

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

	}
}

同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

class Tickets implements Runnable{
	private  int tick = 1000;
	Object obj = new Object();
	boolean flag = true;
	public  void run(){
		if(flag){
			while(true){
				synchronized(obj){//obj的锁
					show();//调用同步函数show();
				}
			}
		}
		else
			while(true)
				show();//调用同步函数show();
	}
	public synchronized void show(){//锁是this,可能在某个时间发生两个线程同时要锁的局面
		synchronized(obj){
			if(tick>0){
				try{
					Thread.sleep(10);
				}catch(Exception e){
					throw new RuntimeException("错了");
				}
				System.out.println(Thread.currentThread().getName()+
						"....code : "+ tick--);
			}
		}
	}
}


public class  DeadLockDemo{
	public static void main(String[] args) {

		Tickets t = new Tickets();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{
			Thread.sleep(10);
		}catch(Exception e){
			throw new RuntimeException("线程等待失败");
		}
		t.flag = false;//为了开启双线程同时运行
		t2.start();


	}
}

等待唤醒机制:涉及的方法:

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

notify唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

这些方法都存在Object中。

waitsleep区别: 分析这两个方法:从执行权和锁上来分析:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但是不释放锁。

class Res
{
	private String name;
	private String sex;
	private boolean flag = false;
	/*
	线程间通讯:
	其实就是多个线程在操作同一个资源,
	但是操作的动作不同。

	*/
	//对同一个资源进行进行安全机制的判断synchronized
	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","man");				
			else	
				r.set("丽丽","女女女女女");				
			x = (x+1)%2;
		}
	}
}

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


class  InputOutputDemo2
{
	public static void main(String[] args) 
	{
		Res r = new Res();

		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
	}
}

等待唤醒机制在JDK1.5中提供了多线程升级解决方案。

将同步Synchronized替换成现实Lock操作。

Object中的waitnotifynotifyAll,替换了Condition对象。

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

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

Lock:替代了Synchronized

   lock

   unlock

   newCondition()

Condition:替代了Object wait notify notifyAll

   await();

   signal();

   signalAll();

package pract;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


class Res{
	private String name;
	private String sex;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();

	private Condition condition_xie = lock.newCondition();
	private Condition condition_du = lock.newCondition();

	public  void set(String name,String sex){
		lock.lock();		
			try{
				if(flag)
				condition_xie.await();
				this.name = name;
				
				this.sex = sex;
				flag = true;
				condition_du.signal();//唤醒du的线程
			}catch(Exception e){
					throw new RuntimeException("xie出现异常");
				}
			finally{
			lock.unlock();//关闭锁
			}
		
		
	}
	public  void out(){
		lock.lock();	
			try{
				if(!flag)
				condition_du.await();
				System.out.println(name+"........"+sex);
				flag = false;
				condition_xie.signal();
			
			}catch(Exception e){
				throw new RuntimeException("du出现异常");
			}
			finally{				
				lock.unlock();
			}
	}
}

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","man");				
			else	
				r.set("丽丽","女女");				
			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  InputOutputDemo{
	public static void main(String[] args) {
		Res r = new Res();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
	}
}

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

停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。

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

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

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


package pract;

class StopThread implements Runnable
{
	private boolean flag =true;
	public  void run()
	{
		while(flag)
		{
			
			System.out.println(Thread.currentThread().getName()+"....run");
		}
	}
	public void changeFlag()//改变flag值,定义循环结束标记
	{
		flag = false;
	}
}

public class  StopDemo
{
	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 num = 0;

		while(true)
		{
			if(num++ == 60)
			{
				st.changeFlag();//改变flag值。线程自动停止
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");
	}
}

把线程标记为守护线程时候,主线程运行结束,守护线程自动取消

public class  StopDemo
{
	public static void main(String[] args) 
	{
		StopThread st = new StopThread();
		
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);


		t1.setDaemon(true);//守护线程,主线程执行完,守护线程自动消失
		t2.setDaemon(true);
		t1.start();
		t2.start();

		



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值