黑马程序员 多线程

-------android培训java培训、期待与您交流! ----------

多线程

一。概述:

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序

      该顺序是一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元。

      线程在控制着进程的执行

一个进程中至少有一个线程

二.Java中的线程:

Java VM启动的时候会有一个进程java.exe

该进程中至少有一个线程负责java程序的执行

而且这个线程运行的代码存在于main方法中

该线程称之为主线程。

扩展:其实更细节说明jvmjvm启动不止一个线程,还有负责垃圾回收机制的线程。

Java使用thread类代表线程,所有线程对象都必须是Thread类或其子类的实例

Thread类用于描述线程

该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法

也就是说Thread中的run方法,用于存储线程要运行的代码。

为什么要把代码放进main方法中:

  因为java虚拟机的主线程需要调用main方法中的代码

三.自定义线程。

方式一:直接继承Thread类。并覆盖run方法

调用线程的start方法:

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

方式二:实现Runnable接口。再把实现类传递给Thread类。在构造函数中赋值。

示例代码:

package cn.itcast.heima;

public class TraditionalThread {
	public static void main(String[] args) {
		//第一种创建线程的方式
		  Thread thread1 = new Thread(){
			  
			  public void run(){
				  while(true){
				  System.out.println("1 "+Thread.currentThread().getName());
				  }
			  }
			  
		  };
		  thread1.start();
	//第二种创建线程的方式
	Thread thread2 =new Thread(new Runnable(){
		 public void run(){
			  while(true){
				  System.out.println("2 "+Thread.currentThread().getName());
				  }
		 }
	});
   thread2.start();
   
   new Thread(new Runnable(){
	   
	   public void run(){
		   while(true){
		   System.out.println("runable"+Thread.currentThread().getName());
		   }
	   }
   }){
	   public void run(){
		   while(true){
		   System.out.println("thread"+Thread.currentThread().getName());
		   }
	   }
   }.start();
}
}

线程的四种状态:


sleep方法需要指定睡眠时间,单位是毫秒。 一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源。

wait(),sleep()有什么区别? 
Wait():释放cpu执行权,释放锁。 

sleep():释放cpu执行权,不释放锁。


多线程的安全问题:

导致安全问题的出现的原因:多个线程访问出现延迟

线程随机性 

。 注:线程安全问题在理想状态下,不容易出 现,但一旦出现对软件的影响是非常大 的。

同步代码块:

  同步的前提:必要有两个或者两个以上的线程

  必须是多个线程使用同一个锁

必须保证同步中只能有一个线程在运行

 

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

 

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

 

如何找问题:

1.明确哪些代码是多线程运行代码

2.明确共享数据

3.明确多线程运行代码中哪些语句是操作共享数据的

 

同步函数:

  同步函数用得是哪一个锁呢?

 函数需要被对象调用。那么函数都有一个所属对象引用。那就是this

 所以同步函数使用的锁是this

 

通过一个小程序来验证:同步函数使用的锁是this

 

使用两个线程来买票。

一个线程在同步代码块中。

一个线程在同步函数中。

都在执行买票动作。

package Thread;

public class ThirdThread implements Runnable{
    
	private int ticket =100;
	Object obj = new Object();
	boolean flag=true;
	@Override
	public void run() {
	   if(flag){
		  while(true){
			  
			  synchronized(obj){
				   if(ticket>0){
					   try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					   System.out.println(Thread.currentThread().getName()+"....code......"+ticket--);
				   					}
			  }
		  }
	   }
	   else{
		   while(true)
		    show();
	   }
	}
	
	public synchronized void show(){
		  if(ticket>0){
			   try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			   System.out.println(Thread.currentThread().getName()+".......show........."+ticket--);
		   					}
	}
	
	public static void main(String[] args){
		   ThirdThread tt = new ThirdThread();
		   
		   Thread t1 = new Thread(tt);
		   Thread t2 = new Thread(tt);
		   t1.start();   //
		   tt.flag=false;
		   t2.start();   
	}
		

}

通过这个买票的程序我们发现输出结果都是当前运行的线程名字加.....show.....加票的数量.

这是为什么呢?   当我们创建两个t1t2线程时,程序中总的有几个线程?  有3个线程:分别是主线程和t1t2线程。 

main方法中的代码块是由主线程执行的,当主线程执行到这的时候,t1马上就运行了吗?  没有它只是获得了执行权。然后主线程继续往下执行

flag变为false了。当cpu轮换到t1t2时,因为它们的flag都是flase所以它们执行的都是else后面的代码。

 

既然这样,那我们就来变变代码,t1.start();这个代码块后面加上一句Thread.sleep(20);  这句代码块的意思是让主线程停止20毫秒。

那么t1就能执行到同步代码块这里面的代码了。我们再来运行一下程序看看,我们会发现最后尽然出现一张0号票?  这说明我们程序有问题。

那是什么问题呢?  要同步的前提是什么?  必须要有两个线程或两个以上线程在执行,锁必须是一样的。  根据这两个前提我们看看第一个条件

我们是满足的,那第二个条件呢?  前面我们说到非静态同步函数的锁是this  而我们同步代码块我们给的锁是什么?  是object  所以锁不一样

我们只要在同步代码块那里把obj改成this,再次运行代码我们就会发现没有0号票了!

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不在是this。因为静态方法中不能定义this

静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码

文件对象。类名.class  该对象的类型是Class

线程间通讯:

其实就是多个线程在操作同一个资源

但是操作的动作不同。

 

线程取得控制权的方法有三:

 

执行对象的某个同步实例方法。

执行对象对应类的同步静态方法。

执行对该对象加同步锁的同步块。

 

静态的同步方法,使用的锁是该方法所在类的字节码文件对象.  即:类名.class

wait;notify;notifyAll;

都使用在同步中,因为要对持有监视器()的线程操作。

所以要使用在同步中,因为只有同步才具有锁。

 

为什么这些操作线程的方法要定义Object类中呢?

因为这些方法在操作同步中的线程时,都必须要标识它们所操作线程持有的锁。

只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒

不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义在object

生产者与消费者实例:

package itcast;
/**
 * 生产者和消费者实例
 * @author kang
 *
 */

//生产者
 class Producer implements Runnable{
	private Resource  r ;
	public Producer(Resource res){
		this.r=res;
	}
	@Override
	public void run() {
		while(true)
	    r.set("商品");
		
	}

}
 
 //资源
 class Resource{
	 private String name;
	 private int count=1;
	 private boolean flag = false;
	 
	 public synchronized void set(String name){
		 while(flag){
			 try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		 }
		 this.name  = name+"----"+count++;
		 System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
		 flag =true;
		 this.notifyAll();
	 }
	 
	 public synchronized void out(){
		 while(!flag)
		 {
			 try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		 }
		 System.out.println(Thread.currentThread().getName()+"---消费者---------"+this.name);
		 flag=false;
		 this.notifyAll();
	 }
 }
 
 //消费者
 class Consumer implements Runnable{
    private Resource res ;
    public Consumer(Resource r){
    	this.res=r;
    }
	@Override
	public void run() {
		while(true)
		res.out();
		
	}
	
	 
 }
 
 public class Produce{
	 public static void main(String[] args){
	 Resource rs = new Resource();
	  Producer  p = new Producer(rs);
	  Consumer c = new Consumer(rs);
	  
	  Thread t1 = new Thread(p);
	  Thread t2 = new Thread(p);
	  Thread t3= new Thread(p);
	  Thread t4 = new Thread(c);
	  Thread t5 = new Thread(c);
	  Thread t6 = new Thread(c);
	  t1.start();
	  t2.start();
	  t3.start();
	  t4.start();
	  t5.start();
	  t6.start();
	 }
 }

如何停止线程?

只有一种,那就是让run方法结束

开始多线程运行,运行代码通常是循环结构 

只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:

当线程处于了冻结状态,

就不会读取标记,那么线程就不会结束

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对

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

Thread类提供该方法interrupt();

示例代码:

package itcast;

 class StopThreadDemo {
      public static void main(String[] args){
    	     int count=0;
    	     StopThread st = new StopThread();
    	     Thread t = new Thread(st);
    	     Thread t2 = new Thread(st);
    	     
    	     t.start();
    	     t2.start();
    	     while(true){
    	    	 System.out.println(Thread.currentThread().getName()+"-------"+count);
    	    	 if(count++==60){  
//    	    		 st.changeFlag();
    	    		 t.interrupt();
    	    		 t2.interrupt();
    	    		 break;
    	    	 }
    	     }
    	     System.out.println("over");
      }
}
 
 class StopThread implements Runnable{
	  private boolean flag =true;
	  public synchronized void run(){
	  while(flag){
		  try {
			wait();
		} catch (InterruptedException e) {
		   System.out.println(Thread.currentThread().getName()+"........Exception");
		   flag = false;
		}
		  System.out.println(Thread.currentThread().getName()+"..........run");
	  }
	  }

 }

守护线程:

setDaemon

该方法的作用为当正在运行的线程都是守护线程时,java虚拟机退出。守护线程也会停止运行。

该方法必须在启动线程前调用

join方法:

A线程执行到了B线程的join方法时,A线程就会等待。等B线程都执行完,A才会执行。

 

jion方法可以用来临时加入线程执行。

 

懒汉式如果在函数上加了锁就会比较低效。

换成同步代码块效率会高一点。

Public static Single getInstance()

{

    If(s==null)

}

懒汉式延迟加载。如果多线程访问时会有安全问题。用同步函数和同步代码块都可以解决安全问题。但是用同步函数效率有点低。用同步代码块加双重判断可以让效率更高点。同步代码块使用的锁事该类所属的字节码对象。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值