Java基础视频教程第11天_多线程一

一、多线程——概述

	进程和线程:
		进程:正在执行中的程序。
		每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

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

		一个进程中至少有一个线程。
	
	参阅: http://jingyan.baidu.com/article/624e74598efcc834e9ba5a66.html

	Java VM  启动的时候会有一个进程java.exe. 该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。

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

二、多线程——创建线程——继承 Thread 类

java.lang.Object  -Thread  public class Thread extends Object implements Runnable  1,如何在自定义的代码中,自定义一个线程呢? 通过对API的查找,java已经提供了对线程这类事物的描述。就是 Thread 类。 创建线程的第一种方式:继承 Thread 类。 步骤: A,定义类继承 Thread 。 B,复写 Thread 类中的run方法。 目的:将自定义代码存储在run方法中,给线程运行。 C,调用线程的start方法, 该方法两个作用:启动线程,并调用run方法。 ======主线程执行的是main函数中的代码;自定义线程执行的是run()方法中的代码。 发现运行结果每一次都不同。 因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。 明确一点,在某一个时刻,只能有一个程序在运行。(多核除外) cpu在做着快速的切换,以达到看上去是同时运行的效果。 我们可以形象把多线程的运行理解为在互相抢夺cpu的执行权。 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,cpu说的算。 代码:
	class Demo extends Thread
	{
		public void run() //自定义线程运行的代码
		{
			for (int x=0 ;x<20 ;x++ )
			{
				System.out.println("demo: x="+x);
			}
		}
	}

	class ThreadDemo 
	{
		public static void main(String[] args) //主线程运行的代码
		{
			Demo d = new Demo(); //创建好一个线程
			
			//Thread d = new Demo(); //也可以这样写(多态)

			d.start(); //启动线程,并调用run()方法。

			//主线程运行的for循环
			for (int x=0 ;x<20 ;x++ )
			{
				System.out.println("x="+x);
			}
		}
	}

三、多线程——创建线程——run和start特点

为什么要覆盖run方法呢?

Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run() 方法。 
也就是说 Thread 类中的run方法,用于存储线程要运行的代码。
start方法的两个作用:
启动线程,并调用run方法。

======主线程要运行的代码存在main方法中,这是由虚拟机定义的。

四、多线程——线程练习

练习:创建两个线程,和主线程交替运行。
代码一:
	class Test extends Thread
	{
		private String name;
		Test(String name)
		{
			this.name = name;
		}
		public void run()
		{
			for (int x=0 ;x<10 ;x++)
			{
				System.out.println(name+"test run -- x="+x);
			}
		}
	}

	class ThreadTest 
	{
		public static void main(String[] args) 
		{
			Test t1 = new Test("one ");
			Test t2 = new Test("twooooooo");
			t1.start();
			t2.start();

			for (int x=0 ;x<10 ;x++ )
			{
				System.out.println("main x="+x);
			}
		}
	}

五、多线程——线程运行状态



六、多线程——获取线程对象及名称

1、线程都有自己默认的名称: Thread-编号 该编号从0开始。

2、currentThread()方法:返回对当前正在执行的线程对象的引用。该方法为静态方法,可以直接被类名调用。 Thread.currentThread();
static Thread currentThread() 
返回对当前正在执行的线程对象的引用。 

3、getName()方法:返回该线程的名称。

4、设置线程的名称:
setName(String name) 或者构造函数(String name)
示例: Thread.currentThrad().getName();

代码二:
	class Demo extends Thread
	{
		//private String name;
		Test(String name)
		{
			//this.name = name;
			super(name);
		}
		public void run()
		{
			for (int x=0; x<10 ;x++ )
				System.out.println(Thread.currentThread().getName()+": demo run--x="+x);
				//System.out.println(this.getName()+"demo: x="+x);
				//Thread.currentThread()=this
				//Thread.currentTread()是标准通用方法。
		}
	}
	class ThreadDemo 
	{
		public static void main(String[] args) 
		{
			Demo d1 = new Demo();
			Demo d2 = new Demo();
			d1.start();
			d2.start();
			for (int x=0; x<10 ;x++ )
				System.out.println("main--x="+x);
		}
	}

七、多线程——售票的例子

	class Ticket extends Thread
	{
		private int tick = 30;
		public void run()
		{
			while(true)
			{
				if(tick>0)
				System.out.println(Thread.currentThread().getName()+"--sale: "+tick--);
			}
		}
	}
	class TicketDemo 
	{
		public static void main(String[] args) 
		{
			Ticket t1 = new Ticket();
			Ticket t2 = new Ticket();
			Ticket t3 = new Ticket();
			t1.start();
			t2.start();
			t3.start();
		}
	}
该程序出现重复票。原因是每创建一个对象就创建30张票。如果tick加上static,可以解决此问题。但是一般不建议定义静态(生命周期太长)。所以用该方法创建线程解决此问题不可行。

八、多线程——创建线程——实现 Runnable 

java.lang.Runnable 
public interface Runnable 

通过查阅API发现,Thread类的除了有空参数才构造函数外,还有一个可以接收Runnable接口子类对象的构造函数。那么,创建多线程就有了第二种方式。

(1)创建线程方法二:
实现 Runnable 接口 

(2)步骤:
1,定义类实现 Runnable 接口。
2,覆盖 Runnable 接口中的run()方法。
将线程要运行的代码存放在该run方法中。
3,通过 Thread 类建立线程对象。
4,将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
为什么要将 Runnable 接口的子类对象传递给 Thread 的构造函数?
因为,自定义的run()方法所属的对象是 Runnable 接口的子类对象。所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属对象。
5,调用 Thread 类的start()方法开启线程并调用 Runnable 接口子类的run()方法。

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

实现方式好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。

两种方式区别:
继承 Thread:线程代码存放 Thread 子类run()方法中。
实现 Runnable ,线程代码存在接口的子类的run()方法。

代码:
	class Ticket implements Runnable 
	{
		private int tick=40;
		public void run()
		{
			while(true)
			{
				if(tick>0)
					System.out.println(Thread.currentThread().getName()+"...sale : "+ tick--);
			}
		}
	}

	class TicketDemo 
	{
		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);
			t1.start();
			t2.start();
			t3.start();

			//new Thread(t).start();
			//new Thread(t).start();
			//new Thread(t).start();
		}
	}

九、多线程——多线程的安全问题 

上面售票的例子中,由于多线程的出现,存在安全问题。

问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完;另一个线程参与进来执行。导致共享数据的错误。

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

如何判断哪些语句需要同步,就看哪些语句在操作共享数据。

代码:
	class Ticket implements Runnable 
	{
		private int tick=40;
		Object obj = new Object();//定义一个对象
		public void run() 
		{
			while(true)
			{
				synchronized(obj)//将需要同步执行的代码放在同步代码块中,同时需要往同步中传入一个对象。
				{
					if(tick>0)
					{
						try{Thread.sleep(10);}catch (InterruptedException e){}
						System.out.println(Thread.currentThread().getName()+"...sale : "+ tick--);
					}
				}
			}
		}
	}

	class TicketDemo 
	{
		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);
			t1.start();
			t2.start();
			t3.start();
		}
	}

十、多线程——多线程同步代码块 

Java对于多线程的安全问题提供了专业的解决方式。就是——同步代码块。

synchronized(对象)
{
需要被同步的代码
}

对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
======锁也叫监视器。

经典例子:火车上的卫生间。

同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。

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

利弊:
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,

十一、多线程——同步函数

因为同步代码块和函数都是用来封装代码的。
那么,当函数封装的代码和同步代码块封装的代码相同时,就可以使用同步函数。

示例:
	public synchronized void add(int n)
	{
		sum = sum + n;
		//try{Thread.sleep(10);}catch(Exception e){};
		System.out.println("sum="+sum);
	}

如何确定同步区域?
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

代码:
/*
需求:
银行有一个金库;
有两个储户,分别存300元,每次存100,存3次。
*/
	class Bank
	{
		private int sum;
		public synchronized void add(int n)
		{
			sum = sum + n;
			//调用sleep方法有可能出现异常,所以需要try catch处理。
			try{Thread.sleep(10);}catch(InterruptedException e){}
			System.out.println("sum="+sum);
		}
	}
	class Cus implements Runnable 
	{
		private Bank b = new Bank();
		public void run()
		{
			for (int x=0 ;x<3 ;x++ )
			{
				b.add(100);
			}
		}
	}
	class BankDemo 
	{
		public static void main(String[] args) 
		{
			Cus c = new Cus();

			Thread t1 = new Thread(c);
			Thread t2 = new Thread(c);
			t1.start();
			t2.start();
		}
	}

以上代码为毕老师讲课时所写,但个人认为该代码不严谨。个人改写代码如下(如有错误请指正):
	class Bank
	{
		private int sum;
		//构造函数私有,因为只有一个银行,使用单例设计模式
		private Bank(){}
		private static final Bank b = new Bank();
		public static Bank getInstance()
		{
			return b;
		}
		public synchronized void add(int n)
		{
			sum = sum + n;
			//调用sleep方法有可能出现异常,所以需要try catch处理。
			try{Thread.sleep(40);}catch(InterruptedException e){}
			System.out.println(Thread.currentThread().getName()+": sum="+sum);
		}
	}

	class Cus extends Thread
	{
		//创建一个储户对象的同时,指向了同一家银行
		private Bank b = Bank.getInstance();
		public void run()
		{
			for (int x=0 ;x<3 ;x++ )
			{
				b.add(100);
			}
		}
	}

	class Test1
	{
		public static void main(String[] args) 
		{
			Cus c1 = new Cus();
			Cus c2 = new Cus();

			c1.start();
			c2.start();
		}
	}

十二、多线程——同步函数的锁是 this 

同步函数示例代码:
	class Ticket implements Runnable 
	{
		private int tick = 100;
		public void run()
		{
			while(true)
			{
				this.show();
			}
		}
		public synchronized void show()
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"--sale: "+tick--);
			}
		}
	}

同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是 this 。所以同步函数使用的锁是 this 。

验证代码:
/*
使用两个线程来卖票,一个线程在同步代码块中,一个线程在同步函数中;
如果两个线程出现错误的票则不同步,如果不出现错误票则是同步的。
*/
	class Ticket implements Runnable 
	{
		private int tick=100;
		Object obj = new Object();
		boolean flag = true;
		public void run() 
		{
			if(flag)
			{
				while(true)
				{
					synchronized(obj)//这里使用obj,会出现0号票;如果将obj改为this,则不会出现错误票
					{
						if(tick>0)
						{
							try{Thread.sleep(10);}catch (InterruptedException e){}
							System.out.println(Thread.currentThread().getName()+"...code : "+ tick--);
						}
					}
				}
			}
			else
			{
				while(true)
					show();
			}
		}
		public synchronized void show()
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}
				System.out.println(Thread.currentThread().getName()+"...show()--- : "+ tick--);
			}
		}
	}

	class ThisLockDemo
	{
		public static void main(String[] args) 
		{
			Ticket t = new Ticket();

			Thread t1 = new Thread(t);
			Thread t2 = new Thread(t);

			t1.start();
			try{Thread.sleep(10);}catch(InterruptedException e){}

			t.flag = false;
			t2.start();
		}
	}

十三、多线程——静态同步函数的锁是 Class 

同步函数被静态修饰后,使用的锁不是 this ,因为静态方法中也不可以定义 this 。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class  该对象的类型是 Class 
静态的同步函数,使用的锁是该函数所在类的字节码文件对象。 类名.class 

验证代码(原理同上):
	class Ticket implements Runnable 
	{
		private static int tick=100;
		Object obj = new Object();
		boolean flag = true;
		public void run() 
		{
			if(flag)
			{
				while(true)
				{
					synchronized(Ticket.class)//如果该为obj则会出现0号票
					{
						if(tick>0)
						{
							try{Thread.sleep(10);}catch (InterruptedException e){}
							System.out.println(Thread.currentThread().getName()+"...code : "+ tick--);
						}
					}
				}
			}
			else
			{
				while(true)
					show();
			}
		}
		public static synchronized void show()
		{
			if(tick>0)
			{
				try{Thread.sleep(10);}catch (InterruptedException e){}
				System.out.println(Thread.currentThread().getName()+"...show()--- : "+ tick--);
			}
		}
	}

	class ThisLockDemo
	{
		public static void main(String[] args) 
		{
			Ticket t = new Ticket();

			Thread t1 = new Thread(t);
			Thread t2 = new Thread(t);

			t1.start();
			try{Thread.sleep(10);}catch(InterruptedException e){}

			t.flag = false;
			t2.start();
		}
	}

十四、多线程——单例设计模式——懒汉式

	class Single
	{
		private static Single s = null;
		private Single(){}
		public static  Single getInstance()
		{
			if(s==null)
			{
				synchronized(Single.class)
				{
					if(s==null)
						s = new Single();
				}
			}
			return s;
		}
	}
面试中常会使用懒汉式。
懒汉式的特点:
1、实例的延时加载。
2、在多线程访问时会出现安全问题,解决办法:加同步。
3、加同步的方式:同步代码块和同步函数的方式都可以,但是比较低效;用双重判断可以解决低效的问题。.
4、加同步时,使用的锁是:该类所属的字节码文件对象。

十五、多线程——死锁

在程序中请避免出现死锁。但是,面试中有时候需要写出一个死锁的程序。
代码一:
	class Ticket implements Runnable 
	{
		private int tick=100;
		Object obj = new Object();
		boolean flag = true;
		public void run() 
		{
			if(flag)
			{
				while(true)
				{
					synchronized(obj)
					{
						show();
					}
				}
			}
			else
			{
				while(true)
					show();
			}
		}
		public synchronized void show()
		{
			synchronized(obj)
			{
				if(tick>0)
				{
					try{Thread.sleep(10);}catch (InterruptedException e){}
					System.out.println(Thread.currentThread().getName()+"...code : "+ tick--);
				}
			}
		}
	}

	class DeadLockDemo
	{
		public static void main(String[] args) 
		{
			Ticket t = new Ticket();

			Thread t1 = new Thread(t);
			Thread t2 = new Thread(t);

			t1.start();
			try{Thread.sleep(10);}catch(InterruptedException e){}

			t.flag = false;
			t2.start();
		}
	}

代码二:
	class Test implements Runnable 
	{
		private boolean flag;
		Test(boolean flag)
		{
			this.flag = flag;
		}
		public void run()
		{
			if (flag)
			{
				while(true)
				{
					synchronized(MyLock.locka)
					{
						System.out.println("if locka");
						synchronized(MyLock.lockb)
						{
							System.out.println("if lockb");
						}
					}
				}
			}
			else
			{
				while(true)
				{
					synchronized(MyLock.lockb)
					{
						System.out.println("else lockb");
						synchronized(MyLock.locka)
						{
							System.out.println("else locka");
						}
					}
				}
			}
		}
	}

	class MyLock
	{
		static Object locka = new Object();
		static Object lockb = new Object();
	}

	class DeadLockTest 
	{
		public static void main(String[] args) 
		{
			new Thread(new Test(true)).start();
			new Thread(new Test(false)).start();
		}
	}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值