黑马程序员 java基础之多线程

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------



1.多线程

进程:是一个正在执行中的程序。

                  每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

 

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

                  线程在控制着进程的执行。

 

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

 

 

 

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

 

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

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

该线程称之为主线程。

 

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

 

 

 

1,如何在自定义的代码中,自定义一个线程呢?

 

通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。

 

创建线程的第一种方式:继承Thread类。

步骤:

1,定义类继承Thread

2,复写Thread类中的run方法。

        目的:将自定义代码存储在run方法。让线程运行。

 

3,调用线程的start方法,

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

 

 

 

发现运行结果每一次都不同。

因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。

明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)

cpu在做着快速的切换,以达到看上去是同时运行的效果。

我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。

 

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

为什么要覆盖run方法呢?

 

Thread类用于描述线程。

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

 

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

创建线程的第二种方式:实现Runable接口

 

步骤:

1,定义类实现Runnable接口

2,覆盖Runnable接口中的run方法。

        将线程要运行的代码存放在该run方法中。

 

3,通过Thread类建立线程对象。

4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

        为什么要将Runnable接口的子类对象传递给Thread的构造函数。

        因为,自定义的run方法所属的对象是Runnable接口的子类对象。

        所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

实现方式和继承方式有什么区别呢?

 

实现方式好处:避免了单继承的局限性。

在定义线程时,建立使用实现方式。

 

两种方式区别:

继承Thread:线程代码存放Thread子类run方法中。

实现Runnable,线程代码存在接口的子类的run方法。

多线程的运行出现了安全问题。

/*
需求:简单的卖票程序。
多个窗口卖票。
*/
class Ticket implements Runnable//extends Thread
{
	private  int tick = 100;
	public void run()
	{
		while(true)
		{
			if(tick>0)
			{
				//显示线程名及余票数
				System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
			}
		}
	}
}


class  TicketDemo
{
	public static void main(String[] args) 
	{
		//创建Runnable接口子类的实例对象
		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();
	}
}

线程的安全问题:容易出现错误信息。

问题的原因:

        当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,

        另一个线程参与进来执行。导致共享数据的错误。

 

解决办法:

        对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

 

 

 

Java对于多线程的安全问题提供了专业的解决方式。

 

就是同步代码块。

 

synchronized(对象)

{

        需要被同步的代码

 

}

对象如同锁。持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。

 

可理解为火车上的卫生间。

 

同步的前提:

1,必须要有两个或者两个以上的线程。

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

 

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

 

 

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

 

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

同步函数:

在函数上加上synchronized修饰符即可。

3、同步的前提

       a,必须要有两个或者两个以上的线程。

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

4、同步的利弊

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

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

5、如何寻找多线程中的安全问题

       a,明确哪些代码是多线程运行代码。

       b,明确共享数据。

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

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

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

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

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

通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:类名.class 该对象的类型是Class

几个小问题:

       1wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?

               a,这些方法存在与同步中。

               b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。

               c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

       2wait(),sleep()有什么区别?

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

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

       3为甚么要定义notifyAll

        因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。

2JDK1.5中提供了多线程升级解决方案。

       将同步synchronized替换成显示的Lock操作。将ObjectwaitnotifynotifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

/*   
02.给卖票程序示例加上同步代码块。 
03.*/  
04.class Ticket implements Runnable  
05.{  
06.    private int tick=100;  
07.    Object obj = new Object();  
08.    public void run()  
09.    {  
10.        while(true)  
11.        {  
12.            //给程序加同步,即锁  
13.            synchronized(obj)  
14.            {  
15.                if(tick>0)  
16.                {  
17.                    try  
18.                    {     
19.                        //使用线程中的sleep方法,模拟线程出现的安全问题  
20.                        //因为sleep方法有异常声明,所以这里要对其进行处理  
21.                        Thread.sleep(10);  
22.                    }  
23.                    catch (Exception e)  
24.                    {  
25.                    }  
26.                    //显示线程名及余票数  
27.                    System.out.println(Thread.currentThread().getName()+"..tick="+tick--);  
28.                }  
29.            }     
30.        }  
31.    }  
32.}  
死锁问题
 
/*
写一个死锁程序
*/

//定义一个类来实现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();
	}
}

 线程之间的通讯

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

*/

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
	}
}

 

 

 

你可能感兴趣的:(黑马程序员 java基础之多线程)