JAVA笔记__多线程(Day 13~15)

文章目录

  • 概述_进程&线程
  • 多线程技术原理
  • JVM中的多线程&垃圾回收
  • 单线程的问题
  • 创建线程的方式一
    • 继承Thread类
    • 多线程_调用start和run的区别
    • 多线程的内存图
    • 多线程的运行状态
    • 多线程_售票示例
  • 创建线程的方式二_实现Runnable接口
    • 步骤
    • 使用Runnable接口的好处
  • 多线程安全问题_同步
    • 原因&同步
    • 同步的好处和弊端
    • 同步的前提
    • 同步的小问题
    • 同步的练习
    • 多线程_同步函数
    • 验证同步函数使用的锁为this。
    • 验证static同步函数锁是类名.class
    • 单例模式的并发访问.class
    • 同步函数和同步代码块的区别
    • 死锁演示
    • 死锁示例
  • 生产者和消费者
    • 演示
    • 等待/唤醒机制体现
    • 等待/唤醒机制演示
    • 等待/唤醒机制原理理解
    • 多生产多消费问题以及解决
    • 多生产多消费JDK1.5-Lock接口
    • 多生产多消费JDK1.5-Condition接口
    • 多生产多消费JDK1.5-Api例程
    • wait()和sleep()的区别
  • 异常在多线程中的体现
  • 多线程_练习
  • 多线程_线程停止
  • 守护线程
  • Join方法
  • 多线程_优先级
  • 多线程_常见写法

概述_进程&线程

进程:就是应用程序在内存中分配的空间。(正在运行中的程序)

线程:是进程中负责程序执行的执行单元。也称为执行路径。

一个进程中至少有一个线程在负责该进程的运行。
如果一个进程中出现了多个线程,就称该程序为多线程程序
举例:运动场——鸟巢。水立方。

多线程技术原理

多线程技术:解决多部分代码同时执行的需求。合理地使用CPU资源。

JVM中的多线程&垃圾回收

/*
*  多线程的运行根据cpu的切换完成的。怎么切换cpu说的算,所以多线程运行有一个随机性(cpu的快速切换造成的)。
 * JVM中的多线程
 * 至少有两个线程,一个是负责自定义代码运行的。这个从main方法开始执行的线程称之为主线程。
 * 一个是负责垃圾回收的。
 * 
 * 通过实验,会发现每次结果不一定相同,因为随机性造成的。
 * 而且每一个线程都有运行的代码内容。这个称之为线程的任务。
 * 之所以创建一个线程就是为了去运行指定的任务代码。
 * 
 * 而线程的任务都封装在特定的区域中。
 * 比如:
 * 主线程运行的任务都定义在main方法中。
 * 垃圾回收线程在收垃圾时都会运行finalize方法。
 */
class Demo
{
    //定义垃圾回收方法
	public void finalize() 
	{
		System.out.println("demo ok");
	}
}
class FinallyDemo
{
	public static void main(String[] args) 
	{
		new Demo();
		new Demo();
		new Demo();
		System.gc();//启动垃圾回收器。不一定会执行,是否执行由CPU决定。
		System.out.println("Hello World");		
	}
}

结果:

Hello World!
demo ok
demo ok
demo ok

Hello World!
demo ok
demo ok

或其他的……

如果

class FinallyDemo
{
	public static void main(String[] args) 
	{
	    System.gc();//启动垃圾回收器。不一定会执行,是否执行由CPU决定。
		new Demo();
		new Demo();
		new Demo();
		System.out.println("Hello World");		
	}
}

结果:

Hello World!

因为执行太快了,所以没有demo ok了

单线程的问题

class Demo
{
	private String name;
	Demo(String name)
	{
		this.name=name;
	}
	public void show() 
	{		
		for(int x=1;x<=10;x++) 
		{
		System.out.println(name+"..."+x);
		}		
	}	
}
class ThreadDemo
{    
	public static void main(String[] args) 
	{   
		Demo d1=new Demo("张三");
		Demo d2=new Demo("李四");
		d1.show();
		d2.show();
	}	
}

创建线程的方式一

继承Thread类

如何建立一个执行路径?
(Java lang-Thread 线程 是程序中的执行线程。java虚拟机允许应用程序并发地运行多个执行线程。)
通过查阅api文档java.lang.Thread类。
该类的描述中有创建线程的两种方式:
一.继承Thread类。
1,继承Thread类。
2,覆盖run方法。
3,创建子类对象就是创建线程对象。
4,调用Thread类中的start方法就可以执行线程。并会调用run方法。

start()开启线程后,都会执行run方法。说明run方法存储的是线程要运行的代码。
所以,记住,自定义线程的任务代码都存储在run方法中。

class Demo extends Thread
{

	//覆盖run方法。
	public void run()
	{
		System.out.println("run run");
	}
	
}
class ThreadDemo
{    
	public static void main(String[] args) 
	{   
		Demo d1=new Demo("张三");
		Demo d2=new Demo("李四");
		d1.start();
		d2.start();
	}	
}

结果:

run run
run run

把上一节的代码用在线程上:

class Demo extends Thread
{
	private String name;
	Demo(String name)
	{
		this.name=name;
	}
	
	//覆盖run方法。
	public void run()
	{
		show();
	}
	
	public void show() 
	{		
		for(int x=1;x<=10;x++) 
		{
		System.out.println(name+"..."+x);
		}		
	}	
}
class ThreadDemo
{    
	public static void main(String[] args) 
	{   
		Demo d1=new Demo("张三");
		Demo d2=new Demo("李四");
		d1.start();
		d2.start();
	}	
}

结果之一(注意顺序):

李四...1
李四...2
李四...3
李四...4
李四...5
张三...1
李四...6
张三...2
李四...7
李四...8
李四...9
李四...10
张三...3
张三...4
张三...5
张三...6
张三...7
张三...8
张三...9
张三...10

再加上一点东西

lass ThreadDemo
{    
	public static void main(String[] args) 
	{   
		Demo d1=new Demo("张三");
		Demo d2=new Demo("李四");
		d1.start();
		d2.start();
		for(int x=1;x<=10;x++) 
		{
		System.out.println("main+"+x);
		}	
	}	
}

结果:

main+1
李四...1
李四...2
张三...1
张三...2
李四...3
李四...4
李四...5
李四...6
main+2
main+3
main+4
main+5
main+6
main+7
main+8
main+9
main+10
李四...7
李四...8
李四...9
李四...10
张三...3
张三...4
张三...5
张三...6
张三...7
张三...8
张三...9
张三...10

图示:
由左变成右
JAVA笔记__多线程(Day 13~15)_第1张图片

多线程_调用start和run的区别

调用start会开启线程,让开启的线程去执行run方法中的线程任务。
直接调用run方法,线程并未开启,去执行run方法的只有主线程。

线程本身有自己给自己定义名称,用getName()可以返回该名称。
“Thread-”
把上一节的代码修改成:

class Demo extends Thread
{
	private String name;
	Demo(String name)
	{
		this.name=name;
	}
	
	//覆盖run方法。
	public void run()
	{
		show();
	}
	
	public void show() 
	{		
		for(int x=1;x<=10;x++) 
		{
		System.out.println(this.getName()+"…"+name+"..."+x);//当前执行对象,Thread-0,1...
		//System.out.println(Thread.currentThread().getName()+"…"+name+"..."+x);//当前执行路径 main(虽然我这里的程序这两种结果都一样,都是Thread-0,1...==)
		}		
	}	
	
}

main中不能直接用getName(),因为没有继承Thread类。
而currentThread()是Thread类中的返回对当前正在执行的线程对象的引用。而且是静态的,可以调用。

lass ThreadDemo
{    
	public static void main(String[] args) 
	{   
		Demo d1=new Demo("张三");//Thread-0
		Demo d2=new Demo("李四");//Thread-1
		d1.start();//start():两件事:1.开启线程,2.调用run方法。
		d2.start();
		for(int x=1;x<=10;x++) 
		{
		System.out.println(Thread.currentThread().getName()+"----------main+"+x);
		}	
	}	
}

结果:

main----------main+1
Thread-1...李四...1
Thread-1...李四...2
Thread-0...张三...1
Thread-1...李四...3
main----------main+2
Thread-1...李四...4
Thread-0...张三...2
Thread-1...李四...5
main----------main+3
main----------main+4
Thread-1...李四...6
Thread-0...张三...3
Thread-1...李四...7
main----------main+5
Thread-1...李四...8
Thread-0...张三...4
Thread-0...张三...5
Thread-1...李四...9
main----------main+6
Thread-1...李四...10
Thread-0...张三...6
main----------main+7
Thread-0...张三...7
main----------main+8
Thread-0...张三...8
Thread-0...张三...9
main----------main+9
Thread-0...张三...10
main----------main+10

多线程的内存图

JAVA笔记__多线程(Day 13~15)_第2张图片 JAVA笔记__多线程(Day 13~15)_第3张图片

每个x是局部变量 所以不冲突

多线程的运行状态

JAVA笔记__多线程(Day 13~15)_第4张图片 JAVA笔记__多线程(Day 13~15)_第5张图片

stop()已过时

多线程_售票示例

class SaleTicket extends Thread
{
	private int tickets=100;
    //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
	public void run() 
	{
		while(true)//无限循环,写多线程的时候常见。
		{
			if(tickets>0)
				//System.out.println(this.getName()+"..."+tickets--);//main
			    System.out.println(Thread.currentThread().getName()+"..."+tickets--);
		}
	}
}
class TicketDemo 
{    
	public static void main(String[] args) 
	{   
		//创建四个线程。会创建400张票。不合适,不建议票变成静态的,所以如何共享这100张票。需要将资源与线程分离。
		//到api中查阅了第二种创建线程的方式。
		SaleTicket t1=new SaleTicket();
		SaleTicket t2=new SaleTicket();
		SaleTicket t3=new SaleTicket();
		SaleTicket t4=new SaleTicket();
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}	
}

结果中每个线程都要从100张减起,于实际不合理。
内存图解:
JAVA笔记__多线程(Day 13~15)_第6张图片

如何改进:

class TicketDemo 
{    
	public static void main(String[] args) 
	{   
		//创建线程,开启四次。
		SaleTicket t1=new SaleTicket();
		//错误
		t1.start();
		t1.start();
		t1.start();
		t1.start();
	}	
}

结果:

java.lang.IllegalThreadStateException //非法线程状态异常
//因为多次启动一个线程是非法的。

所以如何改进:
用static(不建议用)、单例。
用创建线程的第二种方法。

创建四个线程。会创建400张票。不合适,不建议票变成静态的,所以如何共享这100张票。需要将资源与线程分离。
到api中查阅了第二种创建线程的方式。

创建线程的方式二_实现Runnable接口

步骤

1.定义一个类实现Runnable。
2.覆盖Runnable接口中的run方法,将线程要运行的任务代码存储到该方法中。
3.通过Thread类创建线程对象,并将实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
4.调用Thread类的start方法,开启线程。

class SaleTicket implements Runnable
{
	private int tickets=10;
    //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
	public void run() 
	{
		while(true)//无限循环,写多线程的时候常见。
		{
			if(tickets>0)
				//System.out.println(this.getName()+"..."+tickets--);//main
			    System.out.println(Thread.currentThread().getName()+"..."+tickets--);
		}
	}
}
class TicketDemo2
{    
	public static void main(String[] args) 
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		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笔记__多线程(Day 13~15)_第7张图片

使用Runnable接口的好处

JAVA笔记__多线程(Day 13~15)_第8张图片

1.避免了继承Thread类的单继承的局限性。
2.Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
3.Runnable接口的出现。降低了线程对象和线程任务的耦合性。
所以,以后创建线程都使用第二种方式。

多线程安全问题_同步

原因&同步

产生的原因:
1.线程任务中有处理到共享的数据。
2.线程任务中有多条对共享数据的操作。
一个线程在操作共享数据的过程中,其他线程参与了运算,造成了数据的错误。

解决的思想:
只要能保证多条操作共享数据的代码在某一时间段,被一条线程所执行,在执行期间不允许其他线程参与运算。

如何保证:
用到了同步代码块。

synchronized(对象)
{
  需要被同步的代码。
}
JAVA笔记__多线程(Day 13~15)_第9张图片
class SaleTicket implements Runnable
{
	private int tickets=10;
    //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
	public void run() 
	{
		while(true)//无限循环,写多线程的时候常见。
		{
			if(tickets>0)
				try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
			    System.out.println(Thread.currentThread().getName()+"..."+tickets--);
		}
	}
}
class TicketDemo3
{    
	public static void main(String[] args) 
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		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笔记__多线程(Day 13~15)_第10张图片
当tickets的数量被判断成1后,同时多进程,可能就判断不到,因此产生负数。

解决:

class SaleTicket implements Runnable
{
	private int tickets=100;
    //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
	Object obj=new Object();
	public void run() 
	{
		while(true)//无限循环,写多线程的时候常见。
		{
			synchronized(obj) 
			{
				if(tickets>0)
					try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
				    System.out.println(Thread.currentThread().getName()+"..."+tickets--);				
		    }
	    }
	}
}
class TicketDemo3
{    
	public static void main(String[] args) 
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		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();
	}	
}

可是结果还是有问题,稍后再做改进。

同步的好处和弊端

加上下面代码后的内存图解:

synchronized(对象)
{
  需要被同步的代码。
}
JAVA笔记__多线程(Day 13~15)_第11张图片

被同步的代码只能让一个线程执行下来。相当于一个锁。

同步在目前情况下保证了一次只能有一个线程在执行。其他线程进不来。
这就是同步的锁机制。

好处:解决了多线程的安全问题。
弊端:降低效率。

同步的前提

有可能出现这样一种情况:
多线程安全问题出现后,加入了同步机制,没有想到,安全问题依旧,怎么办?

这时肯定是同步出了问题。

只要遵守了同步的前提,就可以解决。

同步的前提:
多个线程在同步中必须使用同一个锁。这才是对多个线程同步。

同步的小问题

把共享数据的部分同步,避免全程单线程。
JAVA笔记__多线程(Day 13~15)_第12张图片

同步的练习

两个储户,到同一个银行存钱,每个人存了3次,一次100元。
1.描述银行。
2.描述储户任务。

分析多线程是否存在安全隐患。
1.线程任务中是否有共享的数据。
2.是否多条操作共享数据的代码。

class Bank
{
	private int sum;
	private Object obj=new Object();
	public void add(int n) 
	{
		synchronized(obj) 
		{
			sum=sum+n;
			try{Thread.sleep(10);}catch(Exception e){}//多测试几次
			System.out.println("sum="+sum);
		}
	}	
}
class Customer implements Runnable
{
	private Bank b=new Bank();
    
	public void run() 
	{
		for(int x=0;x<3;x++)
		{
			b.add(100);
		}
	}
}
class ExtendsDemo3 
{    
	public static void main(String[] args) 
	{   
		//1.创建任务对象。
	    Customer c=new Customer();
		//创建2个线程。通过Thread类对象。
		Thread t1=new Thread(c);
		Thread t2=new Thread(c);	
		t1.start();
		t2.start();	
	}	
}

不加同步结果之一:

sum=200
sum=200
sum=300
sum=400
sum=500
sum=600

出现两次200,出现安全问题。

解决:
加同步
该安全隐患消失。

多线程_同步函数

上一小节中的第一段代码
因为函数封装性,在这里可以把同步当成修饰符。

同步函数 其实就是在函数上加上了同步关键字进行修饰。
同步表现形式有两种:
1.同步代码块。
2.同步函数。

同步函数使用的锁是什么?
函数需要被对象调用,哪个对象不确定,但是都用this来表示。
所以同步函数使用的锁是this.
(这里指b)

class Bank
{
	private int sum;
	public synchronized void add(int n) 
	{	
			sum=sum+n;
			try{Thread.sleep(10);}catch(Exception e){}//多测试几次
			System.out.println("sum="+sum);
	}	
}

验证同步函数使用的锁为this。

验证需求:
启动两个线程,一个线程负责执行同步代码块(使用明锁)。
另一个线程使用同步函数(使用this锁)。
两个执行的任务是一样的,都是卖票。如果他们没有使用相同的锁,说明他们没有同步。会出现数据错误。

怎么能让一个线程一直在同步代码块中,一个线程在同步函数呢?
可以通过切换的方式。

class SaleTicket implements Runnable
{
	private int tickets=100;
    //定义一个boolean标记。
	boolean flag=true;
	Object obj=new Object();
	public void run() 
	{
		if(flag)
			while(true)//无限循环,写多线程的时候常见。
			{
				synchronized(obj) 
				{
					if(tickets>0){
						try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
					    System.out.println(Thread.currentThread().getName()+"...code..."+tickets--);		}		
			    }
		    }
		else 
			while(true)
				sale();
			
	}
	
	public synchronized void sale()
	{
	if(tickets>0){
		try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
	    System.out.println(Thread.currentThread().getName()+"...function..."+tickets--);	}	
	}
}
class ExtendsDemo3 
{    
	public static void main(String[] args) 
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		
		t1.start();
		t.flag=false;
		t2.start();
	}	
}

结果:只有function在运行。
原因:因为flag很快被转换为false.

Thread-0...function...100
Thread-1...function...99
Thread-1...function...98
Thread-1...function...97
Thread-1...function...96
Thread-1...function...95
Thread-1...function...94
Thread-1...function...93
Thread-1...function...92
Thread-1...function...91
Thread-1...function...90
Thread-1...function...89
Thread-1...function...88
Thread-1...function...87
Thread-1...function...86
Thread-1...function...85
Thread-1...function...84
Thread-1...function...83
Thread-1...function...82
Thread-1...function...81
Thread-1...function...80
Thread-1...function...79
Thread-1...function...78
Thread-1...function...77
Thread-1...function...76
Thread-1...function...75
Thread-1...function...74
Thread-1...function...73
Thread-1...function...72
Thread-1...function...71
Thread-1...function...70
Thread-1...function...69
Thread-1...function...68
Thread-1...function...67
Thread-1...function...66
Thread-1...function...65
Thread-1...function...64
Thread-1...function...63
Thread-1...function...62
Thread-1...function...61
Thread-1...function...60
Thread-1...function...59
Thread-1...function...58
Thread-1...function...57
Thread-1...function...56
Thread-1...function...55
Thread-1...function...54
Thread-1...function...53
Thread-1...function...52
Thread-1...function...51
Thread-1...function...50
Thread-1...function...49
Thread-1...function...48
Thread-1...function...47
Thread-1...function...46
Thread-1...function...45
Thread-1...function...44
Thread-1...function...43
Thread-1...function...42
Thread-1...function...41
Thread-1...function...40
Thread-1...function...39
Thread-1...function...38
Thread-1...function...37
Thread-1...function...36
Thread-1...function...35
Thread-1...function...34
Thread-1...function...33
Thread-1...function...32
Thread-1...function...31
Thread-1...function...30
Thread-1...function...29
Thread-1...function...28
Thread-1...function...27
Thread-1...function...26
Thread-1...function...25
Thread-1...function...24
Thread-1...function...23
Thread-1...function...22
Thread-1...function...21
Thread-1...function...20
Thread-1...function...19
Thread-1...function...18
Thread-1...function...17
Thread-1...function...16
Thread-1...function...15
Thread-1...function...14
Thread-1...function...13
Thread-1...function...12
Thread-1...function...11
Thread-1...function...10
Thread-1...function...9
Thread-1...function...8
Thread-1...function...7
Thread-1...function...6
Thread-1...function...5
Thread-1...function...4
Thread-1...function...3
Thread-1...function...2
Thread-1...function...1

解决方法:
加Thread.sleep(10);

class ExtendsDemo3 
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		
		t1.start();
		Thread.sleep(10);                  //加这行
		
		t.flag=false;
		t2.start();
	}	
}

结果:同步代码块用的是obj锁
同步函数用得是this锁。

如果想把这两个改为同一个锁:
把(obj)改为(this)

class SaleTicket implements Runnable
{
	private int tickets=100;
    //定义一个boolean标记。
	boolean flag=true;
	Object obj=new Object();
	public void run() 
	{
		if(flag)
			while(true)//无限循环,写多线程的时候常见。
			{
				synchronized(this)    //把obj改为this
				{
					if(tickets>0){
						try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
					    System.out.println(Thread.currentThread().getName()+"...code..."+tickets--);	}			
			    }
		    }
		else 
			while(true)
				sale();
			
	}
	
	public synchronized void sale()
	{
	if(tickets>0){
		try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
	    System.out.println(Thread.currentThread().getName()+"...function..."+tickets--);	}	
	}
}
class ExtendsDemo3 
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		
		t1.start();
		Thread.sleep(10);
		
		t.flag=false;
		t2.start();
		
	}	
}

验证static同步函数锁是类名.class

如果同步函数被静态修饰
这时的锁肯定不是this.
static方法随着类加载,这时不一定有该类的对象。但是一定有一个该类的字节码文件对象。
这个对象简单的表示方式就是 类名.class
属于 Class类型(描述类的类)

如下:

class SaleTicket implements Runnable
{
	private int tickets=100;
    //定义一个boolean标记。
	boolean flag=true;
	Object obj=new Object();
	public void run() 
	{
		if(flag)
			while(true)//无限循环,写多线程的时候常见。
			{
				synchronized(SaleTicket.class)    //把this改为SaleTicket.class
				{
					if(tickets>0){
						try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
					    System.out.println(Thread.currentThread().getName()+"...code..."+tickets--);}				
			    }
		    }
		else 
			while(true)
				sale();
			
	}
	
	public static synchronized void sale()
	{
	  if(tickets>0){
		  try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
	      System.out.println(Thread.currentThread().getName()+"...function..."+tickets--);}		
	}
}
class ExtendsDemo3 
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		
		t1.start();
		Thread.sleep(10);
		
		t.flag=false;
		t2.start();
		
	}	
}

Demo.class字节码文件
JAVA笔记__多线程(Day 13~15)_第13张图片

表达方式
Demo.class
Person.class
int.class
arr.class

单例模式的并发访问.class

//饿汉,相对于多线程并发,安全。
class Single {
	  //创建一个本类对象
	 static Single s=new Single();
	 
	  //构造函数私有化
	 private Single() {}
	 
	 //定义一个方法返回该对象。让其他程序可以获取到。
	  static Single getInstance()
	  {
		  return s;
	  }
}
//懒汉。延迟加载模式。
//在多线程并发访问时,会出现线程安全问题,所以要加同步。
//加了同步就可以解决问题。无论是同步函数,还是同步代码都行。
//但是,效率低了。
class Single {
	  //创建一个本类对象
	 private static Single s=new Single();
	 
	  //构造函数私有化
	 private Single() {}
	 
	 //定义一个方法返回该对象。让其他程序可以获取到。之所以定义访问,就是为了可控。
	 public static synchronized Single getInstance()//加同步
	  {
	       public static Single getInstance()
	       {
		        if(s==null)
		            s=new Single();//
		        return s;
	        }
}
class Demo implements Runnable
{
     public void run()
     {
           Single.getInstance();
     }
}

如何提高懒汉式的提高效率?用静态同步代码块。
可以通过if对单例对象的双重判断的形式。

class Single {
	  //创建一个本类对象
	 private static Single s=new Single();
	 
	  //构造函数私有化
	 private Single() {}
	 
	 //定义一个方法返回该对象。让其他程序可以获取到。之所以定义访问,就是为了可控。
	 public static Single getInstance()//加同步
  {
    if(s==null)
    {
	       synchronized(Single.class)
	       {
	            public static Single getInstance()
	           {
		            if(s==null)
		                s=new Single();//      
	           }
	    }
	    return s;
	 }
}

解析:
1.当线程0进去后,判断是否为空,为空。进入同步的锁,判断是否为空,为空,创建对象。
2.当线程1进去后,判断是否为空,为空,但因为同步,所以只能等线程0执行完被同步的部分时,再进去,再判断是否为空,不为空,不能创建对象。
3.当新的线程再进来时,判断是否为空,不为空,不创建对象。省去了同步的过程,因此提高效率。

同步函数和同步代码块的区别

同步代码块使用的任意的对象作为锁。
同步函数只能使用this作为锁,还有多个类中使用同一个锁。

如果说,一个类中只需要一个锁,这是可以考虑同步函数,使用this,写法简单。
但是一个类中如果需要多个锁,还有多个类中使用同一个锁。这时就只能使用同步代码块。

建议使用同步代码块。

死锁演示

场景一:
同步嵌套。
有两个锁,一个锁嵌套另一个锁。
下面代码中的两个锁分别为obj锁和this锁。

class SaleTicket implements Runnable
{
	private int tickets=100;
    //定义一个boolean标记。
	boolean flag=true;
	Object obj=new Object();
	public void run() 
	{
		if(flag)
			while(true)//无限循环,写多线程的时候常见。
			{
				synchronized(obj)      //obj lock
				{
					sale();
			    }
		    }
		else 
			while(true)
				sale();
			
	}
	
	public synchronized void sale()    //this锁
	{
	    synchronized(obj)
	    {
	         if(tickets>0){
		        try{Thread.sleep(10);}catch(InterruptedException e) {}//让线程到这里稍微停一下。
	            System.out.println(Thread.currentThread().getName()+"...function..."+tickets--);}
	    }
	}
}
class DeadLockDemo
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//线程任务对象。
		SaleTicket t=new SaleTicket();
		//创建四个线程。通过Thread类对象。
		Thread t1=new Thread(t);
		Thread t2=new Thread(t);
		
		t1.start();
		Thread.sleep(10);
		
		t.flag=false;
		t2.start();
		
	}	
}

结果:
有时候不会运行到1.

所以尽量避免同步嵌套

死锁示例

以同步嵌套的死锁为例

class Task implements Runnable
{
	private boolean flag;
	Task(boolean falg)
	{
		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
	{
		synchronized (MyLock.LOCKB) 
		{
		  while(true){
			System.out.println("else...lockb");
			synchronized(MyLock.LOCKA) 
			{
				System.out.println("else...locka");
			}
		  }
		}
	}
  }
}
class MyLock
{
	public static final Object LOCKA=new Object();
	public static final Object LOCKB=new Object();
}
class ExtendsDemo3
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//线程任务对象。
		Task t1=new Task(true);
		Task t2=new Task(false);
		//创建四个线程。通过Thread类对象。
		//Thread t1=new Thread(t);
		//Thread t2=new Thread(t);
		new Thread(t1).start();
	    new Thread(t2).start();
	}	
}

结果为什么只有else?

生产者和消费者

多线程间的通信。多个线程都在处理同一个资源,但是处理的任务却不一样。

演示

class Resource 
{
	private String name;//商品名称
	private int count;//商品资源
	//提供了给商品赋值的方法
	public synchronized void set(String name) 
	{
		this.name=name+"..."+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
	}
	
	//提供一个获取商品的方法
	public synchronized void get() 
	{
		System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
	}
}
//生产者
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.get();
	}
}
class ProducerConsumerDemo
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//1.创建资源。
		Resource r=new Resource();
		//2.创建两个任务。
		Producer pro=new Producer(r);
		Consumer con=new Consumer(r);
		//创建线程。
		Thread t1=new Thread(pro);
		Thread t2=new Thread(con);
		
		t1.start();
		t2.start();
	}	
}

结果:
和实际想要的生产出来的产品再被消费的顺序可以通过同步解决,但是大面积的生产然后再大面积地消费,不符合实际。
也就是说,通过同步解决了没生产就消费的问题。
但出现了连续的生产没有消费的情况,和需求中的生产一个,消费一个的情况不符。

使用了等待唤醒机制。

wait():该方法可以让线程处于冻结状态,并将线程临时存储到线程池中。
notify():唤醒指定线程池中的任意一个线程。唤醒等待的线程。
notifyAll():唤醒指定线程池中的所有线程。

等待/唤醒机制体现

先定义标记,如果标记为false,表示没有产品,此时需要生产者生产,消费者冻结,生产完后唤醒消费者。以此类推。
JAVA笔记__多线程(Day 13~15)_第14张图片

等待/唤醒机制演示

java文档中 wait在Thread类中

把上上节中的代码的第一部分改下即可。

//描述资源
class Resource 
{
	private String name;//商品名称
	private int count=1;//商品资源
	
	//定义标记
	private boolean flag;
	
	//提供了给商品赋值的方法
	public synchronized void set(String name) 
	{
		if(flag)
			try{wait();}catch(InterruptedException e) {}//如果标记为true,则表示有产品,不需要生产,生产者需要再等一等。为false,就生产。
		this.name=name+"..."+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
		
		//生产完毕,将标记改为true。
		flag=true;
		
		//唤醒消费者
		notify();
	}
	
	//提供一个获取商品的方法
	public synchronized void get() 
   {
	  if(!flag)
		  try{wait();}catch(InterruptedException e) {}
	  
		System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
		
	  //生产完毕,将标记改为false。
	  flag=false;
			
	  //唤醒生产者
	   notify();
   }
}

等待/唤醒机制原理理解

注意点:
1.wait()、notify()、notifyAll()必须使用在同步之中,因为它们用来操作同步锁上的线程的状态的。
2.在使用这些方法时,必须标识它们所属于的锁,标识方式就是 锁对象.wait(),锁对象.notify(),锁对象.notifyAll()。
相同锁的notify,可以获取相同锁的wait();
3.省略锁对象时默认为this.

比如下图,4线程的lockb.notify只能唤醒2线程和3线程:
JAVA笔记__多线程(Day 13~15)_第15张图片

多生产多消费问题以及解决

class ProducerConsumerDemo
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//1.创建资源。
		Resource r=new Resource();
		//2.创建两个任务。
		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();
	}	
}

结果中有出现类似

Thread-0...生产者..面包...32539
Thread-1...生产者..面包...32540
Thread-3...消费者..面包...32539

或者

Thread-0...生产者..面包...32539
Thread-2...消费者..面包...32539
Thread-3...消费者..面包...32539

产生问题1:
重复生产,重复消费。

具体:
notify唤醒的可能是任意一个等待的线程,假如唤醒的是生产者的第二个线程,这时不需要再经过标志判断和wait,直接运行下面的程序,因此会出现第一个结果。
第二个结果同理。

原因:
经过复杂的(等,资格)分析,发现被唤醒的线程没有判断标记就开始工作(生产/消费)了。
导致了重复的生产和消费的发生。

解决:
那就是被唤醒的线程必须判断标记。
使用while循环搞定。

但结果出现死锁现象。

产生问题2:
死锁了,所有的线程都处于冻结状态。

具体:
notify唤醒的可能是任意一个等待的线程,假如唤醒的是生产者的第二个线程,这时第二个线程要while循环,判断标记,因为生产的商品未被消费,因此2线程也处于等待状态。综上,四个线程都处于等待状态,因此处于死锁状态。

原因:
本方线程在唤醒时,又一次唤醒了本方线程,而本方线程循环判断标记,又继续等待,而导致所有的线程都等待了。
JAVA笔记__多线程(Day 13~15)_第16张图片

解决:
希望本方如果唤醒了对方线程,就可以解决。
可以使用notifyAll()方法。
虽然是全部唤醒,也就是说既有本方,又有对方。但是本方醒后,会判断标记继续等待。这样对方就有线程可以继续执行了。

综上:将if改为while循环,将notify()改为notifyAll();

这样已经实现了多生产多消费。
小缺陷:效率略低,因为notifyAll也唤醒了本方,做了不必要的判断。

多生产多消费JDK1.5-Lock接口

1.5新增java.util.concurrent.locks

解决多生产多消费的效率问题。
使用了JDK1.5 java.util.concurrent.locks包中的对象
Lock接口:它的出现比synchronized有更多的操作。

lock():获取锁。
unlock():释放锁。

同步代码块或者同步函数的锁操作是隐式的。
使用了JDK1.5 Lock接口,按照面向对象的思想,将锁单独封装成了一个对象。
并提供了对锁的显示操作。

Lock接口就是同步的替代。
1.将线程中的同步更换为Lock接口的形式。

TIPs:
父类名 a = new 子类名()
子类名 b = new 子类名()
a只能调用父类的函数,不能调用子类的函数,因为它没有继承
b可以调用父类的函数也可以调用子类的函数
但是对构造函数的执行上二者是没有区别的。

如果获取锁和释放锁之间出现异常,那么异常下面的局部代码是不执行的,那么这个锁是不完整的。影响同步。
所以应该使用:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

把第一段代码改成

import java.util.concurrent.locks.*;
import java.util.concurrent.locks.ReentrantLock;
//描述资源
class Resource 
{
	private String name;//商品名称
	private int count=1;//商品资源
	private Lock lock=new ReentrantLock();
	//定义标记
	private boolean flag;
	
	//提供了给商品赋值的方法
	public void set(String name) 
	{
		//获取锁
		lock.lock();
		
		try {
		while(flag)
			try{wait();}catch(InterruptedException e) {}//如果标记为true,则表示有产品,不需要生产,生产者需要再等一等。
			//为false,就生产。
		this.name=name+"..."+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
		
		//生产完毕,将标记改为true。
		flag=true;
		
		//唤醒消费者
		notifyAll();}
		
		finally {
		//释放锁
		lock.unlock();}
	}
	
	//提供一个获取商品的方法
	public synchronized void get() 
   {
	   //获取锁
	  lock.lock();
				
	  try {
	  while(!flag)
		  try{wait();}catch(InterruptedException e) {}
	  
		System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
		
	  //生产完毕,将标记改为false。
	  flag=false;
			
	  //唤醒生产者
	   notifyAll();}
	  
	  finally {
		//释放锁
	    lock.unlock();}
   }
}

结果报错,因为wait(),notify()等只能用在同步函数和同步代码块中,
在这里wait没有了同步区域,没有了所属的同步锁。
不能用在这个Lock类中。
同步升级了。其中锁已经不是在任意对象,而是Lock类型的对象。
那么和任意对象绑定的监视器方法,是不是也升级了,有了专门和Lock类型锁的绑定的监视器方法呢?
查阅api。Condition接口替代了Object中的监视器方法。

多生产多消费JDK1.5-Condition接口

以前监视器方法封装到每一个对象中。
现在将监视器方法封装到了Condition对象中。
方法名为:await signal signalAll
因此要用到Condition接口。

监视器对象Condition如何和Lock绑定呢?
可以通过Lock接口的newCondition()方法完成。
JAVA笔记__多线程(Day 13~15)_第17张图片

改进:修改监视器方法。
具体如下:

import java.util.concurrent.locks.*;
import java.util.concurrent.locks.ReentrantLock;
//描述资源
class Resource 
{
	private String name;//商品名称
	private int count=1;//商品资源
	
	//创建新Lock。
	private Lock lock=new ReentrantLock();
	
	//创建和Lock绑定的监视器对象。
	private Condition con=lock.newCondition();
	
	//定义标记
	private boolean flag;
	
	//提供了给商品赋值的方法
	public void set(String name) 
	{
		//获取锁
		lock.lock();
		
		try {
		while(flag)
			try{con.await();}catch(InterruptedException e) {}//如果标记为true,则表示有产品,不需要生产,
			//生产者需要再等一等。为false,就生产。
		this.name=name+"..."+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
		
		//生产完毕,将标记改为true。
		flag=true;
		
		//唤醒消费者
		con.signalAll();}
		
		finally {
		//释放锁
		lock.unlock();}
	}
	
	//提供一个获取商品的方法
	public synchronized void get() 
   {
	   //获取锁
	  lock.lock();
				
	  try {
	  while(!flag)
		  try{con.await();}catch(InterruptedException e) {}
	  
		System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
		
	  //生产完毕,将标记改为false。
	  flag=false;
			
	  //唤醒生产者
	   con.signalAll();}
	  
	  finally {
		//释放锁
	    lock.unlock();}
   }
}

但是,问题依旧,一样唤醒了本方,效率仍旧低。

如何解决?
JAVA笔记__多线程(Day 13~15)_第18张图片

完整代码:

import java.util.concurrent.locks.*;
import java.util.concurrent.locks.ReentrantLock;
//描述资源
class Resource 
{
	private String name;//商品名称
	private int count=1;//商品资源
	
	//创建新Lock。
	private Lock lock=new ReentrantLock();
	
	//创建和Lock绑定的监视器对象。创建两个。
	//生产者监视器
	private Condition producer_con=lock.newCondition();
	//消费者监视器
	private Condition consumer_con=lock.newCondition();
	
	//定义标记
	private boolean flag;
	
	//提供了给商品赋值的方法
	public void set(String name) 
	{
		//获取锁
		lock.lock();
		
		try {
		while(flag)
			try{producer_con.await();}catch(InterruptedException e) {}//如果标记为true,则表示有产品,不需要生产,生产者需要再等一等。为false,就生产。
		this.name=name+"..."+count;
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
		
		//生产完毕,将标记改为true。
		flag=true;
		
		//生产完毕,应该唤醒一个消费者来消费。
		consumer_con.signal();}
		
		finally {
		//释放锁
		lock.unlock();}
	}
	
	//提供一个获取商品的方法
	public synchronized void get() 
   {
	   //获取锁
	  lock.lock();
				
	  try {
	  while(!flag)
		  try{consumer_con.await();}catch(InterruptedException e) {}
	  
		System.out.println(Thread.currentThread().getName()+"...消费者.."+this.name);
		
	  //消费完毕,将标记改为false。
	  flag=false;
	  
	  //消费完毕,应该唤醒一个消费者来消费。
	   producer_con.signal();}
	   
	  
	  finally {
		//释放锁
	    lock.unlock();}
   }
}
//生产者
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.get();
	}
}
class ExtendsDemo3
{    
	public static void main(String[] args) throws InterruptedException //因为主函数不是覆盖函数,所以需要加抛出异常。
	{   
		//1.创建资源。
		Resource r=new Resource();
		//2.创建两个任务。
		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-Api例程

 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;//如果角标加一后为数组长度,那么把角标设为0,也就是说这次存完商品后要从头存起。
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; //从取的角标上拿该产品,也就是x。
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

前几节的程序为下图左半部分,例程为下图的右半部分。
JAVA笔记__多线程(Day 13~15)_第19张图片

wait()和sleep()的区别

相同:可以让线程处于冻结状态。

不同:
1.wait()可以指定时间,也可以不指定。
sleep()必须指定时间。
2.wait()释放CPU资源,释放锁。
sleep()释放CPU资源,不释放锁。

注意:
同步中是否只有一个线程运行。
是的。
但是如果存在wait(),他会出现多个情况,但是不需要担心数据错误的问题,是因为只有一个线程持有锁,执行完就会把锁放掉,其他的才有资格去执行。

synchronized(obj)
{
  obj.wait();//t0.t1,t2
  code...

}

synchronized(obj)
{
  obj.notifyAll();//t3
}

异常在多线程中的体现

异常会提示发生在那个线程上。
异常会结束线程任务,也就是可以结束所在线程。
演示1:

线程异常演示

class ThreadExceptionDemo
{    
	public static void main(String[] args) 
	{   
		int[] arr=new int[3];
		System.out.print(arr[3]);
	}
}

结果(异常在主线程中):

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
	at com.covnew.demo.ExtendsDemo3.main(ExtendsDemo3.java:9)

演示2:

class Demo implements Runnable
{
	public void run() 
	{
		System.out.println(4/0);
	}
}
class ThreadExceptionDemo
{    
	public static void main(String[] args) throws Exception
	{   
		new Thread(new Demo()).start();
		Thread.sleep(10);
		int[] arr=new int[3];
		System.out.print(arr[4]);
	    System.out.print("over");
	}
}

结果:

Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at com.covnew.demo.Demo.run(SingleDemo.java:8)
	at java.lang.Thread.run(Unknown Source)
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
	at com.covnew.demo.ExtendsDemo3.main(ExtendsDemo3.java:11)

异常结束的是线程
over也不能输出。

多线程_练习

1.搞定妖的问题。
分析:
1)共享数据。
2)线程任务中有多条操作共享数据的代码。

2.name和sex是私有的,需要在Res类中对外提供访问name和sex的方法。这个可以参照生产者消费者produceconsumerdemo.java。

3.实现间隔输出,使用等待唤醒机制produceconsumerdemo2.java。
一般情况下,需要判断条件。

解决一:
加了同步,问题依旧。看同步前提!
把第一个线程和第二个线程都用一个锁同步 synchronized(Resource.class){} 可以用静态锁,也可以用r,都是唯一的。实在找不到合适的锁,可以自己定义。如下面的代码。

class MyLock
{
	public static final Object obj=new Object();
}
//描述资源
class Resource 
{
	String name;
	String sex;
}
class Input implements Runnable
{
	private Resource r;
	Input(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		int x=0;
		while(true) 
		{
			synchronized(MyLock.obj) {
			if(x==0)
			{
				r.name="张三";
				r.sex="男男男男男男男";
			}
			else 
			{
				r.name="rose";
				r.sex="women";
			}
			x=(x+1)%2;}
		}
	}
}
class Output implements Runnable
{
	private Resource r;
	Output(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		while(true) 
		{
			synchronized(MyLock.obj) {
			System.out.println(r.name+"..."+r.sex);}
		}
	}
		
}
class Test
{    
	public static void main(String[] args) throws Exception
	{   
		//1.创建资源。
	    Resource r=new Resource();
		//2.创建两个任务。
		Input pro=new Input(r);
		Output con=new Output(r);
		//创建线程。
		Thread t0=new Thread(pro);
		Thread t1=new Thread(con);			
		t0.start();
		t1.start();
	}
}

解决二:

//描述资源
class Resource 
{
	private String name;
	private String sex;
	public synchronized void set(String name,String sex) 
	{
		this.name=name;
		this.sex=sex;
	}
	
	public synchronized void out() 
	{
		System.out.println(name+"..."+sex);
	}
}
class Input implements Runnable
{
	private Resource r;
	Input(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		int x=0;
		while(true) 
		{
			
			if(x==0)
			{
				r.set("张三","男男男男男男男");
			}
			else 
			{
				r.set("rose","women");
			}
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable
{
	private Resource r;
	Output(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		while(true) 
		{	
			r.out();
		}
	}		
}
class Test
{    
	public static void main(String[] args) throws Exception
	{   
		//1.创建资源。
	    Resource r=new Resource();
		//2.创建两个任务。
		Input pro=new Input(r);
		Output con=new Output(r);
		//创建线程。
		Thread t0=new Thread(pro);
		Thread t1=new Thread(con);	
		t0.start();
		t1.start();
	}
}

解决三:
加标志和等待唤醒

/描述资源
class Resource 
{
	private String name;
	private String sex;
	//定义标记
	private boolean flag;
	public synchronized void set(String name,String sex) 
	{
		if(flag)
			try{this.wait();}catch(InterruptedException e){}
		this.name=name;
		this.sex=sex;
		
		flag=true;
		notify();
	}
	
	public synchronized void out() 
	{
		if(!flag)
			try{this.wait();}catch(InterruptedException e){}
		System.out.println(name+"..."+sex);
		
		flag=false;
		this.notify();
	}
}
class Input implements Runnable
{
	private Resource r;
	Input(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		int x=0;
		while(true) 
		{
			
			if(x==0)
			{
				r.set("张三","男男男男男男男");
			}
			else 
			{
				r.set("rose","women");
			}
			x=(x+1)%2;
		}
	}
}
class Output implements Runnable
{
	private Resource r;
	Output(Resource r)
	{
		this.r=r;
	}
	public void run() 
	{
		while(true) 
		{	
			r.out();
		}
	}		
}
class ExtendsDemo3 
{    
	public static void main(String[] args) throws Exception
	{   
		//1.创建资源。
	    Resource r=new Resource();
		//2.创建两个任务。
		Input pro=new Input(r);
		Output con=new Output(r);
		//创建线程。
		Thread t0=new Thread(pro);
		Thread t1=new Thread(con);			
		t0.start();
		t1.start();
	}
}

改进:
把代码的第一段改为:

//描述资源
class Resource 
{
	private String name;
	private String sex;
	//定义标记
	private boolean flag;
	//定义新锁。
	private final Lock lock=new ReentrantLock();
	//获取监视器对象
	private Condition con=lock.newCondition();
	
	public void set(String name,String sex) 
	{
		
		lock.lock();
		try {
		while(flag)
			try{con.await();}catch(InterruptedException e){}
		this.name=name;
		this.sex=sex;
		
		flag=true;
		con.signal();}
		finally {
		lock.unlock();}
	}
	
	public void out() 
	{
		lock.lock();
		try {
		while(!flag)
			try{con.await();}catch(InterruptedException e){}
		System.out.println(name+"..."+sex);
		
		flag=false;
		con.signal();}
		finally {
		lock.unlock();}
	}
}

多线程_线程停止

如何停止线程?

原理:让run方法结束。
线程任务通常都有循环。因为开启线程就是为了执行需要一些时间的代码。

只要控制住循环,就可以结束run方法,就可以停止线程。

控制循环弄个标记即可。定义变量。

基础版
(去掉wait()部分即可,结果中有Thread-0或者Thread-1的运行结果),加上try{wait()}部分后,t1和t2都被冻结。

class StopThread implements Runnable
{
	private boolean flag=true;
	public synchronized void run() 
	{
		while(flag) 
		{
			try {
				wait();//t1 t2
				}
			catch(InterruptedException e) 
			{
				System.out.println(Thread.currentThread().getName()+"..."+e.toString());
			}
			System.out.println(Thread.currentThread().getName()+"...hello");
		}
	}
	public void changeFlag() 
	{
		flag=false;
	}
}
class ExtendsDemo3 
{    
	public static void main(String[] args) throws Exception
	{   
		StopThread st=new StopThread();	
		Thread t1=new Thread(st);
		Thread t2=new Thread(st);
		t1.start();
		t2.start();
		for(int x=0;x<=50;x++) 
		{
			if(x==40) 
			{
				st.changeFlag();
			}
			System.out.println("main..."+x);
		}
		System.out.println("over");
	}
}

结果:

main...0
main...1
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
main...10
main...11
main...12
main...13
main...14
main...15
main...16
main...17
main...18
main...19
main...20
main...21
main...22
main...23
main...24
main...25
main...26
main...27
main...28
main...29
main...30
main...31
main...32
main...33
main...34
main...35
main...36
main...37
main...38
main...39
main...40
main...41
main...42
main...43
main...44
main...45
main...46
main...47
main...48
main...49
main...50
over

st.changeFlag();
改为

t1.interrupt();//将t1中断
t2.interrupt();//将t2中断

也相当于在System.out.println(Thread.currentThread().getName()+"..."+e.toString());后加flag=false

然后这样的结果为:

main...0
main...1
main...2
main...3
main...4
main...5
main...6
main...7
main...8
main...9
main...10
main...11
main...12
main...13
main...14
main...15
main...16
main...17
main...18
main...19
main...20
main...21
main...22
main...23
main...24
main...25
main...26
main...27
main...28
main...29
main...30
main...31
main...32
main...33
main...34
main...35
main...36
main...37
main...38
main...39
main...40
main...41
main...42
main...43
main...44
main...45
main...46
main...47
main...48
main...49
main...50
over
Thread-0...java.lang.InterruptedException
Thread-0...hello
Thread-1...java.lang.InterruptedException
Thread-1...hello

因为interrupt的存在可以清除wait(),wait()无法执行就会catch到这个异常。也就是强行将线程从冻结中唤醒,然后执行异常的catch部分。下面的程序也就可以继续执行下去。

sleep()也可以,因为wait()和sleep()都被释放资格和执行权,如果没有其余合法的唤醒方式,interupt可以让他们重新获取到执行权。

守护线程

(后台线程)
前台线程和后台线程在执行中没有区别,在结束时有区别。
没有定义后台线程的执行方式时,它会随着前台线程的结束而结束。

在java.lang中的Thread的方法摘要中。

public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。

该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。

on - 如果为 true,则将该线程标记为守护线程。

将上节第二段程序改为:

class ExtendsDemo3 
{    
	public static void main(String[] args) throws Exception
	{   
		StopThread st=new StopThread();	
		Thread t1=new Thread(st);
		Thread t2=new Thread(st);
		t1.start();
		t2.setDaemon(true);//将t2标记为后台线程。(守护线程)
		for(int x=0;x<=50;x++) 
		{
			if(x==40) 
			{
				//st.changeFlag();
				t1.interrupt();//将t1中断
				//t2.interrupt();//将t2中断
			}
			System.out.println("main..."+x);
		}
		System.out.println("over");
	}
}

当t1(前台线程)的冻结被打断后,继续运行到真正结束,t2(后台线程)也跟随着结束。

垃圾回收线程就是后台线程中的一种。

Join方法

public final void join()
                throws InterruptedException

等待该线程终止。
抛出:
InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的 中断状态 被清除。

join()的规律具体看这里

class Demo implements Runnable
{
	public void run() 
	{
		for(int x=0;x<40;x++) 
		{
			System.out.println(Thread.currentThread().getName()+"..."+x);
		}
	}
}
class StopThreadDemo
{    
	public static void main(String[] args) throws InterruptedException
	{   
		Demo d=new Demo();
		Thread t1=new Thread(d);
		Thread t2=new Thread(d);
		t1.start();
		t1.join();//等待该线程终止。
		t2.start();
		for(int x=1;x<=40;x++) 
		{
			System.out.println("main..."+x);
		}
	}
}

结果:先执行完Thread-0的程序。

如果把join()放在另一个线程的下面。

class StopThreadDemo 
{    
	public static void main(String[] args) throws InterruptedException
	{   
		Demo d=new Demo();
		Thread t1=new Thread(d);
		Thread t2=new Thread(d);
		t1.start();
		t2.start();
		t1.join();//等待该线程终止。
		
		for(int x=1;x<=40;x++) 
		{
			System.out.println("main..."+x);
		}
	}
}

结果:Thread-0和Thread-1共同抢资源。Thread-0和join容器息息相关,Thread-0结束,即容器死亡,主线程死亡。b随便。

多线程_优先级

1.优先级
先被切到的几率更大

toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

如果把System.out.println(Thread.currentThread().getName()...);改为System.out.println(Thread.currentThread().toString()...);

结果如:

Thread[Thread-1,5,main]...38

优先级:数字越大,优先级越高(1~10)
5是默认优先级。

setPriority(int newPriority):更改线程的优先级。

JAVA笔记__多线程(Day 13~15)_第20张图片

例如:

class StopThreadDemo 
{    
	public static void main(String[] args) throws InterruptedException
	{   
		Demo d=new Demo();
		Thread t1=new Thread(d);
		Thread t2=new Thread(d);
		t1.start();
		t2.start();
		t1.setPriority(Thread.MAX_PRIORITY);
		for(int x=1;x<=40;x++) 
		{
			System.out.println("main..."+x);
		}
	}
}

2.yield()暂停当前正在执行的线程对象,并执行其他线程。
把第一段改为

class Demo implements Runnable
{
	public void run() 
	{
		for(int x=0;x<40;x++) 
		{
			System.out.println(Thread.currentThread().toString()+"..."+x);
			Thread.yield();
		}
	}
}

结果会分配得很杂乱,因为切换到一个线程,该现象就会很快被临时暂停,然后就会切换到其他线程。

3.Thread(ThreadGroup group, Runnable target, String name, long stackSize)
分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。

线程组的中断

多线程_常见写法

第一种:

class ExtendsDemo3 
{    
	public static void main(String[] args) 
	{   
		new Thread() //Thread-0
		{
			public void run() 
			{
				for(int x=1;x<=50;x++) 
				{
					System.out.println("x="+x);
				}
			}
		}.start();
		
		
		Runnable r=new Runnable() //Thread-1
		{
			public void run() 
			{
				for(int x=1;x<=50;x++) 
				{
					System.out.println("y="+x);
				}
			}
		};
		new Thread(r).start();
		//Thread t=new Thread(r).start();
		//t.join();//等待该线程终止。
		
		for(int x=1;x<=50;x++) //主线程
		{
			System.out.println("z="+x);
		}
	}
}

第二种(稍有难度):

class ExtendsDemo3 
{    
	public static void main(String[] args) 
	{   
		//-----------稍有难度-------------------结果是什么?
		new Thread(new Runnable() 
		{
			public void run() 
			{
				System.out.println("runnable run......");
			}
		}) 
		{
			public void run() 
			{
				System.out.println("subThread...run...");
			}
		}.start();
	}
}

结果:

subThread...run...

因为子类覆盖了父类。

你可能感兴趣的:(JAVA)