java基础_07_多线程

多线程

线程是程序运行的基本执行单元,一个进程中可以包含多个线程,这些线程共享这个进程中的内存空间。但是进程和进程之间是不共享内存的,都有自己的独立的运行空间。

 

建立线程的两种方法

1,一种方法是类去继承 Thread 类,其实是Thread自己实现了Runnable

2,用接口的方法,去实现 Runnable (用这个比较好)

   创建步骤:

1,定义自己的类实现Runnable接口

2,覆盖Runnable接口中的run方法

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

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

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

 

   例子:卖票窗口,3个窗口卖100张票

 

两种方法的区别?

java 是单继承 如果继承 Thread 则不能在继承其它的类 ,实现接口的话还可以继承其它类,接口可以多实现的

 

多线程会出现安全问题

一:解决方法是加synchronized ,单线程不存在这个问题

     好处:解决了安全问题    

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

1,
    synchronized()
    {
    	共享代码块
    }																							   
     例:
   	 Object obj=new Object();
   	 public void run()
   	 {
   	 	while(true)
       	{
    			synchronized(obj)//加锁安全 这个锁的是obj 
    			{
    				if(tick>0)    //这句在多线程来说是有安全问题的,如果两个tick=1了,
 					      //多个进程同时进到这里来执行,就会出现问题
    				System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
    			}
    		}
    }

    2,可以把共享代码封装在一个方法中,在调用其方法就行, 在方法上加上synchronized
     例:
      public synchronized void run() //加锁 这个锁的是this 
      {
      	while(true)
      	{
      		if(tick>0)  		
             		System.out.println(Thread.currentThread().getName()+"...sale :"+tick--);
      	}
      }
     或:
       public  void  method() 
       {	
           synchronized(this)  //相当于锁的是 method() 方法
           {
         	共享代码块
    	      }	
       }
    

   3,或直接把synchronized 放在方法上,锁的是this,如果方法被static静态了,那么锁的就不是this了。(静态中也不可能有this),这个时候锁的是所在类的字节码文件对象。<类名.class>                                        
      不同锁的建立:
       建立锁:
    class Loak
    {
    	static Object loak1=new Object();
    	static Object loak2=new Object();//两种锁
    }
    
  	 加入锁:
    synchronized(Loak.loak1)
    {
    		
    }

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

将同步synchronized替换成现实lock操作

要为特定 Lock 实例获得 Condition 实例,使用其 newCondition() 方法。

Condition 实例实质上被绑定到一个锁上。

Condition 将 Object 监视器方法(waitnotify 和 notifyAll)分解成截然不同的对象,

以便通过将这些对象与任意 Lock 实现组合使用

 

采用 lock 锁来锁定代码

   final Condition nl  = lock.newCondition(); 

   final Condition n2 = lock.newCondition(); 

 

用 n1.signal   n2.signal 来唤醒一个指定等待线程

用 n1.signalAll    n2.signalAll  来唤醒所有指定等待线程           

       例:

    class ziyuan //资源
    {
	
		//定义锁	
		private Lock lock = new ReentrantLock();  
	
		private Condition c1 = lock.newCondition();
		private Condition c2 = lock.newCondition();

   		 public void set(String name) throws InterruptedException
		{
			lock.lock();   代码上锁
			try
			{
				while(!flg)
				{
			   		 c1.await();    // p1  等待
				}
			
				flg = false;
				c2.signal();	// v2	唤醒
			}
			finally 
			{
				lock.unlock();  最后一定要解锁
			}
	}

      如果在一个类中有多处使用加锁,要处理同一共享数据时。要加锁同一个。不然就要出错

 

注意: 使用这个不要出现 死锁 ,当类中存在多个不同的锁时,而这些锁存在交叉,那么就可能会出现死锁

如:

1,锁A--11

      {B--22}

2,锁B--33

      {A--44} 

像上面这样出现交叉的锁。如11 33 同时执行后 要运行22 44就锁住了。22被下面B锁了,44又被上面11锁住了。那么就执行不下去了

 

怎么让线程停下来呢?

stop已过时,只有一种方法,让 run 方法停止。

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

用 while 循环来判断标识。

原因:让被唤醒的线程再一次判断标记

例:

    class StopThread implements Runnable
    {
    	private boolean flag = true; //标记,用于控制线程结束
    	public void run()
    	{
    		while(flag)
    		{
    			System.out.println(Thread.currentThread().getName()+"..........run");	
    		}
    	
    	}
    	public void changeFlag() //更改标记
    	{
    		flag = false;
    	}
    }
    class  ThreadStopclass
    {
    	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(); //修改标记让线程停下
    				break;
    			}				
    			System.out.println(Thread.currentThread().getName()+"..........");
    		}
    		System.out.println("主函数运行完。。。");
    	}
    }

 

特殊情况

当线程处于了冻结状态(就是等待状态),就不会读取到标记,那么线程就不会结束。

这个时候怎么结束呢?

这时要清除冻结状态,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

 

Thread类中提供了该方法 interrupt() 中断线程.

例:

    public synchronized void run() //在这加了锁这种情况,所以下面等待就不会让线程停下 	{	
    while(flag)
    	{
    		try
    		{
    			wait();   //在这里让线程等待了。所以必须先唤醒线程才能让其停止。
    		}
    		catch (Exception e)
    		{
    			System.out.println(Thread.currentThread().getName()+"................Exception");
    		}
    		ystem.out.println(Thread.currentThread().getName()+"..........run");	
    	}
    	
    }
    		
    
    while(true)
    {
    	if(num++==60)
    	{
    		st.changeFlag(); //修改标记让线程停下
    		t1.interrupt();   // 中断线程
    		t2.interrupt();
    		break;
    	}				
    	System.out.println(Thread.currentThread().getName()+"..........");
    }
    System.out.println("主函数运行完。。。");

 

线程带来的好处?

1,充分利用CPU资源

2,简化编程模型

如果要使程序完成多项任务,这时用单线程的话,就得作出判断以及什么时候执行。而且不好操作,使用多线程来完成呢,就方便的多。

3,使GUI更有效率。

4,节约成本。

使用多进程,提高了程序的执行效率。

5,简化异步事件的处理。

 

线程的生命周期:开始、运行、挂起、停止 四种不同的状态。

 

join方法的使用

程序在返回数据的过程中,而这个数据又是在线程执行过程中给赋予的,这时如果线程没有执行结束,或还没有给我们所调用的信息赋值,那么这时,返回的数据就是错误的信息了。

这时,我们可以采用让调用者先等待一会,在去调用,但是等多长时间又是一个问题,所以在这里出现了join()方法。调用了join()方法,就是让这个线程执行完,才执行下面的代码。

例:

//  有 10 个线程

for(int  i=0; i<10; i++)

{

threads[i].start();

}

//让每一个线程执行完后,再往下执行

for(int  i=0; i<10;  i++)

{

threads[i].join();

}

//等待线程执行完才会执行后面的代码

System.out.println();

 

注意:其实在这里调用join()方法,和调用其它任意一个方法,是一样的,只要在这里调用了一下,就意味着线程执行完在往下执行。

 

向线程传递数据的三种方法:

1,通过构造函数

2,通过方法和变量

3,通过回调函数

回调函数就是事件函数。

就是把自己给别人,让别人通过方法来给自己传值。

例:

    下面我们给Data 类中value 赋值

class Data{

public int value = 0;

}

class Work{

public void process(Data data,int a)

{

data.value = a;

}

}

public static void min(String [] args)

{

Data data = new Data();

Work work = new Work();

work.process(  data , 3  );  //通过回调函数给value 赋值

}

 

volatile 关键字

用于声明简单类型,如 int float boolean 等。

public volatile int n = 9;

声明后,对它们的操作就会变成原子级别的。 每次使用它都到主存中进行读取。多线程在操作的时候操作的是同一个数据,所以保证了数据的同步。

注意:当变量值由自身的上一个值决定时,如 n=n+1  n++ 等,volatile关键字将失效。

 

心得:

使用线程就得注意数据同步的问题,使用join方法非常好,在线程中还有一个问题,就是在给代码加上锁的时候注意别产生死锁。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章