11、多线程


十一、多线程

1、系统结构图(xmind)

2、tips

——1.多线程概述

JVM启动时启动了多条线程,至少有两个线程可以分析的出来:

1.执行main函数的线程,该线程的任务代码都定义在main函数中

2.负责垃圾回收的线程。

System类的gc方法告诉垃圾回收器调用finalize方法,但不一定立即执行。


——2.继承Thread类

       创建线程的目的就是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务,jvm创建的主线程的任务都定义在了主函数中,而自定义的线程,它的任务在哪儿呢?Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述,这个任务就是通过Thread类中的run方法来体现,也就是说,run方法就是封装自定义线程运行任务的函数,run方法中定义的就是线程要运行的任务代码。开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法,将运行的代码定义在run方法中即可。


多线程栗子:

class Demo extends Thread
{
	private String name;
	Demo(String name)
	{
		this.name = name ;
	}
	//复写Thread类的run方法
	public void run()
	{
		//Thread.currentThread().getName()当前线程名字
		for(int x = 0;x < 10;x++)
		{
			System.out.println(name+"...x="+x+"....Threadname:"+Thread.currentThread().getName());
		}
	}
}
class ThreadDemo4
{
	public static void main(String[] args)
	{
		Demo d1 = new Demo("毛1");
		Demo d2 = new Demo("毛2");
		//开启线程,调用run方法
		d1.start();
		d2.start();
		for(int x=0;x<10;x++)
		{
			System.out.println("x:"+x+"...over....."+"Threadname:"+Thread.currentThread().getName());
		}
	}
}

运行结果:

1.可以通过Thread的getName方法获取线程的名称,名称格式:Thread-编号(从0开始)。
2.Thread在创建的时候,该Thread就已经命名了。

——3.实现Runnable接口

栗:

//准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行
//通过接口的形式完成
class Demo implements Runnable
{
	//复写run方法
	public void run()
	{
		show();
	}
	public void show()
	{
		for(int x=0;x<10;x++)
		{
			System.out.println(Thread.currentThread().getName()+"...."+x);
		}
	}
}
class ThreadDemo1
{
	public static void main(String[] args)
	{
		Demo d = new Demo();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t2.start();
	}
}


运行结果:

      


——4.线程安全问题

栗:

/*
需求:模拟4个线程卖100张票。
*/
class Ticket implements Runnable 
{
	private int num = 100;
	Object obj = new Object();
	public void run()
	{
		while (true)
		{
			//同步锁,当有线程进入时,将会锁住,在当前线程为运行完前不允许进入
			synchronized(obj)
			{
				if (num > 0)
				{
					System.out.println(Thread.currentThread().getName()+"..."+num--);
				}
			}
		}
	}
}
class TicketDemo2
{
	public static void main(String[] args)
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}


运行结果:

           

           

原因分析:从运行结果可以得知,安全问题已经解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。当num=1时,Cpu切换到某个线程后,其他线程将无法通过同步代码块继而进行if判断语句,只有等待前一个线程执行完num--操作,并且跳出同步代码块后,才能抢到锁,其他线程即使抢到锁,然而,此时num已经是0,也就无法通过if语句判断,从而无法再执行num--操作,也就不会出现0、-1、-2的情况了。


——5.同步函数和同步代码块的区别

1.同步代码块的锁是任意的对象

2.同步函数的锁是固定的this。

建议使用同步代码块

       同步函数的锁是固定的this,同步代码块的锁是任务的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

       静态的同步函数使用的锁是该函数锁属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

栗:

class Ticket implements Runnable 
{
	private int num = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				//使用this作为锁,而线程sleep需要抛出异常所以使用try/catch捕捉
				synchronized(this.getClass())//Ticket.Class()
				{
					if(num>0)
					{
						try
						{
							Thread.sleep(10);
						}
						catch (InterruptedException e)
						{
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()+"...."+num--);
					}
				}
			}
		}
		else
			while(true)
				show();
	}
	//当flag为false时执行同步函数
	public synchronized void show()
	{
		if(num > 0)
		{
			try
			{
				Thread.sleep(10);
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"...."+num--);
		}
	}
}
class SynFunctionLockDemo2
{
	public static void main(String[] args)
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		/*
		需要sleep10毫秒的原因是因为可能线程t1尚未真正启动,flag就被设置为false,那么当ti执行
		时,就会按照flag为false的情况执行,线程t2也按照flag为false的情况执行.
		*/
		try
		{
			Thread.sleep(10);
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}
		t.flag = false;
		t2.start();
	}
}


运行结果:


——6.多线程下的单例模式

饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况

懒汉式存在安全问题,能够使用同步函数解决:

class Single
{
	public static Single s =null;
	public void Single(){}
	public static Single getInstance()
	{
		if(s == null)
		{
			Synchronized(this.getClass())//Single.class
			{
				if(s == null)
					s = new Single();
			}
		}
		return s;
	}
}

如果使用同步函数,效率较低,因为每次都需要判断,而上部代码的方式不会有这样的问题,当任何一个线程执行到第一个if判断语句时,Single对象已经创建,则直接能够获取,不用判断是否能够获取锁,相对于使用同步函数的方法就提升了效率。


——7.死锁

当同步中嵌套同步时就有可能出现死锁的现象。

栗:

/* 
写一个死锁程序 
*/  
  
//定义一个类来实现Runnable,并复写run方法  
class LockTest implements Runnable  
{  
    private boolean flag;  
    LockTest(boolean flag)  
    {  
        this.flag=flag;  
    }  
    public void run()  
    {  
        if(flag)  
        {  
            while(true)  
            {  
                synchronized(LockClass.locka)//a锁  
                {  
                    System.out.println(Thread.currentThread().getName()+"------if_locka");  
  
                    synchronized(LockClass.lockb)//b锁  
                    {  
                    System.out.println(Thread.currentThread().getName()+"------if_lockb");  
                    }  
                }  
            }  
        }  
        else  
        {  
            while(true)  
            {  
                synchronized(LockClass.lockb)//b锁  
                {  
                  System.out.println(Thread.currentThread().getName()+"------else_lockb");  
  
                    synchronized(LockClass.locka)//a锁  
                    {  
                   System.out.println(Thread.currentThread().getName()+"------else_locka");  
                    }  
                }  
            }  
        }  
    }  
}  
  
//定义两个锁  
class LockClass  
{  
    static Object locka = new Object();  
    static Object lockb = new Object();  
}  
  
class DeadLock  
{  
   public static void main(String[] args)  
   {  
       //创建2个进程,并启动  
       new Thread(new LockTest(true)).start();  
       new Thread(new LockTest(false)).start();  
   }  
} 


运行结果:


——8.线程中通信

等待/唤醒机制设计的方法

1.这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。

2.必须要明确到底操作的是哪个锁上的线程!

wait和sleep区别?

1)wait可以指定时间也可以不指定。sleep必须指定时间。

2)在同步中时,对CPU的执行权和锁的处理不同。

        wait:释放执行权,释放锁。

        sleep:释放执行权,不释放锁。

        为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定在object类中。


栗:

/*
	有一个资源
一个线程往里存东西,如果里边没有的话
一个线程往里取东西,如果里面有得话
*/

//资源
class Resource
{
	private String name;
	private String sex;
	private boolean flag=false;
	
	public synchronized void setInput(String name,String sex)
	{
		if(flag)
		{
			try{wait();}catch(Exception e){}//如果有资源时,等待资源取出
		}
		this.name=name;
		this.sex=sex;

		flag=true;//表示有资源
		notify();//唤醒等待
	}

	public synchronized void getOutput()
	{		
		if(!flag)
		{
			try{wait();}catch(Exception e){}//如果木有资源,等待存入资源
		}
		System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出
				
		flag=false;//资源已取出
		notify();//唤醒等待
	}
}


//存线程
class Input implements Runnable
{
	private Resource r;
	Input(Resource r)
	{
		this.r=r;
	}
	public void run()//复写run方法
	{
		int x=0;
		while(true)
		{
			if(x==0)//交替打印张三和王羲之
			{
				r.setInput("谢",".....man");
			}
			else
			{
				r.setInput("毛","..woman");
			}
			x=(x+1)%2;//控制交替打印
		}
	}
}

//取线程
class Output implements Runnable
{
	private Resource r;
	Output(Resource r)
	{
		this.r=r;
	}
	public void run()//复写run方法
	{
		while(true)
		{
			r.getOutput();
		}
	}
}
<strong>

</strong>
class ResourceDemo6
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();//表示操作的是同一个资源

		new Thread(new Input(r)).start();//开启存线程

		new Thread(new Output(r)).start();//开启取线程
	}
}<strong>
</strong>

运行结果:
       

几个小问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
       a,这些方法存在与同步中。
       b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
       c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
       wait():释放cpu执行权,释放锁。
       sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

——9.JDK1.5新特性

栗:
/*
生产者生产商品,供消费者使用
有两个或者多个生产者,生产一次就等待消费一次
有两个或者多个消费者,等待生产者生产一次就消费掉

*/

import java.util.concurrent.locks.*;

class Resource 
{	
	private String name;
	private int count=1;
	private boolean flag = false;
	
	//多态
	private Lock lock=new ReentrantLock();

	//创建两Condition对象,分别来控制等待或唤醒本方和对方线程
	Condition condition_pro=lock.newCondition();
	Condition condition_con=lock.newCondition();

	//p1、p2共享此方法
	public void setProducer(String name)throws InterruptedException
	{
		lock.lock();//锁
		try
		{
			while(flag)//重复判断标识,确认是否生产
				condition_pro.await();//本方等待

			this.name=name+"......"+count++;//生产
			System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产
			flag=true;//控制生产\消费标识
			condition_con.signal();//唤醒对方
		}
		finally
		{
			lock.unlock();//解锁,这个动作一定执行
		}
		
	}

	//c1、c2共享此方法
	public void getConsumer()throws InterruptedException
	{
		lock.lock();
		try
		{
			while(!flag)//重复判断标识,确认是否可以消费
				condition_con.await();

			System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费
			flag=false;//控制生产\消费标识
			condition_pro.signal();
		}
		finally
		{
			lock.unlock();
		}

	}
}

//生产者线程
class Producer implements Runnable 
{
	private Resource res;
	Producer(Resource res)
	{
		this.res=res;
	}
	//复写run方法
	public void run()
	{
		while(true)
		{
			try
			{
				res.setProducer("商品");
			}
			catch (InterruptedException e)
			{
			}
		}
	}
}

//消费者线程
class Consumer implements Runnable
{
	private Resource res;
	Consumer(Resource res)
	{
		this.res=res;
	}
	//复写run
	public void run()
	{
		while(true)
		{
			try
			{
				res.getConsumer();
			}
			catch (InterruptedException e)
			{
			}
		}
	}

}

class  ProducerConsumer
{
	public static void main(String[] args) 
	{
		Resource res=new Resource();

		new Thread(new Producer(res)).start();//第一个生产线程 p1
		new Thread(new Consumer(res)).start();//第一个消费线程 c1

		new Thread(new Producer(res)).start();//第二个生产线程 p2
		new Thread(new Consumer(res)).start();//第二个消费线程 c2
	}
}


运行结果:


——10.停止线程
1.开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。
     如:run方法中有如下代码,设置一个flag标记。

public  void run()
{
	while(flag)
	{	
		System.out.println(Thread.currentThread().getName()+"....run");
	}
}

那么只要在主函数或者其他线程中,在该线程执行一段时间后,将标记flag赋值false,该run方法就会结束,线程也就停止了。
2、上面的1方法可以解决一般情况,但是有一种特殊情况:就是当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
        当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法interrupt();
如:
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  StopThreadDemo
{
	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)
			{
				t1.interrupt();//清除冻结状态
				t2.interrupt();
				st.changeFlag();//改变循环标记
				break;
			}
			System.out.println(Thread.currentThread().getName()+"......."+num);
		}
		System.out.println("over");
	}
}

运行结果:
      

——11.扩展
1、join方法
        当A线程执行到了b线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)join可以用来临时加入线程执行。
2、setPriority()方法用来设置优先级
        MAX_PRIORITY 最高优先级10
        MIN_PRIORITY   最低优先级1
        NORM_PRIORITY 分配给线程的默认优先级
3、yield()方法可以暂停当前线程,让其他线程执行。





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