Java 线程的基本概念(模仿生产者与消费者的问题)

1.什么是线程?

程序中一个单一顺序的控制流程。

2.线程与进程的区别?

  • 每个进程都有独立的代码和数据空间,进程间的切换有较大的开销。
  • 线程可以看做是轻量级的进程,是进程的一个实例。同一类线程共享代码和数据空间,每个线程都有独立的运行栈和程序计数器(PC),线程间切换开销小。
3.多线程与多进程
  • 多进程:在操作系统中能同时运行多个任务(程序)
  • 多线程:同一应用程序中有多个顺序流在同时执行。
4.线程创建与启动的方法有两种
  • 实现Runable接口
    class Runner1 implements Runables{
          public void run()
          {
           for (int i=0;i<100;i++)
           {
             System.out.println("Runner1="+i);
           }
         }
    }
    public class TestThread1
    {
    	public static void main(String args[])
    	{
    		Runner1 r = new Runner1();
    		Thread t = new Thread(r);
    		t.start();
    		for(int i=0;i<100;i++)
    		{
    			System.out.println("Main Thread:--"+i);
    		}
    	}
    }
  • 继承Thread类
    class Runner extends Thread
    {
             public void run()
             {
                       for(int i=0;i<100;i++)
                       {
                               System.out.println("Runner1="+i);
                        } 
              }
    }
    public class Test{
    	public static void main(String args[])
    	{
    		Runner r = new Runner();
    		r.start();
    	}
    }
5.线程的转换状态
创建 、就绪、运行、阻塞和终止状态。
6.线程控制的基本方法
  • isAlive:判断线程是否活着,就绪、运行和阻塞都是活着。
  • getPriority(),setPriority():获取或设置线程的优先级
  • Thread.sleep():当前线程sleep的毫秒数
  • join():线程合并
  • yield():让出CUP,当前线程进入就绪队列,等待CPU调度
  • wait():当前线程进入等待池(wait pool)
  • notify()/notifyAll():唤醒 wait pool 中的线程或全部线程
(1)Sleep():可以用Thread.sleep()直接调用。会抛出InterruptedExcepteion
import java.util.*;
class MyThread extends Thread
{
	public void run()
	{
		while(true)
		{
			System.out.println("==="+new Date()+"===");
			try
			{
				sleep(1000);
			}
			catch(InterruptedException e)
			{
				return;
			}
		}
	}
}
public class TestInterrupt
{
	public static void main(String args[])
	{
		MyThread thread = new MyThread();
		thread.Start();
		try
		{
			Thread.Sleep(10000);
		}
		catch(InterruptedException e)
		{
		}
		thread.interrupt();
	}
}

(2) Join() :合并线程,合并后会抛出InterruptedException异常
import java.util.*;
class MyThread2 extends Thread
{
	Mythread2(String s)
	{
		super(s);
	}
	public void run()
	{
		for(int i=0;i<10;i++)
		{
			System.out.println("I AM"+getName());
		}
		try
		{
			sleep(1000);
		}
		catch(InterruptedException e)
		{
			return;
		}
	}
}
public class TestInterrupt
{
	public static void main(String args[])
	{
		MyThread2 t = new MyThread2("t");
		t.start();
		try
		{
			t.join();
		}
		catch(InterruptedException  e)
		{
		}
		for(int  i=0;i<10;i++)
		{
			System.out.println("I Am Main Thread");
		}
	}
}

7.线程同步:协调多个线程访问同一个资源
class Timer{
	private static int num=0;
	public Synchronized void add(String name)
	{
		Synchronized(this)
		{
			num++;
			try
			{
				Thread.sleep(1);
			}catch(interruptedException e){}
			System.out.println(name+",你是第"+num+"使用Timer的线程");
		}
	}
}
public class TestSync implements Runnable{
	Timer timer = new Timer();
	public Static void  Mian(String args[])
	{
		TestSync  test = new TestSync();
		Thread t1=  new Thread(test);
		Thread t2 = new Thread(test);
		t1.setName("t1");
		t2.setName("t2");
		t1.start();
		t2.start();
	}
	public void run()
	{
		timer.add(Thread.currentThread().getName());
	}
}
注意:
运行结果:t1,你是第1个使用Timer的线程
                    t2,你是第2个使用Timer的线程
如果不加Synchronizaed等关键字,运行的结果是:t1,你是第2个使用Timer的线程
                                                                                           t2,你是第2个使用Timer的线程
原因:首先,t1访问timer.add,num++,num=1;
            接着,t1线程sleep(1),即使没有sleep(1)结果也会如此,因为CPU的时间片段会打断线程的运行。
            然后,t2访问timer.add,num++,num=2;
            接着,t2.sleep(1),t1唤醒了继续执行,由于num是static变量,因此输出的num=2;
            最后,t2醒后,输出num=2.
最主要的原因是,线程在sleep时没有将当前线程正在使用的资源进行 锁住,让其他的线程进来破坏了共享数据的完整性, 因此加入关键字Synchronized,它来与对象间的互斥锁联系,当个资源被synchronized修饰时,表示该资源在任意时刻只能有一个线程访问。

8.java模拟一个死锁的 例子
class TestDeadLock implements Runnable
{
	public int flag=1;
	Static objec o1 = new object();
	Static objec o2 = new object();
	public void run()
	{
		System.out.println("flag="+flag);
		if(flag==1)
		{
			Synchronized(o1)
			{
				try
				{
					Thread.sleep(500);
				}catch(InterruptedException e){}
				Synchronized(o2)
				{
					System.out.println("1");
				}
			}
		}
		if(flag==0)
		{
			Synchronized(02)
			{
				try
				{
					Thread.sleep(500);
				}catch(InterruptedExcetpion e){}
				Synchronized(01)
				{
					System.out.prinltn("0");
				}
			}
		}
	}
	public Static void Main(String  args[])
	{
		TestDeadLock td1 = new TestDeadLock();
		TestDeadLock td2 = new TestDeadLock();
		td1.flag=1;
		td2.flag=0;
		Thread t1 = new Thread(td1);
		Thread t2 = new Thread(td2);
		t1.start();
		t2.start();
	}
}

9.写一个线程同步的例子
public class TT implements Runable
{
	int b=100;
	public Synchronized void m1() throws Exception
	{
		b=1000;
		Thread.sleep(5000);
		System.out.println("b="+b);
	}
	public void m2() throws Exception
	{
		Thread.sleep(7500);
		b=2000;
		System.out.println(b);
	}
	public void run()
	{
		try
		{
			m1();
		}
		catch(Exception e)
		{
			e.PrintstackTrace();
		}
	}
	public Static void Main(String args[])
	{
			TT tt = new TT();
			Thread t = new Thread(tt);
			t.start();
			Thread.sleep(1000);
			tt.m2();
	}
}
分析:首先,t线程开始执行,进入m1()方法,将b的值改为b=1000,t线程开始sleep(5000);
            接着,主线程Main也Thread.sleep(1000)后,执行tt.m2(),而 m2中Thread.sleep(7500),而此时t线程已经唤醒,因此先输出b=1000;
            最后,主线程被唤醒,b的值改为2000,最后输出2000。
注意:如果某个资源会使线程产生死锁,那么必须正确的为该资源上锁,必须把访问到这个资源的所有方法都考虑到。加了同步效率可能会降低,当不加 同步可能产生数据不一致的问题。
        
10 生产者与消费者问题
生产者 往容器类生产产品 ,直到到达容器的上限必须停止生产,等待消费者的消费。
消费者消费容器中的产品,直到容器中的产品消费完了,等待生产者生产产品。
代码实现:
%产品类
class Goods
{
	int id;
	Goods(int id)
	{
		this.id =  id;
	}
	public String toString()
	{
		return "Goods"+id;
	}
}

%放产品的堆栈 相当于一个容器,由于消费者和生产者都要对容器进行操作,因此必须对多个线程共同访问的资源枷锁
class  SyncStack
{
	int index=0;
	Goods arrGoods[] = new Goods[6];%只能放6个产品
	%Push(Goods good)往容器中放产品的方法
	public Synchronized void Push(Goods good)
	{
		while(index==arrGoods.length)%如果容器已满,就等待
		{
			try
			{
				this.wait();
			}catch(InterruptedException e){}
		}
		this.notify();%否则容器中有空位,放入产品
		arrGoods[index]=good;
		index++;
	}
	
	%Pop()返回的是消费的产品
	public synchronized Goods Pop()
	{
		while(index==0)%如果容器中没有产品了,就等待
		{
			try
			{
				this.wait();
			}catch(InterruptedException e){}
		}
		this.notify();%容器中有产品就可以消费产品
		index--;
		return arrGoods[index];
	}
}

%生产者只负责往容器中放入产品
class Producer implements Runnable
{
	SyncStack ss = null;
	Producer(SyncStack ss)
	{
		this.ss = ss;
	}
	public void run()
	{
		for(int i=0;i<20;i++)
		{
			Goods goods =  new Goods(i);
			ss.Push(goods);
			System.out.println("生产:"+goods);
			try
			{
				Thread.sleep(1000);
			}catch(InterruptedException e){}
		}
	}
}

%消费者只负责从容器中取走产品
class Consumer implements Runnable
{
	SyncStack ss= null;
	Consumer(SyncStack ss)
	{
		this.ss = ss;
	}
	public void run()
	{
		for(int  i=0;i<20;i++)
		{
			Goods goods = ss.Pop();
			System.out.println("消费"+goods);
			try
			{
				Thread.sleep(2000);
			}catch(InterruptedException e){}
		}
	}
}

public  class ProducerConsumer
{
	public Static void Main(String args[])
	{
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
分析:wait()和sleep()的区别
(1)在一个线程中任何地方都可以条用sleep()方法,当只有加锁的对象才能调用wait(),也就是说只有用Synchronized的地方才能用wait();
(2)wait()是object类的方法,而sleep()是Thread类的方法;
(3)加锁的对象调用了wait()后,这个线程就不在拥有锁了,只有被 notify()叫醒后,才能找回该锁,而加锁的对象调用了sleep()后,这个线程依然用后该锁;
(4)wait()必须用notify()才能被叫醒,而sleep(1000),当过1000毫秒后就自动唤醒。

11.关于线程的一些基本的问题
  • java中有几种方法可以实现创建一个线程?
             有两种方法创建一个线程:第一种方法是定义线程实现Runnable接口,调用的是Runnable接口的run()方法,即要重写Runnable接口的run()方法(推荐使用)
                                                             第二个方法是定义线程继承Thread方法,调用Thread父类的run()方法。
  • stop()和suspend()方法为什么不推荐使用?
              (1)反对用Stop()方法,因为它不安全,它会解除由线程获取的所有锁,而且如果对象处于一种不连贯的状态,那么其他线程能在那种状态下检查和修改它们。
              (2)反对用Suspend()方法,因为此方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然保持在此之间获得锁。因此其他任何线程都不能                           访问锁定的资源,除非被suspend()挂起的线程状态恢复。
对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁
  因此不应该使用suspend(),若线程要挂起就使用wait()方法,命其进入等待状态,因为加锁的对象调用了wait()后,此线程就不在拥有锁了,只有被notify()唤醒,才能找回该锁。
  • sleep()和wait()方法的区别?
  • 线程同步和异步有何异同,分别在什么情况下使用它们?
             (1)如果数据将在线程间共享,例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程谢过了 ,那么这些数据是共享数据,必须进行 同步读取。
     (2)当应用程序在对象上调用了一个需要很长时间执行的方法,并不希望让程序等待该方法的返回值,这时应该异步编程,异步的效率更高。
  • 当一个线程进入一个对象的Synchronized方法后,其他线程是否可以进入此对象的其他方法?
             要分几种情况来讨论:
             (1)如果此对象的其他方法前没有加Synchronized,就可以访问这些方法。
             (2)如果此对象的其他方法前加了Synchronized,但是当前Synchronized方法内部 有wait(),则这个线程可以进入其它加了Synchronized的方法。
             (3)如果当前Synchronized方法内部没有wait(),则 这个线程不能进入加入其它的Synchronized方法内部。
  • 线程有哪些基本的状态,这些状态之间的关系是什么?
              一个程序可以有多个线程同时执行,每个线程都有关联要执行的代码,而每个程序至少 有一个线程,即Main主线程。
              当一个线程start()后,该线程就进入了就绪状态,等待CPU的调度,调度后就绪状态的线程就变成运行状态。
              在运行 状态线程遇到某个对象的Synchronized语句时,但又获得不了LOCK时候,就由运行状态变成阻塞状态。
              当Synchronized获得锁LOCK后,线程再由阻塞状态变成运行状态,同时可以使用wait ()转成挂起状态。
              当线程相关代码执行完成后,线程变为结束状态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值