Java基础知识之我的理解---线程再回首

酷提纲:

一.开篇废话

二.再看多线程

     1)线程的状态(这一部分是总结前面所学的线程基础知识)

     2)线程的同步问题(这一部分是最近的一些收获)

一.开篇废话

     Where又回来了,貌似有快一个学期没写技术博客了,虽然期间做过一些总结但也没发上博客,又到了假期可以安下心好好写点东西了,废话不多说,希望自己在寒假能安心下来好好学点东西,写点东西,那么让我们从线程开始。

二.再看线程

    1.线程的几个状态

     一个线程可以有四种状态:

     (1) 新(New):线程对象已经创建,但尚未启动,所以不可运行。

           问题一:如何新建线程?

           答:让一个类成为线程类的方式有两种:一个是实现java.lang.Runnable接口,另一个是继承java.lang.Thread类。

           范例代码如下:

public class TestThread extends Thread {
   //继承Thread类的线程类
     public void run(){
       System.out.println("线程开始运行");
      do...
     }

}


public class TestThread1 implements Runnable{
  //实现Runable接口的线程类
   public void run(){
       System.out.println("线程开始运行");
      do...
     }

}

      问题二:比较这两种方式的异同?

      答:

          相同点:两者都需要根据具体要求实现run()方法。

          区别:(1)线程类继承自Thread类则不能继承其他类,而Runnable可以。(因为JAVA的类只能继承自一个类,却可以实现多个接口,这个在之前的博客有提到)。

                    (2)线程类继承Thread类相对于Runnable来说,使用线程的方法更方便些。(因为Thread类提供了很多关于线程的方法,比如:获取线程名,线程状态等)。

                    (3)  实现Runable接口的线程类的多个线程,可以更方便的访问同一变量可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的

                            情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想而Thread类则需要内部类替换。

                            如果发现第三点不好理解那就看一下我下面的例子:

                         现有火车票10张(1-10号),现有X和Y两个代售点要把这十张票卖出我们用Thread和Runnable来试验一下这个过程。

package 比较多线程实现方法;
/**
 * 用Runnable的方式模拟场景
 * @author where
 *
 */
public class TestRunnable implements Runnable{

	private static int MAX=10;//共有MAX张票   
	 
	public void run() {
		while(MAX>0){  
            System.out.println("-"+Thread.currentThread().getName()+"-卖掉第 : "+MAX--+" 张");  
        }  	
	}
	public static void main(String[] args) {  
		TestRunnable tt0=new TestRunnable();  
        new Thread(tt0,"X").start();  
        TestRunnable tt1=new TestRunnable();  
        new Thread(tt1,"Y").start();  
        System.out.println("-------");  
    }  


}

           运行结果:

            -X-卖掉第 : 9 张
            -X-卖掉第 : 8 张
            -X-卖掉第 : 7 张
            -Y-卖掉第 : 10 张
            -Y-卖掉第 : 5 张
            -X-卖掉第 : 6 张
            -Y-卖掉第 : 4 张
            -X-卖掉第 : 3 张
            -Y-卖掉第 : 2 张
            -X-卖掉第 : 1 张

    可已看出用这种方式的结果是对的,那么我们再来看看另一种。

package 比较多线程实现方法;
/**
 * 用Thread方式来模拟场景
 * @author where
 *
 */
public class TestThread extends Thread{
	private static int MAX=10;//共有MAX张票 
	public void run() {
		while(MAX>0){  
            System.out.println("-"+Thread.currentThread().getName()+"-卖掉第 : "+MAX--+" 张");  
        }  	
	}
	public static void main(String[] args) {
		TestRunnable tt0=new TestRunnable();  
        new Thread(tt0,"X").start();  
        TestRunnable tt1=new TestRunnable();  
        new Thread(tt1,"Y").start();  
        System.out.println("-------");  

	}

}

           运行结果(这只是结果的一种情况):

         -Y-卖掉第 : 10 张
         -X-卖掉第 : 10 张
         -Y-卖掉第 : 9 张
         -Y-卖掉第 : 7 张
         -X-卖掉第 : 8 张
         -Y-卖掉第 : 6 张
         -Y-卖掉第 : 4 张
          -X-卖掉第 : 5 张
         - X-卖掉第 : 2 张
          -X-卖掉第 : 1 张
          -Y-卖掉第 : 3 张

          这次就有问题了,Y售票站已经将10号票售出为什么X又买出了一张10号票,这就是上面我们讲的那个问题。貌似到这里你该明白第三点了吧。
     (2) 可运行(Runnable ):意味着一旦时间分片机制有空闲的CPU 周期提供给一个线程,那个线程便可立即
          开始运行。因此,线程可能在、也可能不在运行当中,但一旦条件许可,没有什么能阻止它的运行——它既
          没有“死”掉,也未被“堵塞”。
     (3) 死(Dead):从自己的run()方法中返回后,一个线程便已“死”掉。亦可调用stop()令其死掉,但会
          产生一个违例——属于Error 的一个子类(也就是说,我们通常不捕获它)。记住一个违例的“掷”出应当
          是一个特殊事件,而不是正常程序运行的一部分。所以不建议你使用stop()(在Java 1.2 则是坚决反
          对)。另外还有一个destroy()方法(它永远不会实现),应该尽可能地避免调用它,因为它非常武断,根
          本不会解除对象的锁定。
     (4) 堵塞(Blocked):线程可以运行,但有某种东西阻碍了它。若线程处于堵塞状态,调度机制可以简单地
           跳过它,不给它分配任何CPU 时间。除非线程再次进入“可运行”状态,否则不会采取任何操作。

         问题三:为何会堵塞?

         答:堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
        1.调用sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。
        2. 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回“可运行”状态。
        3. 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成“可运行”
           (是的,这看起来同原因2 非常相象,但有一个明显的区别是我们马上要揭示的)。
        4. 线程正在等候一些IO(输入输出)操作完成。
        5. 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。

    2.线程的同步问题(这一部分是最近的一些收获)

       多线程一旦操作同一内存的数据就容易造成数据的混乱,也就是常说的线程安全问题。针对这个问题我们一般有四种解决方式:

  • 采用synchronized关键字锁定要同步的方法
  • 采用synchronized关键字锁定要同步的代码块
  • 采用lock锁对象锁定同步代码,注意这里的所对象必须要为同一个否则加锁没意义。
  • 采用共享的代码操作为原子操作 
  • 在这一部分我用生产者消费者模型为例说明:场景模拟:有一个最大存货量为5的仓库,生产者往里放,消费者取,当放满了则生产者wait,当取空了则消费者wait.
    package 生产消费者模型;
    //用wait/notify
    public class Store {
    	public  static final int MAXSIZE = 5;//表示仓库里的最大存货量
    	int n;//表示仓库里当时的存货量
    	public Store(int n){
    		this.n= n;
    	}
    	
    	/**
    	* 放入货物的方法
    	*/
    	public synchronized void addThing(){
    		while(n>=MAXSIZE){
    			try {
    				System.out.println(Thread.currentThread().getName()+"当前仓库已经存放到最大限度,需等待才能在放");
    				//notifyAll();
    				wait();
    				
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		n++;
    		System.out.println(Thread.currentThread().getName()+"当前仓库放入了"+n+"个或物");
    		notify();
    	}
    	/**
    	 * 移除货物的方法
    	 */
    	public synchronized void removeThing(){
    		while(n<=0){
    			try {
    				System.out.println(Thread.currentThread().getName()+"当前仓库已没有货物要取需等待");
    				//notifyAll();
    				wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		n--;
    		System.out.println(Thread.currentThread().getName()+"仓库被取走货物后剩下的货物为:"+n+"个");
    		notify();
    	}
    }
          
    package 生产消费者模型;
    
    public class Customer extends Thread{
    	Store s;
    	public Customer(Store s){
    		this.s = s;
    	}
    	public void run(){
    		while(true){
    		s.removeThing();
    		try {
    			Thread.sleep(1500);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		}
    	}
    }
     
    package 生产消费者模型;
    
    public class Producer extends Thread{
    
    	private Store s;
    	public Producer(Store s){
    		this.s = s;
    	}
    	public void run(){
    		while(true){
    			s.addThing();
    			try {
    				Thread.sleep(500);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
     
    package 生产消费者模型;
    
    public class Test {
    
    	/**
    	 * 测试入口
    	 */
    	public static void main(String[] args) {
    		Store s = new Store(0);
    		Thread pro1 = new Producer(s);
    		Thread pro2 = new Producer(s);
    		//Thread pro3 = new Producer(s);
    		Thread cus1 = new Customer(s);
    		Thread cus2 = new Customer(s);
    		Thread cus3 = new Customer(s);
    		pro1.start();
    		pro2.start();
    		//pro3.start();
    		cus1.start();
    		cus2.start();
    		cus3.start();
    	}
    
    }
     运行结果(由于是while(true)所以只选取了部分结果来说明问题):

        Thread-0当前仓库放入了1个或物
        Thread-2仓库被取走货物后剩下的货物为:0个
        Thread-3当前仓库已没有货物要取需等待
        Thread-1当前仓库放入了1个或物
        Thread-3仓库被取走货物后剩下的货物为:0个
        Thread-4当前仓库已没有货物要取需等待
        Thread-0当前仓库放入了1个或物
        Thread-4仓库被取走货物后剩下的货物为:0个
        Thread-1当前仓库放入了1个或物
        Thread-0当前仓库放入了2个或物
        Thread-1当前仓库放入了3个或物
        Thread-0当前仓库放入了4个或物
        Thread-2仓库被取走货物后剩下的货物为:3个
        Thread-3仓库被取走货物后剩下的货物为:2个
        Thread-1当前仓库放入了3个或物
        Thread-0当前仓库放入了4个或物
        Thread-4仓库被取走货物后剩下的货物为:3个
        Thread-1当前仓库放入了4个或物
        Thread-0当前仓库放入了5个或物
        Thread-1当前仓库已经存放到最大限度,需等待才能在放
        Thread-0当前仓库已经存放到最大限度,需等待才能在放
       Thread-2仓库被取走货物后剩下的货物为:4个
       Thread-1当前仓库放入了5个或物
       Thread-0当前仓库已经存放到最大限度,需等待才能在放
       Thread-3仓库被取走货物后剩下的货物为:4个
       Thread-0当前仓库放入了5个或物
       ...

       结果分析:

       (1)线程并不是按它们创建时的顺序运行的

    (2)当执行到后面,有多个wait(),那么notify该激活哪个wait或者说它有什么调度机制?根据结果(标红的部分)我们可以做此大胆猜想:这里有一个wait队列,先进先出,即notify相应最先进队列的wait.

        

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值