黑马程序员——java基础--多线程

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------

三、 多线程

1.概述

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

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

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

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

而且这个线程运行的代码存在于。

该线程称之为主线程。

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

 

2.创建线程

自定义一个线程:

自定义线程,就要用到javaAPI中的类Thread类

创建线程有两种方式:

第一种:继承Thread类

/*
第一步:继承Thread类
第二步:复写run方法,定义线程要运行的代码
		为什么要复写run方法呢?
			Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行
			的代码。该存储功能就是run方法。
			也就是说,Thread类中的run方法是用于存储线程要运行的代码
第三步:调用线程的start方法
		该方法有两个作用,调用run方法,启动线程

发现每次运行的结果不一致,
因为多个线程都获取cpu执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时的效果。
我们可以形象的把多线程的运行行为在互相抢夺cpu的执行权

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


*/


class Demo extends Thread
{
	public void run()
	{
		for (int x=0;x<60 ;x++ )
			System.out.println("demo run");
	}
}


class  ThreadDemo
{
	public static void main(String[] args) 
	{
		Demo d = new Demo();
		d.start();
		for (int x=0;x<100 ;x++ )
			System.out.println("main run");
		
	}
}


 

线程的运行状态:

创建:new Thread

运行:start()

冻结:sleep()   wait()   notify()

消亡:stop()

 获取线程对象和名称:

/*
练习:
创建两个线程,和主线程交替运行

原来线程自己都有名称
Thread-编号,编号从0开始

currentThread():获取当前线程对象
getName():获取线程名称

设置线程名称:setName()或构造函数
*/
class  Test extends Thread
{
	//private String name;
	Test(String name)
	{
		//this.name = name;
		super(name);
	}
	public void run()
	{
		for (int x=0;x<60 ;x++ )
		{
			System.out.println(Thread.currentThread().getName()+"run..."+x);
		}
	}
	
}
class ThreadTest
{
	public static void main(String[] args) 
	{
		Test t1 = new Test("one");
		Test t2 = new Test("two");
		t1.start();
		t2.start();
	}
}

售票的例子:

class Ticket extends Thread
{
	private int tick = 100;
	public void run()
	{
		while (true)
		{
			if (tick>0)
			{
				System.out.println(Thread.currentThread().getName()+"....sale:"+tick);
				tick--;
			}
		}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t1 = new Ticket();
		Ticket t2 = new Ticket();
		Ticket t3 = new Ticket();
		Ticket t4 = new Ticket();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}


第二种创建方式:实现Runnable接口

定义线程的第二种方式:实现Runnable接口

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

class Ticket implements Runnable
{
	private int tick = 100;
	public void run()
	{
		while (true)
		{
			synchronized(obj)
			{
				if (tick>0)
				{
					System.out.println(Thread.currentThread().getName()+"....sale:"+tick);
					tick--;
				}
			}
		}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}

(为什么要设计Runnable接口):当要用到多线程的类已经继承了一个类的时候,就不能再继承Thread类,所以要实现Runnable接口

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

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

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

两种方式的区别:

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

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

3.线程的同步

同步代码块:

解决线程安全问题:

/*
通过分析打印结果,发现,出现了0,-1,-2等错票。
多线程的运行出现了安全问题。

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

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

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

synchronized(对象)
{
	需要被同步的代码
}
*/
class Ticket implements Runnable
{
	private int tick = 100;
	Object obj = new Object();
	public void run()
	{
		while (true)
		{
			synchronized(obj)
			{
				if (tick>0)
				{
					try{Thread.sleep(10);}catch{Exception e}
					System.out.println(Thread.currentThread().getName()+"....sale:"+tick);
					tick--;
				}
			}
		}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}

synchronized(对象)

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

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

同步的前提:

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

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

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

弊端:每次判断锁会消耗资源

同步函数:

/*
需求:
银行有一个金库。
有两个储户,分别存100元,存三次。

目的:该程序是否有安全问题,如果有,如何解决?

如何找到问题:
1,明确哪些代码是多线程运行代码
2,明确共享数据
3,明确多线程运行代码中哪些操作语句是操作共享数据的
*/

class Bank
{
	private int sum=0;
	public synchronized void add(int n)     //synchronized作为关键字放在函数上作为同步函数
	{
		sum = sum+n;
		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 cus = new Cus();
		new Thread(cus).start();
		new Thread(cus).start();
	}
}


 

class Ticket implements Runnable
{
	private int tick = 100;
	Object obj = new Object();
	public void run()
	{
		while (true)
		{
			this.show();
		}
		
	}
	public synchronized void show()
	{
		if (tick>0)
				{

					System.out.println(Thread.currentThread().getName()+"....sale:"+tick);
					tick--;
				}
	}
}
class  TicketDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}


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

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

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

静态同步函数使用的锁是Class对象

 

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


*/

class Ticket implements Runnable
{
	private static  int num = 100;
//	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
//		System.out.println("this:"+this.getClass());

		if(flag)
			while(true)
			{
				synchronized(Ticket.class)//(this.getClass())
				{
					if(num>0)
					{
						try{Thread.sleep(10);}catch (InterruptedException e){}						
						System.out.println(Thread.currentThread().getName()+".....obj...."+num--);
					}
				}
			}
		else
			while(true)
				this.show();
	}

	public static synchronized void show()
	{
		if(num>0)
		{
			try{Thread.sleep(10);}catch (InterruptedException e){}
			
			System.out.println(Thread.currentThread().getName()+".....function...."+num--);
		}
	}
}

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

//		Class clazz = t.getClass();
//		
//		Class clazz = Ticket.class;
//		System.out.println("t:"+t.getClass());

		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 ststic final Single s = new Single();
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
}
*/
//懒汉式

class Single
{
	private static Single s = null;
	private Single(){}

	public static Single getInstance()
	{
		if(s==null)
		{
			synchronized()
			{
				if(s==null)
					s=new Single();
			}
		}
		return s;
	}
}


死锁:

同步中嵌套同步,而锁却不同

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(Thread.currentThread().getName()+"..if   locka....");
					synchronized(MyLock.lockb)				{
						
						System.out.println(Thread.currentThread().getName()+"..if   lockb....");
					}
				}
		}
		else
		{
			while(true)			
				synchronized(MyLock.lockb)
				{
					System.out.println(Thread.currentThread().getName()+"..else  lockb....");
					synchronized(MyLock.locka)
					{
						System.out.println(Thread.currentThread().getName()+"..else   locka....");
					}
				}
		}

	}

}

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




class DeadLockTest 
{
	public static void main(String[] args) 
	{
		Test a = new Test(true);
		Test b = new Test(false);

		Thread t1 = new Thread(a);
		Thread t2 = new Thread(b);
		t1.start();
		t2.start();
	}
}

4.多线程间的通信与等待唤醒机制

/*
线程间通信:
其实就是多个线程在操作同一个资源,
但操作的动作不同。
*/
/*
	等待唤醒机制
	wait();notify();notifyAll();
	都使用在同步中,因为要持有监视器(锁)的线程操作。
	所以要使用在同步中,因为只有同步才具有锁。

	为什么这些操作线程的方法要定义在Object类中呢?
	因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有
	的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
	不可以对不同锁中的线程唤醒。

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

	而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
*/
class Res
{
	String name;
	String sex;
	boolean flag = false;
}
class Input implements Runnable
{
	
	int x = 0;
	private Res r;
	Input(Res r)       //建立对象引用
	{
		this.r = r;
	}
	public void run()
	{
		while (true)
		{
			synchronized(r)
			{
				if (r.flag)
				{
					try
					{
						r.wait();
					}
					catch (Exception e)
					{
						System.out.println("");
					}
				}
				if (x==0)
				{
					r.name="mike";
					r.sex="man";
					x=1;
				}
				else
				{
					r.name="丽丽";
					r.sex="妖妖妖妖妖妖";
					x=0;
				}
				r.flag = true;
				r.notify();
			}
		}
	}
}
class Output implements Runnable
{
	private Res r;
	Output(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		while (true)
		{
			
			synchronized(r)
			{
				if(!r.flag)
				{
				try
					{
						r.wait();

					}
					catch (Exception e)
					{
						System.out.println("");
					}
				}
				System.out.println(r.name+"..."+r.sex);
				r.flag=false;
				r.notify();
			}
		}
	}
}
class  TongXin
{
	public static void main(String[] args) 
	{
		Res r = new Res();
		Input in = new Input(r);
		Output ou = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(ou);
		t1.start();
		t2.start();
	}
}

线程间通信实例:生产者与消费者

/*
生产者,消费者。

多生产者,多消费者的问题。
if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行!

notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
notifyAll解决了本方线程一定会唤醒对方线程的问题。


*/

class Resource			//定义资源类,并在资源类中放置线程需要的存和取的方法,并应用同步
{
	private String name;
	private int count = 1;
	private boolean flag = false;		//设置标记,如果标记为true,表示只可以取,如果为false,只可以存
										//意思是flag为true,表示有商品,就不需要再生产商品,为false就表示没有商品
										//不可以消费商品。
										//需要生产一件商品,就消费一件商品,所以在存完需要改变flag标记为false
	public synchronized void set(String name)  
	{
		while(flag)						//判断标记,因为flag为false,所以这里循环体不会被执行,所以线程不会等待
										//直接存储数据,然后改变flag标记
			try{this.wait();}catch(InterruptedException e){}   
										//wait方法需要声明调用者,即设置监视器(锁)
			this.name = name + "..."+count++;
			System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
			flag = true;
			notifyAll();				//唤醒持有同一个监视器(锁)的线程
		
	}

	public synchronized void out()
	{
		while(!flag)					//消费商品的需要run方法调用的方法
										//线程开启后,传入资源对象后初始化的标记flag为false,这里直接冻结线程
										//并循环判断标记flag的值,当flag被存储线程设置为true的时候,这里循环结束
										//开始执行取的语句,取完后将商品清零,即把标记设置为false
			try{this.wait();}catch(InterruptedException e){}	
			System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
			flag = false;
			notifyAll();				//唤醒持有同一个监视器(锁)的线程
		
			
	}
}

class Producer implements Runnable			//创建生产商品线程的实现类
{
	private Resource r;
	Producer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.set("商品");
		}
	}
}

class Consumer implements Runnable				//创建消费商品线程的实现类
{
	private Resource r;
	Consumer(Resource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.out();
		}
	}
}



class  ProducerConsumerDemo
{
	public static void main(String[] args) 
	{
		Resource r = new Resource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();

	}
}


jdk1.5中将schronized同步替换为Lock对象,将Object中的wait,notify和notifyAll方法替换为condition对象,通过Lock对象的newCondition方法获取其对象,可以提高了线程的安全性
 

文档中给出的lock锁的实例(缓冲区的读取操作):

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

5.线程对象的其他方法

线程停止:
stop方法已经过时
那么如何停止线程呢?只有一种方法,使run方法结束,线程也随之结束

如果有循环存在,只要控制住循环,就可以停止住线程

特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。

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

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

Thread类提供该方法interrupt();
守护线程:

线程的setDaemon()方法,放在start方法之前调用

将线程标记为后台线程,当前台线程结束,后台线程随之结束,不用刻意结束。

类似圣斗士星矢守护雅典娜,圣斗士是后台线程,雅典娜是前台线程,雅典娜一挂,圣斗士就失业了

 

/*
join方法:
当A线程执行到了B线程的join方法的时候,A线程就会等待,等B线程执行完,A才会执行
join可以用来临时加入线程执行

setPriority方法:
优先级设置1-10
Thread.MAX_PRIORITY=10
Thread.MIN_PRIORITY=1
Thread.NORE_PRIORITY=5
*/
class Demo implements Runnable
{
	public void run()
	{
		for(int x=0; x<60; x++)
		{
			System.out.println(Thread.currentThread().toString()+"....."+x);
			Thread.yield();
		}
	}
}

class  JoinDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo d = new Demo();

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

		t1.start();
		t2.start();
		t2.setPriority(Thread.MAX_PRIORITY);	

		t1.join();		//t1线程要申请加入进来,运行。
						//临时加入一个线程运算时可以使用join方法。

		for(int x=0; x<70; x++)
		{
			System.out.println(Thread.currentThread()+"....."+x);
		}
	}
}



 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值