黑马程序员:多线程的学习

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

多线程的概述
进程:正在进行(运行)中的程序
线程:就是进程中一个执行单元或执行情景或执行路径
	负责进程中程序执行的控制单元
	一个进程中至少要有一个线程
当一个进程中线程有多个时,就是多线程

多线程解决的问题:可以让多部分代码同时执行
什么是同时执行呢??
 其实都是CPU在瞬间做着快速切换完成的。
 cpu的切换随机的,按照时间片的方式来完成。

 程序的执行不是内存和硬盘完成的,
 硬盘是持久化存储
 内存是临时存储

 ---------------------------------------------
 JVM中的多线程
    JVM的多线程是从Main(主线程)入口开始执行的。
    其实JAVA运行就是多线程 
    在执行main函数中内容的同时,垃圾回收器也在回收堆内存的垃圾
    所以执行main方法的线程,和执行垃圾回收器的线程同时在执行
    这就是多线程。

    System.gc(); 定义垃圾回收器后,不会马上就开始执行。垃圾回收器是不定时的进行回收

    垃圾回收器是存在Object类中的。
    -----------多线程就是每个线程都在同时运行着不同的功能。

JVM在启动线程的时候,每一个线程都有自己要执行的内容
其中一个负责执行main函数中的内容,这个线程称之为:主线程 main
负责垃圾回收器运行的称之为垃圾回收线程
-----------------------
创建线程的目的
  我们就可以推理出,为什么要启动多线程 呢?
  因为有多部分代码需要同进执行
  而且每一个线程都有自己要执行的内容,这个内容称之为:线程的任务
  简单说:
    启动线程就是为了执行任务,当任务有多个需要同时执行时,就需要多个线程。
 
如果 不使用线程话,在进入主线程后,先要把第一个程序执行完,再执行另一个程序
希望同时执行不同的程序。
如何创建线程
 java既然需要调用底层才能完成进程的建立和线程的创建
 那么,java应该有对外提供描述线程的对象来方便于程序员对线程的操作

 在java的lang包中,去找描述线程的类.Thread


 通过查阅thread类的描述,发现
 创建线程有两种方式:
 ------------------------------------------------------- 创建线程方式一:
  步骤:
  1、定义类继承Thread类
  2、覆盖Thread类中的run方法
  3、创建Thread类的子类对象
  4、调用Thread类的start方法开启线程
  线程要执行什么内容,线程自己肯定有定义
  这个定义就是run 方法,这个方法中定义就是线程要运行的内容
可是该run方法的内容并不是我们所需要的

怎么能让线程去运行我们自定义的内容呢?
	只有覆盖Thread类中的run方法,覆盖之前需要先继承
	这就是为什么要继承Thread类并覆盖run方法的原因

主线程要运行的任务都在main函数中
自定义的线程要运行的任务都在run方法中
run方法就是专门用于存储线程任务代码的地方


 Thread.currentThread().getName(); //获取当前执行线程的名字

    继承Thread 并要重写run方法
    class Demo extends Thread
    {
    //将线程要运行的自定会引起的任务都封装到了run方法中。
     public void run()
     {
      for(int x=1;x<=10;x++)
      {
       for(int y=99999999;y<999999999;y++){}//刻意写个循环让程序慢一点
	
      }
     }
    
    }
    d1.run();  //还是在运行主线程的,所以调用run 方法是不行的。
		// 线程光创建不行,还需要运行才会执行。发现要运行线程,必须使用start方法


start方法做了两件事,开启了线程让线程可以运行,并调用run方法。

为什么结果是不规律呢?
	那是因为现在有三个线程在同时执行,cpu在这三个线程间快速的切换,
	随机性造成了结果的没规律

-----------------
调用run方法和调用start方法的区别:
	1、调用run方法仅仅是一般对象在调用对象中的方法,并没有开启线程 
	还是主线程在完成的run方法的运行
	2、调用start方法,是开启了一个线程(一个新的执行路径)这个线程却说执行了run方法里面的数据
每个线程都会有自己所属的栈区



				临时阻塞状态 :这种状态的线程 具备cpu的执行资格
				              不具备cpu的执行权


				这种状态的线程,
				具备cpu的执行资格
				具备着 cpu的执行权

被创建        start()          运行状态   sleep()时间到     冻结状态  这种状态的线程释放了cpu的执行权,
									并释放了cpu的执行资格
						notify();唤醒
				stop()
				run()结束

				消亡状态

售票的例子
class Ticket extends 
{
 private static int num=100; //可以将票数定义成静态的让其数据变成共享数据
 //将卖票的代码定义在run方法中
 public void run()
 {
 while(true)
 {
	if(num>0)
	{

	 System.out.println(Thread.currentThread().getName()"第售票"+num--);
	}
 }
 }


}
如果是继承Thread类,覆盖run方法这种情况
    Thread的子类即封装了线程的任务,而且自身还是一个线程
    对象这就是任务和对象耦合性过高

为什么要传递
  让线程对象创建时,就要明确要运行哪个run方法以,而这个run方法是
  需要被对象调用的,所以将run方法所属的对象传递给Thread类的

--------------------------------------------------创建线程方式二:
 1、 定义一个类实现Runnable接口
  覆盖Runnable接口中的run方法
  2、将线程的要运行的代码存储到run方法中
  3、创建该接口的子类对象
  4、通过Thread类进行线程的创建,并将Runnable接口的子类对象作为
  Thread类的构造函数的实参进行传递 
  5、调用Thread类中的start方法开启线程

  class Demo implements Runnable
  {
  
  public void run()
  {
  线程的任务
  
  }
  
  }

  Demo d=new Demo();
  这是一个任务的对象
  Thread t1=new Thread(); 创建线程对象去运行指定的任务
  Runnable接口的出现,线程对象在使用该接口,我们只要实现该接口
  即可,将实现的子类对象,也就是线程任务传递给线程对象就哦了 


  ----------以后创建线程建议使用第二种方式就是实现 Runnable接口的方式
  该方式避免单继承的局限性

Runnable接口的由来其实就是将线程的任务进行对象的封装
 将线程任务封装成对象后,通过Runnable接口可以降低Thread对
 象的耦和性
    
---多线程的安全问题--------------

多线程安全问题的原因
  多个线程在操作共享数据
  操作共享数据的代码有多条
一个线程在执行多条操作共享数据的过程中,其他线程
参与了运算,这是就会发生安全问题 

想要分析是否安全问题 
 依据:线程任务中有没有共享数据,该数据是否被多条
 语句操作。

 多线程安全问题的解决方案

  只要保证一个线程在执行多条操作共享数据的语句时,其也线程不能参
  与运算即可
   当该线程执行完后,其他线程才可以执行这些语句

   代码表现: 同步代码块 

   synchronized(对象) //对象可以是任意的对象。
   {
    需要被同步的语句 
   }


   同步的原理:其实就是将需要同步的代码进行封装,并在该代码上加一个锁

   同步的例子
   例如: 火车上的卫生间

   同步的好处:解决了多线程的安全问题
   同步的弊端: 会降低性能
 一种现象:出现了多线程安全问题,为了解决加上同步发现问题依旧
   怎么办?
     ************出现此问题:不是多个线程 操作同一把锁
    
同步的前提:
	必须要保证在同步中有多个线程,因为同步中只有一个线程该同步是没有意义。
	必须要保证多个线程在同步中使用的是同一个锁
     

 *********必须保证多个线程使用的锁是同一个。这时才能使多个线程被同步了*****************

 --银行的例子。
 calss Bank
 {
 private int sum;  //sum是共享数据
  public void add(int num) 
  {
   sum= sum + num;  //每次用来记录存的金额数
  }
 }
class Cus implements Runnable
{
	private Bank b=new Bank(); // 
	public void run()
	{
	 
	 for(int x=0;x<3;x++)
	 {
	  b.add(100);
	 }
	}
}
这是一个多线程程序,如何分析安全问题?
1、从线程任务代码中分析
2、分析是否有共享数据。有没有多条语句在操作共享数据呢?
发现同步代码块和同步函数封装的内容是一样的
直接用函数封装不就可以了吗?
 差别在哪? 函数不具备同步性,所以只要让函数具备同步性就哦了 
  直接将同步关键字作为修饰函数即可
  这样函数就具备了同步
  public synchronized void run() //同步函数
  {
  
  }
  这就是同步函数。同步的另一种表现形式。

  同步函数和同步代码快的不同?
   函数肯定被对象调用,代表调用函数对象的引用就是this.
   也就是说同步函数所使用的锁就是this 

   同步函数和同步代码块区别?
1、同步函数比同步代码块写法简单
2、同步函数使用的锁是this,同步代码块使用的锁是任意指定的对象

建议开发时,使用同步代码块,尤其是需要不同锁时。

-------------静态函数使用的锁---------------------------
静态的锁是:类名.class 就是静态函数的锁
静态随着类的加载而加载,这时内存中存储的对象到少有一个,就是该类字节码文件对象
这个对象的表式方式,类名。class它就是表示该类的字节码文件对象

----------------单例设计模式------------------
----饿汉式:-------------------------------------------
class Single
{
  private static final Single s=new Single();
  prvate Single(){}
  public static Single getInstance()
  {
  
  return s ;
  }
}
-----懒汉式--------------------------------

/*
懒汉式在被多线程并发访问时,会出现多线程安全问题 
为了解决,加入了同步,虽然安全问题 解决了
但是性能降低了,有没有办法将安全问题解决的同时,还可以提高性能

有的,改成同步代码块
可以通过双重判断的形式完成这个过程 
*/
class Single 
{
private static Single s =null;
private Single(){}
public static Single getInstance()
{
if(s==nll)  // 用来判断是不是为空,可以降低锁的次数
{
 synchronized(Single.class)  //同步代码块,
 {
 if(s==null)
 {
 s=new Single();
 return s;
 }
 }
 }

}
}


-------------死锁------------------------
同步的另一个弊端:死锁
其实就是你拿着我的锁,我拿着你的锁
最常见的死锁情况,就是同步嵌套
同步中还有同步,两个同步用的不是一个锁
记住尽量避免同步嵌套的情况

class Dead implements Runnable
{
	//定义一个标记符
	private boolean flag;
	Dead(boolean flag)//构造函数进行初始化值
	{
	 this.flag=flag;
	}
	//实现Runnable接口,并重写run方法
	public void run()
	{
	 while(true) //无限循环
		{
			if(flag) // 当为false就执行同步代码块。
			{
				synchronized(Locket.lockA)//同步代码快
				{
					System.out.println(Thread.currentThread().getName()+"LOCKA  ");
					synchronized(Locket.lockB)
					{
					System.out.println(Thread.currentThread().getName()+"LOCKB  ");
					}
				
				}

			}else
			{
				synchronized(Locket.lockB)
				{
					System.out.println(Thread.currentThread().getName()+"LOCKB  ");
					synchronized(Locket.lockA)
					{
					System.out.println(Thread.currentThread().getName()+" LOCKA ");
					}
				
				}
			}

	 }
	
	
	}

}

/*定义两个锁*/
class Locket
{
	public static Object lockA=new Object();
	public static Object lockB=new Object();
}

class DeadDemo 
{
	public static void main(String[] args) 
	{
		Dead d=new Dead(false);  //创建Dead对象
		Dead d1=new Dead(true);
		Thread t1=new Thread(d);  //创建一个线程
		Thread t2=new Thread(d1);
		t1.start();         //开启线程 
		t2.start();

	}
}

---------------线程间的通信----------------------------
线程间通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同
   (就是一个线程存东西另一个线程取东西,但是只能存一个取一个)
 class Resource
{
	String name;  //定义变量用来存名字
	String sex;    //定义变量用来存性别
	boolean flag=false;  // 定义一个标记
}
//描述输入任务
class Input implements Runnable
{
	Resource r=null;  //创建Resource r的类变量 
	Input(Resource r) //构造函数进行初始化值 
	{
	 this.r=r;   //this.r this表示本类对象的引用,用来区分变量与局变量,并进行赋值
	}
	public void run()
	{
		int x=0;
		while(true)
		{
			synchronized(r)//只要是对象都是可以是锁,用来解决安全问题
			{
				if(r.flag) //当为true的时候就会wait()等待,为false时会执行if
				{
				 try
				 {
					r.wait();
				 }
				 catch ()
				 {
				 }
				}
				if(x==0)
				{
				r.name="mike";
				r.sex="man";
				}else
				{
				r.name="小红";
				 r.sex="woman女女 女女";
				}
				flag=true;
				r.notify(); //必须要写在同步代码块里面。用来唤醒
			}
			x=(x+1)%2;
		}
	}
}
//描述输出任务
class Output implements Runnable
{
	Resource r=null;
	Output(Resource r)
	{
	 this.r=r;
	}
	public void run()
	{
		while(true)
		{
			synchronized(r)
			{
				if(!r.flag) //此处为false时,就会wait(),当为true时,结果就不会成立,就不会报执行wait().
			System.out.println(r.name+"         "+r.sex);
				r.flag=false;
				r.notify();   //必须要写在同步代码块里面
	}
	}
}
}
class ResourceDemo 
{
	public static void main(String[] args) 
	{
		//创建资源对象
		Resource r =new Resource();
		//创建任务对象
		Input in=new Input(r);
		Output out=new Output(r);
		//创建线程对象
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		//开启线程 
		t1.start();
		t2.start();
		
	}
}




等待唤醒机制
 涉及的方法
 wait(): 等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
 notify(): 唤醒,唤醒线程池中被wait的线程,一次唤醒一个,而且是任意的
 notifyAll():唤醒全部,可以将线程池中的所有wait线程都唤醒

 唤醒的意思就 是让线程池中的线程具备执行资格

 这些方法都要使用在同步中才有效
 这些方法在使用时必须要明确所属锁,这样才可以明确出这些操作到底是哪个锁上的线程 

为什么要定义锁在Object类中????
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象
能被任意对象调用的方法一定定义在object中

/*
代码重新优化
*/

class Resource
{
	private String name;  //定义变量用来存名字
	private String sex;    //定义变量用来存性别
	private boolean flag=false;  // 定义一个标记
	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
{
	Resource r=null;  //创建Resource r的类变量 
	Input(Resource r) //构造函数进行初始化值 
	{
	 this.r=r;   //this.r this表示本类对象的引用,用来区分变量与局变量,并进行赋值
	}
	public void run()
	{
		int x=0;
		while(true)
		{
			if(x==0)
			{
			
			r.set("Mike","man");
			}
			else
			{
			
			r.set("小红","vvv女女女 ")
			}
			
			x=(x+1)%2;
		}
	}
}
//描述输出任务
class Output implements Runnable
{
	Resource r=null;
	Output(Resource r)
	{
	 this.r=r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}
class ResourceDemo 
{
	public static void main(String[] args) 
	{
		//创建资源对象
		Resource r =new Resource();
		//创建任务对象
		Input in=new Input(r);
		Output out=new Output(r);
		//创建线程对象
		Thread t1=new Thread(in);
		Thread t2=new Thread(out);
		//开启线程 
		t1.start();
		t2.start();	
	}
}

-------------------------生产者消费者问题-----------------------------
class Resource
{
 private String name;  //生产出来的产品名
 private int count;  //用来定义产品的编号
 prvate boolean flag;
	 public synchronized void set(String name)
	 {
	    if(flag)// 可以将此处的if改成while是一种方法,用来解决多生产多消费的问题 ,
			因为在执行while里面的代码时,都会去与判断语句里面的进行对比,但是必须要使用notifyAll唤醒全部
		try{this.wait();}catch(Exception e){}
		  this.name=name +conut;
		  System.out.println(Thread.currentThread().getName()+"  生产者"+this.name);
		flag=false;
		this.notify();
	 }
	 public synchronized void get()
	{
		if(!flag)// 可以将此处的if改成while是一种方法,用来解决多生产多消费的问题 ,
			因为在执行while里面的代码时,都会去与判断语句里面的进行对比,但是必须要使用notifyAll唤醒全部
		{
		  System.out.println(Thread.currentThread().getName()+"  消费者"+this.name);
		  count++;
		  flag=false;
		  this.notify();
		  }
	 }



}
//生产者
class Cur implements Runnable
{
  private  Resource r=null;
   Cur(Resource r)
   {
    this.r=r;
   }
   public void run()
   {
   while(true)
   {
   r.set("馒头");
   }



}

//消费者
class Pro implements Runnable
{
	Resource r=null;
	Pro(Resource r)
	{
	    this.r=r;
	 }

	  public void run()
	  {
	   while(true)
	   {
	   r.get();
	   }
	  
	  }
}


当此代码进行多线程的时候 就会出现问题了。有可以多条线程操作同一个数据
或都生产者产出多个产品,但是消费者只消费了一个产品
 本方唤醒了本方,被唤醒的本方没有判断标记
 只要将if判断改成while判断

 将if改为while循环判断标记后,出现了死锁
 因为本方唤醒本方,而被唤醒的本方一判断标记,就继续等待,这样所有的线程都等待
 必须唤醒对方才行,但是没有直接唤醒对方的动作,所以就使用了notifyAll,唤醒全部


 JDk1.5--------------------LOCK接口--------------------------------
 java.util.concurrent.locks;

  JDK1.5后出现的新的接口和类
Lock:比同步函数和同步代码块要好一些。

 分析发现:
 同步函数还是同步代码块所做的都是隐式的锁操作。
 同步函数或者同步代码块使用的锁和监视器是同一个。
 
  LOck接口:是将锁进行单独对象的封装。而且提供对锁对象的很多功能
  例如:lock获取锁
	unlock释放锁
	LOCK对锁的操作都是显示操作
所以它的出现要比同步函数或同步代码块明确的多
更符合面向对象思想
   简单说:LOCK接口的出出替代同步           ( 什么点什么 的类名的时候就是内部类)

   创建一个锁对象
   Lock lock=new ReentrantLock();//此处要进行引包 java.util.concurrent.locks.*;在实际开发的时候要用到(.Lock)

   //获取锁
     lock.lock();
     //释放锁
     lock.unlock();//此处要写在finally里面,必须要释放锁**************


    ************ wait()和notify()必须要写在同步代码块里面,因为它们自己要标示自己所属的锁是谁
   

原来在同步中,锁和监视器是同一个对象
现在,升级后,锁是一个单独的对象
而且将监视器的方法也单独封装到了一个对象中,这个对象就是升级后的Condition
升级后,都进行 了单独的封装,锁被封装成了LOck对象,
监视器方法都被封装到了Condition对象(监视器对象)中

说白了。Lock替代了同步,Condition替代了object中的监视器方法

Condition中提供了监视器的方法:awati(),signal(),signalAll();
Lock lock=new ReentrantLock(();

直接通过LOCK接口中的newCondition()方法就可以获取到能绑定到该LOCK对象的上的监视器对象Condition
	private Lock lock=new ReentrantLock(();
	private Condition con=lock.newCondition();
-------------------------停止线程---------------------------------------------------

停止线程
 1、stop();过时。
 2、run()方法结束,当线程没有了要运行的代码线程就结束了,意味着任务结束,线程消失
	一般情况下RUN方法中都会定义循环
	开启新的执行路径就是为了对多次的运算和其他代码进行同时的执行

	开启多线程会同时并重复做很多运算
	结束run方法,只要控制run中的循环即可
	控制循环通常需要通过标记来完成
	-----中断线程 --------
	interrupt
Thread类中有一个interrupt方法,可以将线程的冻结状态清除
让线程恢复到具备执行资格

setDaemon守护线程 :该方法必须在启动线程前调用。也只做后台线程 
 setDaemon(); //将t对应的线程标记成后台线程
 join()方法:加入(等待应线程中止)
 setPriority() 更改线程的优先级


 wiat() 和sleep()的异同点:
   两个方法都可以让线程处于冻结状态
   sleep()必须指定时间, 
   wait()是可以指定时间,也可以不指定。
   sleep()会释放执行权,不会释放锁
   wait()会释放执行权,会释放锁

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值