(高级基础)12_多线程

一、基本概念: 程序、进程、线程

①程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
②进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
③线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间—>它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来
安全的隐患

在这里插入图片描述
在这里插入图片描述

1单核CPU和多核CPU的理解

1.单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。 
2.如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
3.一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

2并行与并发

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。 
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

3使用多线程的优点

背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

4何时需要多线程

1.程序需要同时执行两个或多个任务。 
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。 
3.需要一些后台运行的程序时。

证明JVM是多线程的:

public class ThreadDemo1{
	public static void main(String[] args) {
		
		 for(int i = 0 ; i < 10000000 ; i++) {
			new Demo();
		 }
		 for(int i = 0 ; i < 10000000 ;i++) {
			 System.out.println("---------------------"+i);
		 }
	}
	
}

class  Demo{
	
	@Override
	protected void finalize() throws Throwable {
		 System.out.println("执行垃圾回收......");
	}
}

在这里插入图片描述

二、线程的创建和使用

1.线程的创建和启动

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的==start()==方法来启动这个线程,而非直接调用run()

2.Thread类

构造器

Thread():创建新的Thread对象 
Thread(String threadname):创建线程并指定线程实例名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法 
Thread(Runnable target, String name):创建新的Thread对象

3.创建线程两种方式

==1 继承Thread ==

1) 定义子类继承Thread类。 
2) 子类中重写Thread类中的run方法。 
3) 创建Thread子类对象,即创建了线程对象。 
4) 调用线程对象start方法:启动线程,调用run方法

2 实现Runnable接口

1)定义子类,实现Runnable接口。 
2) 子类中重写Runnable接口中的run方法。 
3) 通过Thread类含参构造器创建线程对象。 
4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。 
5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。

第一种方法(外部类):
**方式一:继承Thread类 **

public class MyThread1  extends Thread{
	
	@Override
	public void run() {
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("自定义线程执行-----------------------"+i);
		}
	}
}

测试类

public class TestThread {
	public static void main(String[] args) {
		Thread t = new MyThread1();
		t.start();//启动线程
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("主线程执行****************************************"+i);
		}
	}
}

在这里插入图片描述

第二种方式:实现Runnable接口

public class MyThread2  implements Runnable{

	@Override
	public void run() {
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("Runable线程执行+++"+i);
		}
		
	}

}

测试

public class TestThread {
	public static void main(String[] args) {
		Thread t = new MyThread1();
		t.start();//启动线程
		
		Runnable r = new MyThread2();
		Thread t2 = new Thread(r);
		t2.start();//启动第二个线程
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("主线程执行****************************************"+i);
		}

	}
}

在这里插入图片描述

线程创建的两种方式的区别:
1 继承Thread类 当前类就是一个真正的线程类
2 实现Runnable接口 实现类本身并不是一个线程类 他只是拥有线程可执行的任务 创建线程 还需要将该可执行任务传递给Thread 类
3 对于线程 必须调用其start方法 否则 该线程是不会执行的
4 如果直接调用线程类的run方法 此时程序不是多线程的 只是普通方法的调用
5 一个线程一旦结束 就不能再次启动 (一个线程只能启动一次) 如果二次启动线程则会出现:“IllegalThreadStateException”
6 如果继承Thread类 但是Java中的继承是单继承

Runnable接口 通过实现的方式 Java实现是多实现
第二种写法:(内部类)

public class TestThread {
	public static void main(String[] args) {
		Thread t1 = new TestThread().new  MyThread3();
		t1.start();
		Thread t2 = new Thread(new TestThread().new MyThread4());
		t2.start();
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("主线程执行****************************************"+i);
		}
	//	t.start();
	}
	
	class MyThread3 extends Thread {
		@Override
		public void run() {
			for(int i = 0 ; i < 10000;i++) {
				System.out.println("Thread线程执行-----------------------"+i);
			}
		}
	}
	class MyThread4 implements Runnable{

		@Override
		public void run() {
			for(int i = 0 ; i < 10000;i++) {
				System.out.println("Runable线程执行+++"+i);
			}
			
		}
		
	}
}

第三种写法:(匿名内部类)

public class TestThread {
	public static void main(String[] args) {
		  new Thread() {
			@Override
			public void run() {
				for(int i = 0 ; i < 10000;i++) {
					System.out.println("Thread线程执行-----------------------"+i);
				}
			}
		}.start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 0 ; i < 10000;i++) {
					System.out.println("Runable线程执行+++"+i);
				}
			}
		}).start();
		for(int i = 0 ; i < 10000;i++) {
			System.out.println("主线程执行****************************************"+i);
		}
	}

}

4.线程中的常用方法:

void start(): 启动线程,并执行对象的run()方法 
run(): 线程在被调度时执行的操作 
String getName(): 返回线程的名称 
void setName(String name):设置该线程名称 
static Thread currentThread(): 返回当前线程。
             在Thread子类中就 是this,通常用于主线程和Runnable实现类
static void yield():线程让步 
    ⚪暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 
    ⚪若队列中没有同优先级的线程,忽略此方法 
join() :当某个程序执行流中调用其他线程的 join() 方法时,
调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 
    ⚪低优先级的线程也可以获得执行 
static void sleep(long millis):(指定时间:毫秒) 
    ⚪令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,
    时间到后 重排队。 
    ⚪抛出InterruptedException异常 
stop(): 强制线程生命期结束,不推荐使用 
boolean isAlive():返回boolean,判断线程是否还活着

获取当前线程并获取线程的名称

Thread.currentThread().getName()

设置线程名称(两种方式):
在这里插入图片描述

setName(String name)

t1.setName("线程1");
		t1.start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 0 ; i < 10000;i++) {
					System.out.println("Runable线程执行"+Thread.currentThread().getName()+"++++"+i);
				}
			}
		},"线程2").start();

礼让线程:
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
⚪若队列中没有同优先级的线程,忽略此方法

public void run() {
				for(int i = 0 ; i < 1000;i++) {
					if(i == 800) {
						System.out.println("Thread线程执行"+Thread.currentThread().getName()+"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"+i);
						Thread.currentThread().yield();
						
					}
					System.out.println("Thread线程执行"+Thread.currentThread().getName()+"-----------------------"+i);
				}
			}
public static void main(String[] args) {
		Thread t1 = new Thread() {
			@Override
			public void run() {
			 for(int i = 0 ; i < 100; i++) {
			
				 System.out.println(Thread.currentThread().getName() +"-------"+i);
			 }
			}
		};
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 0 ; i < 100; i++) {
					 System.out.println(Thread.currentThread().getName() +"*****************"+i);
					 if(i == 60) {
						 try {
							t1.join();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					 }
					
				 }
				
			}
		});
		t1.start();
		t2.start();
	}

join 加入线程 在t2线程中调用t1.join() 则在此条件下 会使 的t2线程处于阻塞状态 直到t1线程执行结束 t2 才会继续执行。
yield()礼让线程 表示暂时愿意让出CPU的执行权 当前线程进入到就绪状态 和其他线程一起再次来抢夺CPU的执行权

yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。yield()做的是让当前运行线程回到就绪状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

5.线程的调度

调度策略:
1.时间片
在这里插入图片描述
2.抢占式:高优先级的线程抢占CPU
Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略 
对高优先级,使用优先调度的抢占式策略

6.线程的优先级

线程的优先级等级:

MAX_PRIORITY:10 
MIN _PRIORITY:1 
NORM_PRIORITY:5

涉及的方法:

getPriority() :返回线程优先值 
setPriority(int newPriority) :改变线程的优先级 

说明:
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

7.线程的分类

Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出

(形象理解:兔死狗烹,鸟尽弓藏)

setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
当on =true的时候 是守护线程 on=false 则为用户线程 默认为false
线程执行结束 并不表示该线程立即死亡
守护线程 是伴随着用户线程的存活而存活 当用用户线程死亡的时候 守护线程不论是否执行结束 都会死亡

三、线程的生命周期

线程的五种状态:
①新建: 当一个Thread类或其子类的对象被声明并创建(new Thread )时,新生的线程对象处于新建状态
**②就绪:**处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
**③运行:**当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
④阻塞: 线程在执行过程中,因为某些原因 失去了CPU的执行权 暂时被挂起或执行输入输出操作 但是线程还没有结束 则线程就处于阻塞状态
**⑤死亡:**线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束,则线程进入到死亡状态

线程的五种状态时可以相互转换的
在这里插入图片描述

四、线程同步

使用多线程模拟火车站售票程序,开启三个窗口售票。

public class Tick implements Runnable{
	private  static int  num = 100;
	@Override
	public void run() {
		 while(true) {
			 if(num > 0) {
				 try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				 System.out.println(Thread.currentThread().getName()+"销售车票座位号为:" + num--); 
			 }else {
				 break;
			 }
			
		 }
	}

}
//此时创建三个线程 表示三个售票窗口
public class SaleTicket {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Tick(),"1号窗口");
		Thread t2 = new Thread(new Tick(),"2号窗口");
		Thread t3 = new Thread(new Tick(),"3号窗口");
		Thread t4 = new Thread(new Tick(),"4号窗口");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
  1. 多线程出现了安全问题
    2.问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
    3.解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

1.Synchronized的使用方法

Java对于多线程的安全问题提供了专业的解决方式:同步机制
如何实现线程同步:

 1 同步方法
 2 同步代码块

Synchronized java对于多线程的安全问题提供了专业的解决方式:同步机制

使用同步代码块来实现线程同步

public static void main(String[] args) {
		Object obj = new Object();
		Thread t1 = new Thread() {
			public void run() {
				while(true) {
				synchronized(obj){
						System.out.print("中");
						System.out.print("北");
						System.out.print("大");
						System.out.print("学");
						System.out.println();
					}
				}
				
				
			};
		};
		
		Thread  t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
				synchronized (obj) {//synchronized (同步锁)
				
						System.out.print("蓝");
						System.out.print("桥");
						System.out.print("软");
						System.out.print("件");
						System.out.print("学");
						System.out.print("院");
						System.out.println();
					}
				}
			}
		});
		
		t1.start();
		t2.start();
	}
}

在同步代码块中 同步锁/线程锁由谁来担当?

在同步代码块中 锁是一个对象   而且可以是任意对象
在同步代码块中 锁对象必须是同一个对象 
也可以使用本类的字节码文件对象

静态方法的同步代码块的锁对象:

可以是任意的静态对象都可以  必须是同一个对象
可以使用本类的字节码文件对象  

什么时候可以使用this来作为同步锁:

public class SynchronizedTest {
	//static String s = "aa";
	
	public static void main(String[] args) {
		SynchronizedTest st = new SynchronizedTest();
		st.show1();
		st.show2();
	}
	
	public  void show1() {//同步方法
		 new Thread() {
			public void run() {
				while(true) {
						print1();
					}
			
			};
		}.start();
	}
	public void show2() {
	  new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
						print2();
				}
			}
		}).start();
	}
	
	public  void print1() {
		synchronized (this) {
			System.out.print("中");
			System.out.print("北");
			System.out.print("大");
			System.out.print("学");
			System.out.println();
		}
		
	}
	public   void print2() {
		synchronized (this) {
			System.out.print("蓝");
			System.out.print("桥");
			System.out.print("软");
			System.out.print("件");
			System.out.print("学");
			System.out.print("院");
			System.out.println();
		}
		
	}
}

同步方法
①同步方法中的锁对象是谁?

public synchronized void print1() {

			System.out.print("中");
			System.out.print("北");
			System.out.print("大");
			System.out.print("学");
			System.out.println();
		
		
	}
	public  void print2() {
			synchronized (this) {
				System.out.print("蓝");
				System.out.print("桥");
				System.out.print("软");
				System.out.print("件");
				System.out.print("学");
				System.out.print("院");
				System.out.println();
			}
			
		
		
	}

通过上述代码 证明同步方法中的锁对象是this
②静态同步方法的锁对象是谁?

public static  synchronized void print1() {

			System.out.print("中");
			System.out.print("北");
			System.out.print("大");
			System.out.print("学");
			System.out.println();
		
		
	}
	public static   void print2() {
			synchronized (SynchronizedTest.class) {
				System.out.print("蓝");
				System.out.print("桥");
				System.out.print("软");
				System.out.print("件");
				System.out.print("学");
				System.out.print("院");
				System.out.println();
			}
			
		
		
	}

通过上述代码 证明静态同步方法中的锁对象是本类的字节码对象

总结:
1 什么时候使用同步?
当多个线程操作共享数据时 就需要线程同步。
2 线程同步的方式:同步代码块 同步方法 可以是静态也可以是非静态
3 同步代码块的锁对象?
当在非静态方法中 对存在线程安全问题的代码实行同步策略 此时的锁对象可以是任意对象 只需要保证所有的线程使用的是同一个锁对象 this也可以充当锁对象
在静态代码块中 可以使用静态对象来作为锁对象 保证所有的线程使用的是同一个锁对象
4 同步方法的锁对象?
对于非静态方法 锁对象为this
对于静态方法 字节码对象 本类的class对象

2.线程的死锁问题

线程中需要避免的问题:死锁

public static void main(String[] args) {
		String lock1 = "aaa";
		String lock2 = "bbb";
	 new Thread() {
			@Override
			public void run() {
				while(true) {
					synchronized (lock1) {
						System.out.println("AAAA");
						synchronized (lock2) {
							System.out.println("BBBB");
						}
					}
				}
			}
		}.start();
		
		new Thread() {
			@Override
			public void run() {
				while(true) {
					synchronized (lock2) {
						System.out.println("CCCCCCCCCCCCCCCCCCCCC");
						synchronized (lock1) {
							System.out.println("DDDDDDDDDDDDDDDDDDDD");
						}
					}
				}
			}
		}.start();
	}

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁 (未能释放锁)
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

解决方法

专门的算法、原则 
尽量减少同步资源的定义 
尽量避免嵌套同步

死锁是如何出现的:死锁的出现是因为锁的嵌套
在开发中 应尽量避免死锁的出现 应尽量不要使用锁的嵌套

3.锁的释放

锁在什么时候会被释放?

1 同步的代码执行结束
2 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。 
3当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导 致异常结束。 
4当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。

②不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行 
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。 
应尽量避免使用suspend()和resume()来控制线程

4.单例设计模式

  1 将构造器私有化
  2 提供一个该类的静态变量(类型为当前类)
  3 提供对外获取该类实例对象的方法

懒汉式的线程安全:

//懒汉式  是否存在线程问题  存在
//如何解决  使用线程同步
public class Singleton2 {
private static Singleton2 instance = null;
	
	private Singleton2() {	
	}
	public static Singleton2 getInstance() {//若不加锁 多个线程都能访问 创建对象
		synchronized (Singleton2.class) {
				if(instance == null) {
				instance = new Singleton2();
		}
			
	 }
		return instance;
	}
}

饿汉式:

// 饿汉式 是否存在线程安全问题  不存在
public class Singleton1 {
	private static Singleton1 instance = new Singleton1();
	
	private Singleton1() {	
	}
	public static Singleton1 getInstance() {
		return instance;
	}
}

5.定时任务

public class Timer extends Object
线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。

在这里插入图片描述

public static void main(String[] args) throws ParseException {
		// 创建定时器
		Timer  timer = new Timer("timer1");
		// 使用定时器执行计划任务
		timer.schedule(new TimerTask() {
			//需要执行的任务
			@Override
			public void run() {
				 System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
				
			}
		}, new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse("2019-11-28 19:56:12"), 1000);
	}
public static void main(String[] args) throws ParseException {
		// 创建定时器
		Timer  timer = new Timer("timer1");
		// 使用定时器执行计划任务
		timer.schedule(new TimerTask() {
			//需要执行的任务
			@Override
			public void run() {
				 System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
				
			}
		}, //new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse("2019-11-28 19:56:12"), 1000
		5000,1000);
		
	}

五 、线程的通信

1.什么时候需要通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的。如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印

2.怎么通信

 如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

3.wait() 与 notify() 和 notifyAll()

wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。

//实现两个线程的交替执行
	/*线程间的通信
	 * wait/wait(long timeout))
	 * notify()  如果由多个线程  则随机的唤醒某一个线程
	 * notifyAll()唤醒所有处于等待的线程
	 */

	public static void main(String[] args) {
		ThreadNotify t = new ThreadNotify();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
					t.print2();
				}
				
				
			}
		}).start() ;
		new Thread() {
			@Override
			public void run() {
				while(true) {
					
					t.print1();
				}
				 
			}
		}.start();
	
	}
	
	
	public synchronized void print1() {
		notifyAll();
		System.out.print("中");
		System.out.print("北");
		System.out.print("大");
		System.out.print("学");
		System.out.println();
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public  synchronized void print2() {
		notifyAll();
		System.out.print("蓝");
		System.out.print("桥");
		System.out.print("软");
		System.out.print("件");
		System.out.print("学");
		System.out.print("院");
		System.out.println();
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

4.互斥锁

三个或三个以上间的线程通信
◦多个线程通信的问题
◦notify()方法是随机唤醒一个线程
◦notifyAll()方法是唤醒所有线程
◦JDK5之前无法唤醒指定的一个线程
◦如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
JDK1.5的新特性互斥锁
所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作。
Lock和synchronized机制的主要区别:
synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放. 而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能.

• 1.同步
◦使用ReentrantLock类的lock()和unlock()方法进行同步
•2.通信
◦使用ReentrantLock类的newCondition()方法可以获取Condition对象
◦需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
◦不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线

public class ThreadNotify {

	public static void main(String[] args) {
		ThreadNotify t = new ThreadNotify();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
					t.print2();
				}
				
				
			}
		}).start() ;
		new Thread() {
			@Override
			public void run() {
				while(true) {
					
					t.print1();
				}
				 
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				while(true) {
					
					t.print3();
				}
				 
			}
		}.start();
	
	}
	
	ReentrantLock lock = new ReentrantLock();//锁对象
	Condition c1 = lock.newCondition();//通信环境
	Condition c2 = lock.newCondition();
	Condition c3 = lock.newCondition();
	int flag = 1 ;
	public void print1() {
		lock.lock();//对当前代码上锁
		if(flag == 1) {
			
			System.out.print("中");
			System.out.print("北");
			System.out.print("大");
			System.out.print("学");
			System.out.println();
			try {
				c1.await();
				flag = 2;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(flag == 2) {
			c2.signal();
		}
		lock.unlock();
		
		
	}
	public   void print2() {
		lock.lock();
		if(flag == 2) {
			System.out.print("蓝");
			System.out.print("桥");
			System.out.print("软");
			System.out.print("件");
			System.out.print("学");
			System.out.print("院");
			System.out.println();
			try {
				c2.await();
				flag = 3;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			
		}
		if(flag == 3) {
			c3.signal();
		}
		lock.unlock();
	}
	public  void print3() {
		lock.lock();
		if(flag == 3) {
			System.out.print("山");
			System.out.print("西");
			System.out.print("太");
			System.out.print("原");
			System.out.println();
			try {
				c3.await();
				flag = 1;
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		if(flag == 1) {
			c1.signal();
		}
		lock.unlock();
		
	}
}

5.生产者消费者模型

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。 

这里可能出现两个问题:

生产者比消费者快时,消费者会漏掉一些数据没有取到。 
消费者比生产者快时,消费者会取相同的数据。
public class Clerk {//店员
	private  int product = 0;
	
	public synchronized void addProduct() {
		if(product >= 20 ) {
			try {
				wait();//当产品数量不小于20 则生产暂停
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {//当产品数量小于20  则继续生成
			product++;
			System.out.println("生产者生产了第" + product+"个产品");
			notify();
		}
	}
	
public synchronized  void  getProduct() {
		if(product <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {	
			
			System.out.println("消费者消费了第" + product+"个产品");
			product--;
		
			notify();
		}	
	}		
}
public class Productor  implements Runnable{
	private  Clerk clerk;
	public  Productor(Clerk clerk) {
		this.clerk = clerk;
	}
	@Override
	public void run() {
		
		System.out.println("生产者开始生产。。。。。。。。。。。。。");
		while(true) {
			try {
				Thread.sleep((long)Math.random() * 1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}
public class Customer implements Runnable {
	private Clerk clerk;
		
	public Customer(Clerk clerk) {
		this.clerk = clerk;
	}
	@Override
	public void run() {
	 System.out.println("消费者可以消费了****************");
		while(true) {
			
			try {
				Thread.sleep((long)Math.random()* 1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.getProduct();
		}
		
	}

}
public class Test {
	public static void main(String[] args) {
		Clerk clerk = new Clerk();
		Thread t1 = new Thread(new Productor(clerk));
		Thread t2 = new Thread(new Customer(clerk));
		t1.start();
		t2.start();
	}
}

生产者消费者模型的作用是什么
这个问题很理论,但是很重要:
(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

小结:

线程间的通信:
一种:synchronized机制  需要借助于Object类提供的wait和nofity/notifyAll
二种:互斥锁 Lock机制 需要借助于 通过lock得到的Condition对象  来使用其中的await/singal方法
lock中lock以及unlock  来上锁和释放锁
典型模式:生产者消费者模式
线程第三种创建方法 :线程池

六、JDK5.0新增线程创建方式

①我们在传统多线程编程创建线程时,常常是创建一些Runnable对象,然后创建对应的Thread对象执行它们,但是如果程序需要并发执行大量的任务时,需要为每个任务都创建一个Thread,进行管理,这将会影响程序的执行效率,并且创建线程过多将会使系统负载过重。
②在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的APIExecutor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外。

1.为什么引入Executor线程池框架

new Thread()的缺点:

每次new Thread()耗费性能
调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
不利于扩展,比如如定时执行、定期执行、线程中断

采用线程池的优点

重用存在的线程,减少对象创建、消亡的开销,性能佳
可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
提供定时执行、定期执行、单线程、并发数控制等功能

2.Executor框架

Executor框架集对线程调度进行了封装,将任务提交和任务执行解耦。它提供了线程生命周期调度的所有方法,大大简化了线程调度和同步的门槛。
在这里插入图片描述

①Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。
②ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法。比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorServiceshutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将关闭ExecutorService。因此我们一般用该接口来实现和管理多线程
1.AbstractExecutorService:ExecutorService执行方法的默认实现
2.ScheduledExecutorService:一个可定时调度任务的接口
3.ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池
4.ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个
ExecutorService
对象Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现ExecutorService接口。
在这里插入图片描述
public static ExecutorService newFixedThreadPool(int nThreads)

创建固定数目线程的线程池。以共享的无界队列方式来运行这些线程。当所有线程都处于活动状态,再次提交的任务都会加入队列等到其他线程运行结束,当线程处于空闲状态时会被下一个任务复用

在这里插入图片描述
public static ExecutorService newCachedThreadPool()

创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线  程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

在这里插入图片描述
public static ExecutorService newSingleThreadExecutor()

  创建一个单线程化的Executor。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

在这里插入图片描述
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。newScheduledThreadPool是延时一定时间之后才执行
public static void main(String[] args) {
		/*
		 *  创建ExecutorService的方式:
		 *  static ExecutorService newFixedThreadPool(int nThreads) 
			创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。  
		 */
		ExecutorService service = Executors.newFixedThreadPool(5);
		for(int i = 0 ; i < 12;i++) {
			Runnable task = new Runnable() {
				@Override
				public void run() {
				 System.out.println(Thread.currentThread().getName());	
				}
			};
			service.execute(task);
		}
		
	}
public static void main(String[] args) {
		/*
		 *  创建ExecutorService的方式:
		 *   public static ExecutorService newCachedThreadPool()创建一个可缓存的线程池,
		 *   调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,
		 *   则创建一个新线  程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。。  
		 */
		ExecutorService service = Executors.newCachedThreadPool();
		for(int i = 0 ; i < 12;i++) {
			Runnable task = new Runnable() {
				@Override
				public void run() {
				 System.out.println(Thread.currentThread().getName());	
				}
			};
			service.execute(task);
		}
		service.shutdown();
	}
public static void main(String[] args) {
		/*
		 *  创建ExecutorService的方式:  
		 */
		ExecutorService service = Executors.newSingleThreadExecutor();
		for(int i = 0 ; i < 12;i++) {
			Runnable task = new Runnable() {
				@Override
				public void run() {
				 System.out.println(Thread.currentThread().getName());	
				}
			};
			service.execute(task);
		}
		service.shutdown();
	}
public class ExecutorPoolTest {
	public static void main(String[] args) {
		/*
		 *  创建ExecutorService的方式:  
		 */
		ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
		for(int i = 0 ; i < 12;i++) {
			Runnable task = new Runnable() {
				@Override
				public void run() {
				 System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));	
				}
			};
			service.scheduleAtFixedRate(task, 3000,1000, TimeUnit.MILLISECONDS);
		}
		
	}
}

这四种方法都是用的Executors中的ThreadFactory建立的线程,
一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。(该段话摘自《Thinking
in Java》第四版)

3.Executor执行Runnable任务:

通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上执行。
execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。
在这里插入图片描述

4.Executor执行Callable任务:

Java 5之后,任务分两类:==一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。==并且Callablecall()方法只能通过ExecutorServicesubmit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future
Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable有返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnablerun()方法,区别同样是有返回值,后者没有。
当将一个
Callable
的对象传递给ExecutorServicesubmit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorServicesubmit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null
在这里插入图片描述

public class CollableTask {
	public static void main(String[] args) {
		//创建ExecutorService
		ExecutorService service = Executors.newFixedThreadPool(5);
		List<Future<String>> resList = new ArrayList<>();
		for(int i = 0 ; i < 20 ;i++) {
			Future<String> result = service.submit(new Task(i));
			resList.add(result);
		}
		
		for(Future<String> f : resList) {
			while(!f.isDone()) {}//如果任务没有完成  则等待其完成
				try {
					String done = f.get();
					System.out.println(done);
				} catch (InterruptedException | ExecutionException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}finally {
					service.shutdown();
				}
				
			
		}
		
	}
	
}

class Task implements Callable<String>{
	private int id;
	public Task(int id) {
		this.id = id;
	}
	
	@Override
	public String call() throws Exception {
		 
		return "id="+id+"***的任务被执行......"+Thread.currentThread().getName();
	}
	
}

从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

5.Executor的生命周期

ExecutorService的生命周期包括三种状态:运行、关闭、终止
创建后便进入运行状态。
当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。
shutdownNow方法将强制终止所有运行中的任务并不再允许提交新任务

总结:

1 了解进程  线程  CPU核  并发 并行  
2 线程的创建方式 :Thread  Runable
3 线程的一些常用方法:run  start  sleep  join  yield
4 线程的调度方式:时间片   抢占式
同优先级 时间片
不同优先级  抢占式
5 线程的优先级: 1--10  默认5  线程的优先级:决定的线程获得CPU的几率
6 线程的生命周期:新建  就绪 运行 阻塞 死亡
7 线程同步 什么时候需要线程同步  线程同步的方式  同步锁   死锁
8 线程间的通信    互斥锁
9 线程池框架  
10 线程池的生命周期
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值