Java基础-多线程

 

一.多线程概述

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

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

 

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

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

一个进程中至少一个进程


Java Vm (虚拟机)启动的时候会有一个进程java.exe
改进程中至少一个线程负责java程序执行。
而且这个线程运行的代码存在于main方法中

该线程可以称之为主线程。


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

 

二.如何创建线程

(1)创建线程的第一种方式:继承thread类

步骤:

1.定义类继承thread

2.复写thread类中的run方法。

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

3.调用线程的start方法,启动线程,调用run方法

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

为什么要覆盖run方法呢?

Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
也就是说thread类中的run方法,用于存储线程要运行的代码
主线程的代码放在main中,所以虚拟机只认main中的代码

 

public class TreadDemo
{
	public static void main(String[] args)
	{
		
		
		Demo d = new Demo();
		d.start();//开启线程并执行该线程的run方法,如果写d.run()仅仅是对象调用方法,
					//而线程创建了,并没有运行,如果这里写d.run,会先执行demorun然后执行"main在运行!!!
					//,因为创建了线程,但是没有运行,相当于只有一个线程在执行,
					//主线程执行到run就执行完再出来,之前是d这个线程来执行demorun
		for(int x = 0;x<120;x++)
		{
			System.out.println("main在运行");
		}
		
	}

}

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

}

//效果
//main在运行
//main在运行
//demo在运行
//demo在运行
//demo在运行
//demo在运行

 

总结:

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

 

(2)第二种创建线程的方式:实现runable

步骤:

1.定义类实现runnable接口

2.覆盖runnable接口中的run方法。

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

3.通过thread类建立线程对象。

4.将runnable接口的子类对象作为实际参数传递给thread类的构造函数

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

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

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

5.调用thread类的start方法开启线程并调用runnable接口子类的run方法

(3)用两种创建方式实现一个需求:需求:简单的买票程序多个窗口同时卖票。

(1)继承Thread的创建方法

//第一种创建线程的方式
//1.继承Thread
//2.复写public void run
//3.new 一个类    然后这个  类.start()
public class TicketSellDemo {

	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();
		

	}

}

class Ticket extends Thread
{
	//public int TicketNum = 100 ;//每个线程都卖100张票
	public  static int TicketNum = 100;//四个线程共卖100张 ,但是static 存储空间大,不建议用!!!!!!!!!!!!!!!!
	public void run()
	{
		while(true)
		{
		
			if(TicketNum>0)
			{
				System.out.println(Thread.currentThread().getName()+"......"+TicketNum--);
			}
			else
			{
				
				break;
			}
			//这就是效果,会出现共同卖一张票的结果,因为每个线程以创建 TicketNum都是100,所以会出现这样的问题
			//Thread-1......100
			//Thread-3......100
			//Thread-0......100
			//Thread-2......100
			//Thread-0......99
			//Thread-3......99
			//Thread-1......99
			//Thread-3......98
			//Thread-0......98
		}
		
		
	}



}

 

(2)第二种实现Runable方式

/第二种创建线程方式
//1.定义类实现runnable接口
//2.覆盖runnable接口中的run方法。
//将线程要运行的代码存放在该run方法中。
//3.通过thread类建立线程对象。
//4.将runnable接口的子类对象作为实际参数传递给thread类的构造函数

//为什么要将runnable接口的子类对象传递给thread的构造函数。
//因为,自定义的run方法所属的对象是runnable接口的子类对象。
//所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象
//5.调用thread类的start方法开启线程并调用runnable接口子类的run方法

class TicketSellDemo
{
	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();
		
		System.out.println("我是最后一个");
		
	}

}

class Ticket implements Runnable
{
	private int TicketNum = 100;
	public void run()
	{
		
		while(true)
		{
				if(TicketNum >0)
				{
				try
				{
					Thread.sleep(500);
				}
				catch (Exception e)
				{
					throw new RuntimeException("错误");
				}
				
					System.out.println(Thread.currentThread().getName()+"....."+TicketNum--);
				}
				else
				{
					break;
					
				}
		}
		
	}

}

//效果
//Thread-1.....3
//Thread-0.....2
//Thread-1.....1
//Thread-3.....-1
//Thread-2.....0
//Thread-0.....-2

总结:

//通过分析,发现,打印出0,-1,-2等错票。
//多线程的运行出现了安全问题
//问题的原因:
//当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
//另一个线程参与进来执行,导致共享数据的错误。
//解决方法:
//对多条操作共享数据的语句,只能让一个线程都执行完在执行过程中,其他线程不可以参与执行

下一章解决多线程安全问题

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

 

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

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

 

两种实现方式区别:

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

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

 

三.多线程安全问题

Java对于多线程的俄安全问题提供了专业的解决方式,就是同步代码块。
Synchronized(对象)  !!!!
{

/需要被同步的代码
}
对象如同锁(有的翻译成监视器),持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu执行权,也进不去,因为没有获取锁
进入同步代码块就就持有锁,出来就释放锁!!!!
sleep 释放执行权,但是不释放锁!!!!


火车上的卫生间--经典。(进去就锁,出来开门)

同步的前提:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

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

2.必须是多个线程使用同一个锁。就是那个Object obj = new Object();


必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。

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

//现在来解决他
class TicketSellDemo
{
	public static void main(String[] args)
	{
		Ticket t = new Ticket();
		
		Thread t1 = new Thread(t);
		/*
		这句话确实创建了一个线程
		t1.start();
		也确实运行了一个线程
		但是如果这样运行的话什么都没有,因为t1.start();跑的是thread的run方法,他的run方法中没有卖票,而Ticket t = new ticket();这个创建的对象有卖票的方法。
		那么怎么把这两者联系起来呢?
		Thread (runnable target)//分配新的thread对象
		于是写为Thread t1 = new thread(t);
		这样就可正常了
		而且用了接口方法,实现了多线程一起卖100张票。(static存储空间大)
		*/
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		
		System.out.println("我是最后一个");
		
	}

}

class Ticket implements Runnable
{
	private int TicketNum = 100;
	//1.必须要有两个或者两个以上的线程。
	//2.必须是多个线程使用同一个锁。就是那个Object obj = new Object();
	Object obj = new Object();
	public void run()
	{
		
		while(true)
		{
			 synchronized(obj)
			 {
					if(TicketNum>0)
					{
							try
							{
								
								Thread.sleep(30);
							}
							catch (Exception e)
							{
								
							}
						System.out.println(Thread.currentThread().getName()+"......"+TicketNum--);
					}
			 }
		}
		
	}

}


 

四.多线程-同步函数

/*
需求:
银行有一个金库。
有两个储户分别存300元,每次存100,存3次
目的:该程序是否有安全问题,如果有,如何解决?

如何找问题:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

1.明确哪些代码是多线程运行代码。

2.明确共享数据。

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


*/

public class BankDemo {

	public static void main(String[] args) {

		Cust cu = new Cust();
		
		Thread t1 = new Thread(cu);
		Thread t2 = new Thread(cu);
		t1.start();
		t2.start();
	
		

	}

}

class Bank
{
	private int sum;
	Object obj = new Object();
	public void add(int money)
	{
		synchronized(obj)
		{
			
			sum = sum +money;
			///线程可能在这里挂起,然后出现问题
	
		try
		{
			Thread.sleep(100); //必须要加这个,否则不会出问题
								//打印3
								//  Thread-0----200
								//	Thread-1----200
								//	Thread-1----400
								//	Thread-0----500
								//	Thread-0----600
								//	Thread-1----600
			
		}
		catch(Exception e)
		{
			
			
		}
			System.out.println(Thread.currentThread().getName()+"----"+sum);
			//正常打印
			//Thread-0----100
			//Thread-0----200
			//Thread-0----300
			//Thread-1----400
			//Thread-1----500
			//Thread-1----600
		}
		//这个也是需要同步的!!!否则会出现上面的打印3
		//System.out.println(Thread.currentThread().getName()+"----"+sum);
		/*
		或者直接写成同步函数
		同步分为:同步代码块,同步函数

		Private int sum;
		//Object obj = new object();
		Public Synchronized void add(int n)
		{
		Sum = sum+n;
		    Try{thread.sleep(10);}catch(exception e){} ///线程可能在这里挂起,然后出现问题
		System.out.println(“sum=”+sum);
		}

		*/
	}


}

class Cust implements Runnable
{
	private  Bank bk = new Bank();
	public void run()
	{
		//正常效果
		//Thread-1----200
		//Thread-0----100
		//Thread-1----300
		//Thread-1----500
		//Thread-0----400
		//Thread-0----600
		//Bank bk = new Bank();//如果放这里效果会这样!!!
		//Thread-1----100
		//Thread-0----100
		//Thread-1----200
		//Thread-0----200
		//Thread-1----300
		//Thread-0----300
		for(int i = 0; i<3;i++)
		{
			
			bk.add(100);
		}
		
		
	}

}

 

五.同步函数,同步代码块,静态同步函数的锁的问题

 需求:使用两个线程来买票。

一个线程在同步代码块中。
一个线程在同步函数中。
都在执行卖票动作。

同步函数

1.同步函数用的是哪一个锁呢?因为没有synchronized(对象),用的是this,但是为什么呢?

因为每个函数都有一个所述对象引用,所以就是this
所以同步函数使用的锁是this.
通过该程序进行验证。

2.静态同步函数

如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不再是this.因为静态方法中也不可以定义this.
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。(ticket.class)
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

3.同步代码块

锁既可以用 this , object ,或者 类名.class都可以

 

使用正不正确有两个前提

1.是不是两个及以上线程

2.是不是使用同一个锁

public class ThisLockDemo {

	public static void main(String[] args) {
		
		Ticketl t =new Ticketl();
		
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.start();
		//为了让主线程在这停,这时能运行的只有t1,如果没有这段话,t1可能启动,但是没有获得执行权,
		//主线程一热按在执行,可能把下面两句话执行完了(t1.start();t.flag = false;),可能就执行一个
		//主线程执行完毕,t1获得执行权,再执行恐怕t1t2都在同步函数中执行,flag没起到应有作用
		try {
			Thread.sleep(2);
		} catch (Exception e) {
			throw new RuntimeException("cuowu");
			
		}
		System.out.println("dier");
		t.flag = false;
		
		
		t2.start();
		

	}

}

class Ticketl implements Runnable
{
	private int num = 100;
	boolean flag  = true ;
	Object obj = new Object();
	public void run()
	{
		if(flag)
		{
			while(true)
			{
				//同步代码块
				//假设这里是A线程
				//为了配合静态同步函数,锁也可以写 类名.class
				synchronized(this)//如果是obj那么打印会有0票,因为跟同步函数不是一个锁,用this才是同一个锁
								//打印Thread-0--2---0
				{
					if(num>0)
					{
						try//这里一定要加sleep//要不然看不出效果
						{
							Thread.sleep(100);	
						}
						catch (Exception e)
						{
						}
						System.out.println(Thread.currentThread().getName()+"--2---"+num--);
					}
					else
					{
						break;
					}
					
				}
			}
		}
		else
		{
			
			
			while(true)
			{
				if(num>0)
				{
					show();
				}
				else
				{
					break;
				}
			}
		}
		
		
	}
	//假设这里是B线程
<span style="white-space:pre">	</span>//public static <span style="font-family: Arial, Helvetica, sans-serif;">synchronized void show() 可以用来判断静态用的是什么锁</span>

	public synchronized void show() //同步函数用的是this,因为函数是被对象调用的
	{
		try
		{
			Thread.sleep(10);	//如果不同线程所不同,那么num =1 进入B线程,拿到B的锁,在这sleep,释放执行权
								//但是不释放锁,
								//然后A获得执行权,因为锁不一样,所以可以继续跑他的代码,打印num = 1
								//然后B获得执行权,又打印num = 0
			
								//如果AB锁相同,那么B在这里sleep的时候,没有释放锁,A拿不到锁,也执行不了代码
								//!!!!!
		}
		catch (Exception e)
		{
		}
			System.out.println(Thread.currentThread()+"--1---"+num--);
		
		
	}

}

 

六.多线程-单例设计模式

 

/*
单例设计模式
突然想到一个问题?为什么要有单例设计模式,已解决
//这个要自己会写
*/

1.饿汉式

class Single
{
	private static final Single s = new Single();//写final更为严谨
	private Single(){}
	public static Single getInstance()
	{
		return s;
	}
}

 

2.懒汉式  //实例延迟加载,如果多线程会出现安全问题

 

 

class Single
{
	private static Single s = null;
	private Single(){}
	/*
	//public static synchronized Single getInstance()//每次判断锁比较低效
	{
	
			if(s == null )
				{
					//可能在这里挂起,如果A在这挂起,B也挂这,A醒了new一个,B醒了又new了一个
					//单例模式就是只要一个对象,所以这里不行
					s = new Single();
				}
	
	}
	*/
	public static Single getInstance()
	{
		if(s == null)
		{
			synchronized(Single.class)
			{
				if(s == null )
				{
					//可能在这里挂起,如果A在这挂起,B也挂这,A醒了new一个,B醒了又new了一个
					//单例模式就是只要一个对象,所以这里不行
					s = new Single();
					//这里只要一个初始化完成后面的就不需要判断锁
				}
			}
		}
		return s;
	
	}
}

class SingleDemo
{
	public static void main(String[] args)
	{
		System.out.println("Hello World");
	}
}

 

 

七.多线程-死锁

 

/*
死锁程序  这个要自己会写
A 有一个筷子
B 有一个筷子
A想要B的筷子吃东西
B想要A的筷子吃东西


也有和谐的时候就是A拿筷子吃一口,B拿筷子吃一口
//和谐情况(很少)
//if  locka
//if  lockb
//out1
//else  lockb
//else  locka
/out2

//不和谐情况
/if  locka
//else  lockb

死锁定义:死锁是指在一组进程中的各个进程均占有不会释放的资源,

但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态

 

//对象如同锁(有的翻译成监视器),持有锁的线程可以在同步中执行。
//没有持有锁的线程即使获取cpu执行权,也进不去,因为没有获取锁
//进入同步代码块就就持有锁,出来就释放锁!!!!
//sleep 释放执行权,但是不释放锁!!!!
//1.必须要有两个或者两个以上的线程。
//2.必须是多个线程使用同一个锁。就是那个Object obj = new Object();
3.必须保证同步中只能有一个线程在运行。!!!!!!!!
//好处:解决了多线程的安全问题。
//弊端:多个线程需要判断锁。较为消耗资源。

 

为什么每个线程要同一个锁?

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。
获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
 
当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
一个对象只有一个锁(!!!!!)。所以,如果一个线程获得该锁(!!!!),就没有其他线程可以获得锁,
直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,
直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。
 

public class DeadLockDemo {

	public static void main(String[] args) {
		
	
		Thread t1 = new Thread(new Test1(true));
		Thread t2 = new Thread(new Test1(false));
		t1.start();
		t2.start();
		
		
	

	}

}
class Test1 implements Runnable
{
	private boolean flag;
	Test1(boolean flag) 
	{
		this.flag = flag;
	}
	public void run()
	{
		if(flag)
		{
			
				synchronized(MyLock.locka)
				{
					//如果判断不是loca就会等在这里//进来就拿了locka,在这等lockb
					System.out.println("if  locka");
					synchronized(MyLock.lockb)
					{
						System.out.println("if  lockb");
					}
					System.out.println("out1");
				}
			
				
				
			
			
			
		}
		else
		{
			
			
				synchronized(MyLock.lockb)
				{
					//如果判断不是locb就会等在这里//进来就拿了lockb,在这等locka
					System.out.println("else  lockb");
					synchronized(MyLock.locka) 
					{
						System.out.println("else  locka");
					}
					System.out.println("out2");
				}
			
				
				
				
			}
			
		}
		
		
	}


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


}

 

八.线程间的通信

线程间通讯简述:

其实就是多个线程在操作同一个资源,但是操作的动作不同。锁又称监视器

Wait:  释放执行权,释放锁

sleep: 释放执行权,不释放锁
Notify();
Notifyall();//唤醒线程池中所有
都使用在同步中,因为要对持有监视器(锁)的线程操作
所以要使用在同步中,因为只有同步才具有锁

为什么这些操作线程的方法要定义在object类中呢?

因为这些方法在操作同步线程中,都必须要标示他们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁!!!
而锁可以是任意对象,所以可以被任意对象调用在方法定义object类中。


线程建立的时候都是存在线程池中,等待的线程被放到线程池里去了,唤醒的时候一般都是唤醒
第一个等待的线程
 

注意:

//142//不建议用静态修饰变量,因为生命周期长,不利于清除,如果变成对象还容易清楚 Res r =new Res(),因为需要类一加载就必须有res,?????

 

1.线程间通信安全问题

 

</pre><pre name="code" class="java">class Res
{
	String name;
	String sex;
}
class Input implements Runnable
{
	private Res r;
	//Object obj = new Object(); //代码1 用了1处的代码同步依然会出现问题,因为不满足两个前提的第二个,不是同一个锁
								//因为这里的Object obj = new object();跟输出的Object obj = new object();,
								//指的不是同一个,因此两个的锁不一样。
	Input(Res r)
	{
		
		this.r =r;
		
	}
	public void run()
	{
		
		
		int x = 0;
		while(true)
		{
			//synchronized(obj) //代码1
			synchronized(r) //代码2
			{
				if(x==0)
				{
					r.name = "mike";
					//易出现问题
					//如果在这里刚给完名字,就被输出线程抢走执行权,那么就打印mike  女女女
					r.sex = "man";
					
				}
				else
				{
					r.name = "丽丽";
					//易出现问题
					//如果在这里刚给完名字,就被输出线程抢走执行权,那么就打印丽丽   nan
					r.sex = "女女女女";
				}
				x =(x+1)%2;
			}
			
		}
	}



}

class Output implements Runnable
{
	private Res r ;
	//Object obj = new Object(); //代码1
	Output(Res r)
	{
		this.r =r;
	}
	public void run ()
	{
		while(true)
		{
			//synchronized(obj)//代码1
			synchronized(r) //代码2//或者可以写input.class,output.class,只要唯一的都可以写,但是比较牵强
			{
				System.out.println(r.name+"...."+r.sex);
			}
		}
		
		
	}
}
class InputOutDemo
{
	public static void main(String[] args)
	{
		Res r = new Res();
		
		Input in = new Input(r);
		Output out = new Output(r);
		
		Thread t1  = new Thread(in);
		Thread t2 = new Thread(out);
		
		t1.start();
		t2.start();
		
		
		
	}

}
*/
//打印效果

//mike....man
//mike....女女女女
//丽丽....女女女女
//丽丽....man
//丽丽....man

//所以添加代码1,但是两个obj不是一样的 ,锁不一样,可以都写input.class,output.class,但是比较牵强,所以用r
//添加代码2
//但是有一个问题,如果输入线程写完名字,被输出抢掉执行权,那么有可能会被不断打印,不能实现输一打一,所以更新代码如下

 

二   等待唤醒机制

class Res
{
	 String name;
	 String sex;
	 boolean flag = false;
	
}
class Input implements Runnable
{
	private Res r;
	Input(Res r)
	{
		
		this.r =r;
		
	}
	public void run()
	{
		
		
		int x = 0;
		while(true)
		{
		
			synchronized(r) 
			{
				if(r.flag)
				{
					
					try
					{
						r.wait(); //等待线程都存线程池中,r.wait()是指让r这个锁的线程等待,其他线程无关
					} 
					catch (Exception e)
					{
						
						
					}
					
				}
				if(x==0)
				{
					r.name = "mike";
					r.sex = "man";
					
				}
				else
				{
					r.name = "丽丽";
					r.sex = "女女女女";
				}
				x =(x+1)%2;
				r.flag = true; //写完一次数据,准备让输出打印一次
				r.notify();    //叫醒线程,唤醒的是线程池中的线程,唤醒第一个等待的,只能唤醒一个!!!
							   //r.notify()是指r这个锁的线程唤醒 ,
			}				   //类似于两伙小朋友(相当于两个锁的线程),这一伙的小朋友定住了,只能这一伙的小朋友解定
			
		}
	}



}

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();
						//wait不能抛,因为外面没抛出异常!!!
						//因为在使用wait()的时候会抛出异常所以必须使用try,同时也要用r.wait
						//	因为wait在其他线程调用此对象notify()或notifyall()方法前,导致当前线程等待。
						//	换句话说,此方法的行为就好像他仅执行wait(0)调用一样。
						//	当前线程必须拥有此对象的监视器。该线程发布对此监视器的所有权并等待,
						//	直到其他线程通过调用notify方法,或notifyall方法通知再次对象的监视器上等待的线程醒来。
						//	然后该线程将等到重新获得对监视器的所有权后才能继续执行。
						//	
					}
					catch (Exception e)
					{
						
					}
				}
				System.out.println(r.name+"...."+r.sex);
				r.flag = false; //数据打印完一次,等待输入
				r.notify();//唤醒线程(只能唤醒一个)
				
			}
		}
		
		
	}
}
class InputOutDemo
{
	public static void main(String[] args)
	{
		Res r = new Res();
		
		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 Res
{
	private String name;
	private String sex;
	private boolean flag = false;
	
	public synchronized void set(String name, String sex)
	{
		if(this.flag)//可以不写flag //因为都是r调用,所以输入输出使用的锁都一样
		{
			
			try
			{
				this.wait(); //等待线程都存线程池中,r.wait()是指让r这个锁的线程等待,其他线程无关
			} 
			catch (Exception e)
			{
				
				
			}
			
		}
		this.name = name;
		this.sex = sex;
		flag  =true;
		this.notify();
	}
	public synchronized void out()
	{
		if(this.flag)//可以不写flag//可以不写flag //因为都是r调用,所以输入输出使用的锁都一样
		{
			
			try
			{
				this.wait(); //等待线程都存线程池中,r.wait()是指让r这个锁的线程等待,其他线程无关
			} 
			catch (Exception e)
			{
				
				
			}
			
		}
		System.out.println(name+"....."+sex);
		flag  =false;
		this.notify();
		
	}
}
class Input implements Runnable
{
	private Res r;
	Input(Res r)
	{
		
		this.r =r;
		
	}
	public void run()
	{
		
		
		int x = 0;
		while(true)
		{

				if(x==0)
				{
					r.set("mike", "man");
					
				}
				else
				{
					r.set("丽丽", "女女女女");
				}
				x =(x+1)%2;
			
			
		}
	}



}

class Output implements Runnable
{
	private Res r ;
	Output(Res r)
	{
		this.r =r;
	}
	public void run ()
	{
		while(true)
		{
			
			r.out();
		}
		
		
	}
}
class InputOutDemo
{
	public static void main(String[] args)
	{
		Res r = new Res();
		
		//Input in = new Input(r);
		//Output out = new Output(r);
		
		//Thread t1  = new Thread(in);
		//Thread t2 = new Thread(out);
		
		//t1.start();
		//t2.start();
		new Thread(new Input(r)).start();
		new Thread(new Output(r)).start();
		
		
		
	}

}

 

四.综合版

class Res
{
	String name;
	String sex;
	Boolean flag =false;
//	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
{
	private Res r;
	//Object obj = new object();// 1  用了1处的代码同步依然会出现问题,因为不满足两个前提的第二个,不是同一个锁
	//因为这里的Object obj = new object();跟输出的Object obj = new object();,
	//指的不是同一个,因此两个的锁不一样。

	Input(Res r)
	{
		this.r =r;
	}


	public void run()
	{
		int x = 0;
		while(true)
		{
		
		   
		//{
			//if(r.flag)//有这个颜色用的是等待唤醒机制。
			//try{r.Wait();}
			//catch(exception e){}

	//	因为在使用wait()的时候会抛出异常所以必须使用try,同时也要用r.wait
	//	因为wait在其他线程调用此对象notify()或notifyall()方法前,导致当前线程等待。
	//	换句话说,此方法的行为就好像他仅执行wait(0)调用一样。
	//	当前线程必须拥有此对象的监视器。该线程发布对此监视器的所有权并等待,
	//	知道其他线程通过调用notify方法,或notifyall方法通知再次对象的监视器上等待的线程醒来。
	//	然后该线程将等到重新获得对监视器的所有权后才能继续执行。
	//	





		//synchronized(obj)  //1  
		//用代码 2 就OK了,因为R都是同样的对象,
		// 所以所也一样,或者可以写input.class,output.class,只要唯一的都可以写,
		// 但是现在打印会出现输入很多次然后打印很多次,最好实现输一次,
		// 打印一次,3  代码,解决这个问题
		  synchronized(r) //2
	{
		if(r.flag)//3  有这个颜色用的是等待唤醒机制。
			try{r.wait();}  //3  为什么不能抛????????
			catch(Exception e){} //3
		if(x==0)
		{
			r.name = "mike";
			//易出现问题
			r.sex = "man";
		}
		else
		{
			r.name="丽丽";
			//易出现问题

			r.sex="女女女";

		}

		x=(x+1)%2;
		r.flag =true;  //3
		r.notify();//3   因为在res中写了公共接口函数
	}
//r.flag =true;
//r.Notify();//因为在res中写了公共接口函数
//所以可以用下面的代替
//if(x==0)
//R.set("mike","man");
//Else
//R.set("丽丽","女女女女");
//X=(x+1)%2;
	}
  }
}



class Output implements Runnable
{
	private Res r;
	//Object obj = new object();  //1
	Output(Res r)
	{
		this.r=r;//this.r是private res r;
	}
	public void run()
	{
			while(true)
		{
		//Synchronized(obj)
		//Synchronized(r)
		//{
		//if(!r.flag)//如果线程notify之后,没到这个wait(),执行权就被抢走了,那么也没事,过程还是一样的(可以看看代码)
		//try{r.Wait();}catch(exception e){}  //放弃执行权
		//System.out.println(r.name+"...."+r.sex);
		//r.flag =flase;
		//r.Notify();//通知别的线程活了
		//}res写了公共函数,所以可以用下面的代替
		//synchronized(obj) //1
		synchronized(r) //2
		{
			//如果线程notify之后,没到这个wait(),
			//执行权就被抢走了,那么也没事,过程还是一样的(可以看看代码)
			if(!r.flag) //3
			try{r.wait();}catch(Exception e){} //3  //放弃执行权
			System.out.println(r.name+"...."+r.sex);
			r.flag =false; //3
			r.notify();//3   //两拨小朋友  一拨的只能唤醒自己的一拨
		}
			//r.out();
		}
	}


}

class InputOutputDemo
{
	public static void main(String[] args)
	{
		Res r = new Res();
		//new Thread(new input(r)).start();
		//new Thread(new output(r)).start();


	
		Input in =new Input(r);
		Output out =new Output(r);
		Thread t1 = new Thread (in);
		Thread t2 = new Thread (out);
		t1.start();
		t2.start();
	
	}


}

 

十.多线程-线程通信-生产者消费者问题

需求:

对于多个生产者和消费者。
为什么要定义while判断标记
原因:让唤醒的线程再一次判断标记


//需求:生产一个,卖一个
为什么定义notifyall
因为需要唤醒对方线程
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

 

一.普通写法

public class ProducerCustomer {

	public static void main(String[] args) 
	{
		Resource r = new Resource();
		

		//new Thread(new Pro(r)).start();
		//new Thread(new Custo(r)).start(); //两个线程看不出为什么下面非要用while
		Thread t1 = new Thread(new Pro(r));
		Thread t2 = new Thread(new Pro(r));
		Thread t3 = new Thread(new Custo(r));
		Thread t4 = new Thread(new Custo(r));
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		//打印1
		//有时候会消费两次,生产一次,有时候也会消费次生产两次
		//消费者:商品--count--114292
		//消费者:商品--count--114292
		//生产者:商品--count--114293
		//消费者:商品--count--114293
		//消费者:商品--count--114293
		//生产者:商品--count--114294
		//消费者:商品--count--114294
		//消费者:商品--count--114294
	}

}

class Resource 
{
	private Resource r;
	private String name;
	private int count =1;
	private boolean flag = false;
	//分析下为什么出现打印1问题
	
//	生产者  t1 t2 
//	消费者   t3  t4												线程池
//	t1开始进入生产一个,然后notify,因为t1wait(释放锁,释放执行权)  						1     2    3
//	假设,这时候被t2抢到执行权,因为flag = true,所以t2wait(释放锁,释放执行权)    			<span style="white-space:pre">	</span>t1    t2
//	只是t3抢到执行权 ,生产一个,然后notify, 线程池t1被唤醒(因为他是第一个放进去的)          	<span style="white-space:pre">	</span>t2
//  t3不一定释放执行权,加入他没释放,继续执行然后wait了(释放锁,释放执行权)				<span style="white-space:pre">	</span>t2    t3
//假设现在执行权被t4抢到,他也会wait										t2    t3   t4
//这时候只能t1活动,于是生产一次,notify一次,于是t2被唤醒,但是不一定获得执行权				t3    t4
//假设t1继续执行,wait,这时候只能t2出来 									t3    t4   t1
//t2之前是在wait的地方,如果开始执行就继续往下走,又生产一次(因为用的是if,之判断一次),所以问题出现了,生产了两次,消费一次

//那么我们用while(),醒来之后可以继续判断,t2被唤醒之后,while()条件不符合,
//只能继续再循环里面wait											t3    t4   t1	 t2			   <span style="white-space:pre">														</span> 
//完了这回全都wait了,所以用while就必须要用notifyall(),全部唤醒
	
	
	public synchronized  void set(String name)
	{
		if(flag)//需要写成while(!flag)为了让每次在wait等待的线程被唤醒之后判断一下while(),但是配合notifyallc才行
				//如果用if的话,会直接往下走,不判断if();
		{	
			try 
			{
				this.wait();
			} 
			catch (Exception e)
			{
				
			}
			
		}
		this.name = name+"--count--"+count++;
		System.out.println("生产者:"+this.name);
		flag = true;//生产了一个
		this.notify(); //唤醒用this锁的线程
	}
	public synchronized void out()
	{
		if(!flag)//需要写成while(!flag)为了让每次在wait等待的线程被唤醒之后判断一下while(),但是配合notifyallc才行
					//如果用if的话,会直接往下走,不判断if();
		{	
			try 
			{
				this.wait();
			} 
			catch (Exception e)
			{
				
			}
			
		}
		
		System.out.println("消费者:"+this.name);
		flag = false;//生产了一个
		this.notify(); //唤醒用this锁的线程
		
	}
	
	
}

class Pro implements Runnable
{
	private Resource r;
	Pro(Resource r)
	{
		this.r =r;
	}
	
	public void run()
	{
		while(true)
		{
			r.set("商品");
		}
		
	}

}

class Custo implements Runnable
{
	private Resource r;
	Custo(Resource r)
	{
		this.r =r;
	}
	
	public void run()
	{
		while(true)
		{
			r.out();
		}
		
	}

}

 

二.优化代码,dk1.5中提供了多线程升级解决方案。

//将同步synchronized替换成现实lock操作。
//将object中的wait,notify,notifyall,替换了condition对象
//该对象可以lock锁,进行获取。
//在该实例中,实现本方只唤醒对方操作。

public class ProducerCustomer {

	public static void main(String[] args) 
	{
		Resource r = new Resource();
		

	
		Thread t1 = new Thread(new Pro(r));
		Thread t2 = new Thread(new Pro(r));
		Thread t3 = new Thread(new Custo(r));
		Thread t4 = new Thread(new Custo(r));
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	
	}

}

class Resource 
{
	private Resource r;
	private String name;
	private int count =1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	//Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
	//Condition为每个对象提供多个等待,多个唤醒!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	private Condition condition_pro =  lock.newCondition();  //生产者的wait  唤醒
	private Condition condition_con =  lock.newCondition();  //消费者的wait  唤醒


	
	public void set(String name)//throws Exception
	{
		lock.lock(); //进来就锁
		
		
		
			
			try 
			{	
				while(flag)
				{
					condition_pro.await();
				}
				this.name = name+"--count--"+count++;
				System.out.println(Thread.currentThread().getName()+"生产者:"+this.name);
				flag = true;//生产了一个
				condition_con.signal(); //唤醒用this锁的线程
			}
			catch(Exception e)
			{
				
				
			}
			
		
			finally
			{
				lock.unlock();//释放锁的动作一定要执行
				
			}
			
		
	
	
	}
	public void out() //throws Exception
	{
		lock.lock();
		{	
			try 
			{	
				while(!flag)
				condition_con.await();
				System.out.println(Thread.currentThread().getName()+"消费者:"+this.name);
				flag = false;//生产了一个
				condition_pro.signal(); //唤醒用this锁的线程
			}
			catch (Exception e) 
			
			{
				// TODO: handle exception
			}
			finally
			{
				
				lock.unlock();//释放锁的动作一定要执行
			}
			
		}
		
	
		
	}
	
	
}

class Pro implements Runnable
{
	private Resource r;
	Pro(Resource r)
	{
		this.r =r;
	}
	
	public void run()
	{
		while(true)
		{	
			try 
			{
				r.set("商品");
			} 
			catch (Exception e) 
			{
			
			}
			
			
		}
		
	}

}

class Custo implements Runnable
{
	private Resource r;
	Custo(Resource r)
	{
		this.r =r;
	}
	
	public void run()
	{
		while(true)
		{
			try 
			{
				r.out();
			} 
			catch (Exception e) 
			{
			
			}
		}
		
	}

}

 

 

 

 

十一.多线程-停止线程


Stop方法已经过时。

如何停止线程?

只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。

只有控制住循环,就可以让run方法结束,也就是线程结束
特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束


当没有指定的方式让冻结的线程恢复到运行状态时,这是需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();(sleep wait join都可以唤醒)
 

1.代码1  读取标记让循环结束

 

class Stop 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)
	{
		Stop st =new Stop();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		t1.start();
		t2.start();
		int num=0;


		while(true)
		{
			if(num++== 60)
			{
				st.changeflag();
				break;
			}


			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
	}


}

 

2.代码二读不到标记,当线程处于冻结状态,就必须强制让线程醒来

/*当线程处于冻结状态,
就不会读取到标记,那么线程就不会结束


当没有指定的方式让冻结的线程恢复到运行状态时,这是需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();(sleep wait join都可以唤醒)
 

class Stop implements Runnable
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(flag)
		{
			try
			{	
				
				//线程0进来在这wait了,线程1进来也在这wait了(wait释放锁释放执行权,所以线程1就进来了)
				wait();   	//因为得先醒来执行完下面的东西,在执行while()才能结束
			}
			catch(InterruptedException e)//发生中断跳到这
			{
				System.out.println(Thread.currentThread().getName()+"...run");
				flag = false;
			}
		System.out.println(Thread.currentThread().getName()+"...run");
		}

	}
	public void changeflag()
	{

		flag =false;
	}

}
class StopThread
{
	public static void main(String[] args)
	{
		Stop st =new Stop();
		Thread t1 =new Thread(st);
		Thread t2 =new Thread(st);
		t1.start();
		t2.start();
		int num=0;

		while(true)
		{
			if(num++ == 60)
			{
				//St.changeflag();
				t1.interrupt();//唤醒冻结状态(线程挂起) //是线程产生中断!!!!!!!!!!!!!!!!!
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
			System.out.println("over");
		}

}

 

十二.多线程-守护线程

ublic final void set daemon(boolean on)
将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出
该方法必须在启动线程前调用


守护线程可以理解为后台线程,前台运行完了,后台也跟着停止

注意:!!!该方法必须在启动线程前调用

class Stop1 implements Runnable
{
	private boolean flag = true;
	public synchronized void run()
	{
		while(true)
		{
			
			System.out.println(Thread.currentThread().getName()+"...run");
		}

	}
	public void changeflag()
	{

		flag =false;
	}

}
class ProtectThread
{
	public static void main(String[] args)
	{
		Stop1 st =new Stop1();
		Thread t1 =new Thread(st);
		Thread t2 =new Thread(st);
		t1.setDaemon(true);
		t2.setDaemon(true);
		t1.start();
		t2.start();
		int num=0;

		while(true)
		{
			if(num++ == 60)
			{
				//St.changeflag();
				t1.interrupt();//唤醒冻结状态(线程挂起)
				t2.interrupt();
				break;
			}
			System.out.println(Thread.currentThread().getName()+"...."+num);
		}
			System.out.println("over");
		}

}

 

十三.多线程-Join方法

 

class demo implements Runnable
{
public void run()
{
For(int x=0;x<70;x++)
{
System.out.println(thread.currentthread().getname()+”.....”+x);
}

}


}

class joindemo
{
<span style="white-space:pre">	</span>Public static void main(String[] args) throws Exception
<span style="white-space:pre">	</span>{
<span style="white-space:pre">		</span>Demo d = new demo();
<span style="white-space:pre">		</span>thread t1 = new thread(d);
<span style="white-space:pre">		</span>thread t2 = new thread(d);
<span style="white-space:pre">		</span>t1.start();
<span style="white-space:pre">		</span>t1.join();//要CPU执行权,t1结束了,主线程才会活过来,碰到谁的join,就等谁挂了才活过来
<span style="white-space:pre">		</span>t2.start();
<span style="white-space:pre">		</span>for(int x=0;x<80;x++)
<span style="white-space:pre">		</span>{
<span style="white-space:pre">			</span>System.out.println(“main....”+x);

<span style="white-space:pre">		</span>}
<span style="white-space:pre">		</span>System.out.println(“over”);



<span style="white-space:pre">	</span>}


}

 

/*
用两种创建线程方法代码
*/
class Threadtest
{
	public static void main(String[] args)
{
		new Thread()
	{
			public void run()
		{
			for(int x=0;x<100;x++)
			{
				System.out.println(Thread.currentThread().getName()+"...."+x);
			
			}
		
		}


	}.start();





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

	Runnable r = new Runnable()
	{
		public void run()
		{
			for(int x=0;x<100;x++)
			{
				System.out.println(Thread.currentThread().getName()+"...."+x);
			}
		}
	
	
	};
	new Thread(r).start();


	}
}

/*
Runnable rn = new Runnable() {
    public void run() {
    }
};

这两个是一样的
class Anomymous implements Runnable {
    public void run() {
    }
}
Runnable rn = new Anomymous();
*/

十四.多线程-优先级

public final void join()
Throw interruptedexception
等待该线程终止。
抛出:
Interruptexception-如果任何线程中断了当前线程。抛出该异常时,当前线程的中断状态被清除。

Join:
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行


设置优先级只是抢的多一点,并不是完全占有
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
效果相当于抢的少一点

 

class Demo2 implements Runnable
{
	public void run()
	{
		for(int x=0;x<70;x++)
		{
			System.out.println(Thread.currentThread().getName()+"....."+x);
			//Thread.yield();//释放执行权,你一下我一下
		}

	}


}

class PriorityYieldDemo
{
	public static void main(String[] args) throws Exception
	{
		Demo2 d = new Demo2();
		Thread t1 = new Thread(d);
		Thread t2 = new Thread(d);
		t1.start();
		t1.setPriority(Thread.MAX_PRIORITY);//数据固定定位常量,数据共享定位静态
		t1.join();//要CPU执行权,t1结束了,主线程才会活过来,碰到谁的join,就等谁挂了才活过来
		t2.start();
		for(int x=0;x<80;x++)
		{

			System.out.println("main...."+x);

		}

		System.out.println("over");

	}


}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值