Skip to content

多线程

多线程

多线程是提升程序性能⾮常重要的⼀种⽅式,使⽤多线程可以让程序充分利⽤CPU资源,提⾼CPU的 使⽤效率,从⽽到达解决⾼并发所带来的负载均衡的问题。

1.多线程的优点:

1.CPU资源得到更合理的利⽤。 2.程序设计更加简洁。 3.程序响应更快,运⾏效率更⾼。 多线程的缺点: 1.需要更多的内存空间来⽀持多线程。 2.多线程并发访问的情况可能会影响数据的准确性。 3.⼀个资源被多个线程共享时,可能会出现死锁的情况。

2.线程与进程

(1)进程

计算机在某个数据集合上进⾏的⼀次运⾏活动,是系统进⾏资源分配和调度的基本单位。

简单理解,进程就是计算机正在运⾏的⼀个具体的应⽤程序,⼀个应⽤程序⾄少有⼀个进程,也可以 是多个进程。

(2)线程

线程是程序执⾏的最⼩单元,线程是进程中的⼀个基本单位,为独⽴完成程序中的某⼀个功能⽽存在 的,⼀个进程是由⼀个或多个线程组成的。 进程和线程是应⽤程序在执⾏过程中所产⽣的概念,即如果⼀个应⽤程序没有运⾏,就没有线程和进 程的概念,应⽤程序是⼀个静态概念,进程和线程是动态概念,只有当静态的应⽤程序运⾏起来,才 会产⽣对应的动态的进程和线程,有创建有销毁,存在也是暂时的,不可能永远存在。

(3)进程和线程的区别

进程在运⾏时拥有独⽴的内存空间,即每个进程所占⽤的内存都是独⽴的,互不⼲扰。⽽多线程是共 享内存空间的,线程不能独⽴执⾏,必须依赖与进程,由进程提供多线程的执⾏控制。 我们通常所说的多线程是指在⼀个进程中,多个线程同时执⾏,注意:这⾥的同时执⾏并不是真正意 义上的同时执⾏,系统会⾃动为每⼀个线程分配CPU资源,在某⼀个具体的时间段内CPU被⼀个线程 所占⽤,不同的时间段内不同的线程占⽤CPU资源,所以多线程实际上是多个线程在交替执⾏,CPU 运⾏速度很快,感觉上是同时在执⾏。

例1:单线程

java
public class Test {
	//主线程
	public static void main(String[] args) {
		//主线程的业务逻辑
		for (int i = 0; i < 100; i++) {
			System.out.println("test+++++++++++++++");
		}

    Test2 test2 = new Test2();
    test2.test();

    // test+++++++++++++++
    // ...
    // -------------test
    // ...
	}
}
java
public class Test2 {
	public void test() {
		for (int i = 0; i < 100; i++) {
			System.out.println("-------------test");
		}
	}
}

例2:多线程

java
package com.southwind.test;

public class Test {
	//主线程
	public static void main(String[] args) {
		//开启第一个子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				//子线程的业务逻辑
				for (int i = 0; i < 100; i++) {
					System.out.println("-----------test");
				}
			}
		}).start();
		
		//开启第二个子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				//子线程的业务逻辑
				for (int i = 0; i < 100; i++) {
					System.out.println("========test==========");
				}
			}
		}).start();
		
		//主线程的业务逻辑
		for (int i = 0; i < 100; i++) {
			System.out.println("test+++++++++++++++");
		}
	}
}

3.Java使⽤线程(两种方式)

(1)继承Thread类

1.⾃定义⼀个类,继承Thread类。 2.重写Thread类中的run⽅法,将相应的业务逻辑在run⽅法中实现。 定义好了⼀个线程类之后,我们就可以通过该类来实例化对象,对象就可以描述⼀个线程。 实例化该对象之后,必须通过调⽤start()来开启该线程,这样该线程才会和其他线程来抢占CPU资 源,不能调⽤run()⽅法,调⽤run()相当于普通的实例对象⽅法调⽤,并不是多线程。

java
public class MyThread extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i = 0; i < 100; i++) {
			System.out.println("MyThread+++++++++++++++");
		}
	}
}
java
public class Test3 {
	//主线程
	public static void main(String[] args) {
		//创建一个MyThread线程,子线程
		MyThread myThread = new MyThread();
		//开启线程,使用start()方法,如果直接使用run()方法,如myThread.run()相当于普通的实例对象⽅法调⽤,并不是多线程。
		myThread.start();
		
		//主线程的业务逻辑
		for(int i = 0; i < 100; i++) {
			System.out.println("----------------Test3");
		}
	}
}

(2)实现Runnable接⼝

1.⾃定义⼀个类,实现Runnable接⼝。 2.实现run⽅法。 MyRunnable的使⽤与MyThread略有不同,MyRunnable相当于定义了线程的业务逻辑,但是 它本身不是⼀个线程,所以需要实例化Thread类的对象作为线程,然后将MyRunnable对象赋给 Thread对象,这样Thread就拥有了MyRunnable中定义的业务逻辑,再通过调⽤Thread对象的 start⽅法来启动线程。

java
public class MyRunnable implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			System.out.println("=========MyRunnable========");
		}
	}
}
java
public class Test4 {
	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		//创建状态
		Thread thread = new Thread(myRunnable);
		//就绪状态
		thread.start();
		
		for(int i = 0 ;i < 100; i++) {
			System.out.println("++++++++Test4");
		}
	}
}

4.线程的状态

线程⼀共有5种状态,在特定的情况下,线程可以在不同的状态之间进⾏切换,5种状态如下: 1.创建状态:实例化了⼀个新的线程对象,还未启动。 2.就绪状态:线程对象创建好之后,调⽤了该对象的start⽅法启动该线程,该状态下的线程位于可运 ⾏线程池中,等待系统为其分配CPU资源。 3.运⾏状态:就绪状态的线程在某个时间段内获取到了CPU资源,执⾏相应的业务逻辑(run⽅法的逻 辑)。 4.阻塞状态:运⾏状态的线程因某些原因暂时放弃CPU资源,停⽌执⾏程序,解除阻塞之后不能直接 回到运⾏状态,⽽是重新回到就绪状态,等待下⼀次的CPU资源。 5.死亡状态:线程执⾏完毕或因为异常退出,该线程的⽣命周期结束了。

线程的状态

5.线程调度

1、线程休眠(sleep):让当前线程对象暂停执行,从运行状态进入阻塞状态,将 CPU资源让给其他线程的一种调度方式。 2、线程合并(join):线程甲在某个时刻调用了线程乙的join,从这一时刻开始, CPU资源完全被线程乙占用,直到线程乙的任务执行完毕,将CPU资源释放, 线程甲才能继续执行。 3、线程礼让(yield):让当前线程在某一个时刻让出CPU资源,仅仅是在这一时刻进行 礼让,在下一个时刻会继续争夺CPU资源。 4、线程中断(interrupt): 线程中断有三种方式: 1.线程的任务执行完毕,自动关闭。 2.线程执行过程中,因为抛出异常导致线程中断。 3.根据需求手动进行中断。

线程休眠

休眠指让当前线程暂停执行一段时间,从运行状态进入阻塞状态,将CPU资源让给其他线程的一种调度方法,通过sleep方法来实现,sleep(long millis)是java.lang.Thread类中定义的方法,使用时需要指定当前线程休眠的时间,单位为毫秒(1000毫秒=1秒)。

java
public class Test2 {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		try {
			myThread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		myThread.start();
	}
}

class MyThread extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			if(i == 5) {
				try {
					sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(i);
		}
	}
}

mian线程进行休眠操作

java
public class Test3 {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			if(i == 5) {
				try {
					Thread.currentThread().sleep(3000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(i);
		}
	}
}

线程合并

合并的意思是将指定的某个线程合并到当前线程中,将原本两个交替执行的线程改为顺序执行,即一个线程执行完毕之后再来执行第二个线程,通过调用线程对象的join方法来实现合并。

具体如何来实现合并?谁为主谁为从?假设有两个线程:线程甲,线程乙。 线程甲在执行到某个时间点的时候调用了线程乙的join方法,则表示从当前时间点开始CPU资源被线程乙独占,线程甲进行阻塞状态,直到线程乙执行完毕,线程甲重新进入就绪状态,等待获取CPU资源进入运行状态继续执行。

java
package com.southwind.thread;

public class Test4 {
	public static void main(String[] args) {
		JoinRunnable joinRunnable = new JoinRunnable();
		Thread thread = new Thread(joinRunnable);
		thread.start();
		for (int i = 0; i < 100; i++) {
			if(i == 10) {
				try {
					// 如果传入时间表示该线程只占用传入时间长度的CPU
					//thread.join(3000);
          thread.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(i+"Test+++++++++++++++++");
		}
	}
}

class JoinRunnable implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(i+"------------JoinRunnable");
		}
	}
	
}

同样是完成线程合并的操作,join()和join(long millis)是有区别的: join()表示被调用线程执行完成之后才能释放CPU资源,让其他线程来执行, join(long millis)则表示被调用的线程执行millis毫秒之后,无论其是否执行完毕,其他线程都可以和它来争夺CPU资源。

线程礼让

线程礼让是指在某个特定的时间点,让当前线程暂停抢占CPU资源的行为,即从运行状态或就绪状态来到阻塞状态,从而将CPU资源让给其他线程来使用。 假如有线程甲和线程乙在交替执行,某个时间点线程甲作出了礼让,所以在这个时间点线程乙就拥有了CPU资源,执行其业务逻辑,但不是说线程甲会一直暂停争夺,线程甲只是在特定的时间节点进行礼让,一旦过了这个时间节点,线程甲再次进入就绪状态,和线程乙来争夺CPU资源。

通过调用线程对象的yield方法来完成礼让。

java
package com.southwind.thread;

public class Test5 {
	public static void main(String[] args) {
		YieldThread1 y1 = new YieldThread1();
		y1.setName("YieldThread1");
		YieldThread2 y2 = new YieldThread2();
		y2.setName("YieldThread2");
		y1.start();
		y2.start();
	}
}

class YieldThread1 extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 10; i++) {
			if(i == 5) {
				yield();
			}
			System.out.println(getName()+"------"+i);
		}
	}
}

class YieldThread2 extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 30; i++) {
			System.out.println(getName()+"------"+i);
		}
	}
}

线程中断

Java中实现线程中断机制有如下几个方法: public void stop() public void interrupt() public boolean isInterrupt()

stop()方法在新版本的JDK已经不推荐使用了,interrupt是一个实例方法,当一个线程对象调用该方法时,表示中断当前线程对象。

每一个线程都有一个标志位来标识当前线程对象是否为中断状态,isInterrupt()方法就是用来获取当前线程对象的标志位的,true表示清除了标志位,当前线程对象已经中断,false表示没有清除标志位,当前线程对象没有中断。

线程中断有三种方式: 1.线程的任务执行完毕,自动关闭。 2.线程执行过程中,因为抛出异常导致线程中断。 3.根据需求手动进行中断。

java
package com.southwind.thread;

public class Test6 {
	public static void main(String[] args) {
		Thread thread = new Thread();
		System.out.println(thread.getState());
		thread.start();
		System.out.println(thread.isInterrupted());
		thread.interrupt();
		System.out.println(thread.isInterrupted());
	}
}

6.线程同步

Java中允许多线程并行访问时会产生数据安全问题,例如,当多个变量同时操作一个资源或者访问一个共享数据/变量时,就可能导致数据不准确或者产生错误。解决这一问题就需要使用线程同步synchronized。 使用synchronized关键字修饰方法来实现线程同步。 每个Java对象都有一个内置锁,内置锁会使用synchronized关键字修饰的方法,即要调用该方法,必须先获得内置锁,否则该线程就处于阻塞状态。

以下例子,有一定概率出现线程1和2都是"第2位访客"。

java
package com.southwind.thread;

public class Test {
	public static void main(String[] args) {
		Account account = new Account();
		Thread thread1 = new Thread(account,"线程1");
		Thread thread2 = new Thread(account,"线程2");
		thread1.start();
		thread2.start();
	}
}

class Account implements Runnable{
	private static int num;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		num++;
		try {
			Thread.currentThread().sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
	}	
}

synchronized可以修饰实例方法,也可以修饰静态方法,还可以修饰代码块,会为代码块加上内置锁,从而实现线程同步。 在静态方法中添加同步代码块。 synchronized修饰同步代码块, synchronized(){ // ... }

java
public class Test {
	public static void main(String[] args) {
		Account account = new Account();
		Thread thread1 = new Thread(account,"线程1");
		Thread thread2 = new Thread(account,"线程2");
		thread1.start();
		thread2.start();
	}
}

class Account implements Runnable{
	// 资源需要共享(使用static)
	private static int num;
	@Override
	// 访问共享资源的方法使用synchronized关键字加锁(修饰实例方法)
	public synchronized void run() {
		// TODO Auto-generated method stub
		num++;
		try {
			Thread.currentThread().sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
	}
}
java
public class SynchronizedTest {
	private static Integer num = 1;
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			// 实例化匿名内部类new Runnable
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					test();
				}
			}).start();
		}
	}
	
	// 使用synchronized修饰静态方法
	public synchronized static void test() {
		System.out.println("start...");
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("end...");
	}
}

// start...
// end...
// start...
// end...
// start...
// end...
// start...
// end...
// start...
// end...

()内部需要设置加锁的资源,静态方法属于类的方法,不属于任何一个实例对象,所以静态方法中的synchronized()只能锁定类,不能锁定实例对象,this可以表示当前的一个实例。

注意,这里所说的的不能锁定实例变量是因为,相当于每个线程锁定了自己的实例变量,并看到变量同步的效果。事实上,synchronized()会调用equals方法比较锁定的值,除了相同变量会被锁定,不同的变量拥有相同值时也会被锁定。

java
package com.southwind.thread;

public class SynchronizedTest {
	private static Integer num = 1;
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					test();
				}
			}).start();
		}
	}
	
	public static void test() {
		// synchronized修饰代码块,synchronized(锁定资源){}
		synchronized (num) {
			System.out.println("start...");
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("end...");
		}
	}
	
}
java
package com.southwind.thread;

public class SynchronizedTest2 {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					SynchronizedTest2 st = new SynchronizedTest2();
					st.test();
				}
			}).start();
		}
	}
	
	public void test() {
		// 不能锁定实例对象或实例变量
		synchronized (this) {
			System.out.println("start...");
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("end...");
		}
	}
}

MyRunnable.class:获取的是MyRunnable在内存中的运行时类,每个运行时类只有一份。

单例模式

整个系统中,无论做怎样的业务操作,保证内存中只有一个对象实例。

java
// 单线程的单例模式
class Singleton{
	private volatile static Singleton instance = null;
	
	private Singleton() {
		System.out.println("创建了Singleton对象");
	}
	
	public static Singleton getInstance() {
		if(instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

数据保存在主内存中的,线程在访问该数据时,并不是直接访问主内存中的数据,而是从主内存中拷贝一份数据出来放入工作内存,线程对工作内存中的数据进行操作,操作完成之后再将工作内存中的数据拷贝到主内存中。

使用volatile关键字来解决因为工作内存-主内存机制导致可能会发生的单例的错误。

使用volatile关键字修饰的变量,主内存对线程可见。

线程甲修改了其工作内存中的数据,线程乙工作内存中的数据会同步更新的。

java
// 多线程的单例模式
public class Test2 {
	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					Singleton singleton = Singleton.getInstance();
				}
			}).start();
		}
	}
}

class Singleton{
	// 使用valatile关键字
	private volatile static Singleton instance = null;
	// 构造函数私有化,保证不能被外部创建,从而保证单例
	private Singleton() {
		System.out.println("创建了Singleton对象");
	}
	// 公有接口,可以让外部取得该实例对象
	public static Singleton getInstance() {
		// double-check
		if(instance == null) {
			synchronized (Singleton.class) {
				if(instance == null) {
					instance = new Singleton();
				}
			} 
		}
		return instance;
	}
}

死锁

java
public class Test3 {
	public static void main(String[] args) {
		DeadLockRunnable d1 = new DeadLockRunnable();
		d1.flag = 1;
		DeadLockRunnable d2 = new DeadLockRunnable();
		d2.flag = 2;
		new Thread(d1,"张三").start();
		//避免出现死锁
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		new Thread(d2,"李四").start();
	}
}

class DeadLockRunnable implements Runnable{
	public int flag;
	// 一个线程必须同时拥有两个资源才能进行
	private static Object o1 = new Object();
	private static Object o2 = new Object();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		if(flag == 1) {
			System.out.println(Thread.currentThread().getName()+"获取了资源o1,等待获取资源o2");
			synchronized (o1) {
				try {
					Thread.currentThread().sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (o2) {
					System.out.println(Thread.currentThread().getName()+"执行完毕");
				}
			}
		}
		if(flag == 2) {
			System.out.println(Thread.currentThread().getName()+"获取了资源o2,等待获取资源o1");
			synchronized (o2) {
				try {
					Thread.currentThread().sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (o1) {
					System.out.println(Thread.currentThread().getName()+"执行完毕");
				}
			}
		}
	}
	
}

线程实际应用

例1

1.多线程模拟两个跑步,每个线程代表一个人,可设置每个人跑步的速度,每跑完100米给出响应的信息,跑到终点之后给出响应的提示。

java
public class Test4 {
	public static void main(String[] args) {
		RunRunnable r1 = new RunRunnable(4000, 1);
		RunRunnable r2 = new RunRunnable(1000, 1);
		new Thread(r1,"张三").start();
		new Thread(r2,"李四").start();
	}
}

class RunRunnable implements Runnable{
	//跑100米需要的时间
	private int time;
	//已跑完的100米
	private int num;
	
	public RunRunnable(int time,int km) {
		this.time = time;
		this.num = km*1000/100;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(num > 0) {
			System.out.println(Thread.currentThread().getName()+"跑了100米!");
			try {
				Thread.currentThread().sleep(this.time);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			num--;
		}
		System.out.println(Thread.currentThread().getName()+"到达了终点!");
	}
}

例2

2.某楼盘摇号买房,分为普通号和VIP号,50个普通号,10个VIP号。VIP号的选房时间为普通号的2倍,开始普通号和VIP号并行叫号,叫到VIP号的概率比普通号更高,当普通号叫完第10号时,要求先让VIP号全部选完,再让普通号选房,用多线程模拟这个过程。

java
public class Test {
	public static void main(String[] args) {
		//VIP选房的线程
		VIPThread vip = new VIPThread();
		vip.setPriority(Thread.MAX_PRIORITY);
		vip.start();
		
		//普通号选房的线程
		NormalRunnable normal = new NormalRunnable();
		normal.setThread(vip);
		Thread thread2 = new Thread(normal);
		// priority为[1,10]之间的整数。NORM_PRIORITY为5,MIN_PRIORITY为1,为10.
		thread2.setPriority(Thread.MIN_PRIORITY);
		thread2.start();
	}
}

class VIPThread extends Thread{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= 10; i++) {
			System.out.println("VIP"+i+"正在选房");
			try {
				Thread.currentThread().sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
} 

class NormalRunnable implements Runnable{
	private Thread thread;
	
	public Thread getThread() {
		return thread;
	}

	public void setThread(Thread thread) {
		this.thread = thread;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= 50; i++) {
			if(i == 10) {
				try {
					thread.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("普通号"+i+"正在选房");
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

例3

3.用多线程模拟网络购票,“游客”,“学生”,“代理”共同抢15张票。

java
public class Test2 {
	public static void main(String[] args) {
		TicketRunnable ticketRunnable = new TicketRunnable();
		Thread t1 = new Thread(ticketRunnable,"游客");
		Thread t2 = new Thread(ticketRunnable,"学生");
		Thread t3 = new Thread(ticketRunnable,"代理");
		t1.start();
		t2.start();
		t3.start();
	}
}

class TicketRunnable implements Runnable{
	//剩余票数
	public int count = 15;
	//已售出的票数
	public int num = 0;
	@Override
	// 法1:public synchronized  void run() 
	public void run() {
		// TODO Auto-generated method stub
		while(count > 0) {
			try {
				Thread.currentThread().sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 法2:
			synchronized (TicketRunnable.class) {
				if(count == 0) {
					return;
				}
				count--;
				num++;
				System.out.println(Thread.currentThread().getName()+"抢到了第"+num+"张票,还剩"+count+"张票");
			}
			
		}
		
	}
	
}

wait:线程等待

wait的功能和sleep类似,都是让线程暂停执行任务,但是其实是两个完全不同的方法。 sleep是Thread类中的方法,让当前线程实例对象暂停执行任务,进入阻塞状态。 wait是Object类的方法,所以它不是针对线程对象的方法,而是针对线程对象要访问的资源对象的方法。

即调用A对象的wait方法表示:让当前正在访问A对象的线程暂停,同时它有一个前提,当前线程对象必须拥有A对象,所以wait方法只能在同步方法或同步代码块中使用,否则会抛出IllegalMonitorStateException异常。

java
package com.southwind.thread;

public class Test4 {
	public static void main(String[] args) {
		A a = new A();

		new Thread(new Runnable() {		
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 0; i < 10; i++) {
					a.test(i);
				}
			}
		}).start();
	}
}

class A {
	public synchronized void test(int i) {
		if(i == 5) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(i+"---A");
	}
}

如何解除wait造成的阻塞? 1.指定wait时间,调用wait(long millis)即可,millis毫米之后会自动解除阻塞,和sleep(long millis)类似的方法。 2.notify

notify:唤醒线程

java
public class Test4 {
	public static void main(String[] args) {
		A a = new A();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int i = 0; i < 10; i++) {
					a.test(i);
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					Thread.currentThread().sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				a.test2();
			}
		}).start();
	}
}

class A {
	public synchronized void test(int i) {
		if(i == 5) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(i+"---A");
	}
	public synchronized void test2() {
		// 把当前正在能够访问A对象的休眠线程唤醒
		this.notify();
	}
}

例4.生产者消费者模型

java
public class Hamburger {
	private int id;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
	public Hamburger(int id) {
		this.id = id;
	}

	@Override
	public String toString() {
		return "Hamburger [id=" + id + "]";
	}
	
}
java
/*
 * 栈:后进先出
 */
public class SyncStack {
	public Hamburger[] array = new Hamburger[6];
	public int index = 0;
	/*
	 * 向容器中添加汉堡
	 */
	public synchronized void push(Hamburger hamburger) {
		while(index == array.length) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.notify();
		array[index] = hamburger;
		index++;
		System.out.println("生产了一个汉堡:"+hamburger);
	}
	
	/*
	 * 从容器中取出汉堡
	 */
	public synchronized Hamburger pop() {
		while(index == 0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		this.notify();
		index--;
		System.out.println("消费了一个汉堡:"+array[index]);
		return array[index];
	}
}
java
// 生产者
public class Produce implements Runnable{
	private SyncStack syncStack = null;
	public Produce(SyncStack syncStack) {
		// TODO Auto-generated constructor stub
		this.syncStack = syncStack;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			Hamburger hamburger = new Hamburger(i);
			this.syncStack.push(hamburger);
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
java
// 消费者
public class Consumer implements Runnable{
	private SyncStack syncStack;
	
	public Consumer(SyncStack syncStack) {
		this.syncStack = syncStack;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			this.syncStack.pop();
			try {
				Thread.currentThread().sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
java
public class Test {
	public static void main(String[] args) {
		SyncStack syncStack = new SyncStack();
		Produce produce = new Produce(syncStack);
		Consumer consumer = new Consumer(syncStack);
		new Thread(produce).start();
		new Thread(produce).start();
		new Thread(produce).start();
		new Thread(consumer).start();
		new Thread(consumer).start();
	}
}

重入锁

ReentrantLock,是对synchronized的升级,synchronized是通过JVM实现的,ReentrantLock是通过JDK实现的。

重入锁的特点:

重入锁指可以给同一个资源添加多个锁,并且解锁的方式与synchronized也不同,synchronized的锁是当线程执行完业务逻辑之后自动释放,ReentrantLock的锁必须手动释放。

1.可重入,可以给同一个资源同时添加多把锁,对应的解锁的次数必须与上锁的次数相同,否则就会出现程序不继续执行的情况。

java
import java.util.concurrent.locks.ReentrantLock;

public class Test {
	public static void main(String[] args) {
		Account account = new Account();
		new Thread(account).start();
		new Thread(account).start();
	}
}

class Account implements Runnable{
	private int num;
	private ReentrantLock reentrantLock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		reentrantLock.lock();
		reentrantLock.lock();
		num++;
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"是第"+num+"位访问");
		reentrantLock.unlock();
		reentrantLock.unlock();
	}
}

2.可中断,指某个线程在等待获取锁的过程中可主动终止线程。

java
import java.util.concurrent.locks.ReentrantLock;

public class Test2 {
	public static void main(String[] args) {
		StopLock stopLock = new StopLock();
		Thread t1 = new Thread(stopLock,"线程1");
		Thread t2 = new Thread(stopLock,"线程2");
		t1.start();
		t2.start();
		try {
			Thread.currentThread().sleep(6000);
			t2.interrupt();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class StopLock implements Runnable{
	private ReentrantLock reentrantLock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			reentrantLock.lockInterruptibly();
			System.out.println(Thread.currentThread().getName()+"get lock");
			Thread.currentThread().sleep(5000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			reentrantLock.unlock();
		}
	}
}

3.限时性,指可以判断某个线程在一定的时间段内能否获取锁,通过boolean tryLock(long time,TimeUnit unit),time表示时间数值,unit表示时间单位,返回值为boolean类型,true表示在该时间段内获取了锁,false表示在该时间段内没有获取锁。

java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class Test3 {
	public static void main(String[] args) {
		TimeLock timeLock = new TimeLock();
		new Thread(timeLock).start();
		new Thread(timeLock).start();
	}
}

class TimeLock implements Runnable{
	private ReentrantLock reentrantLock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			if(reentrantLock.tryLock(6, TimeUnit.SECONDS)) {
				System.out.println(Thread.currentThread().getName()+"get lock");
				Thread.currentThread().sleep(5000);
			}else {
				System.out.println(Thread.currentThread().getName()+"not get lock");
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			// 判断当前线程是否已经被上锁
			if(reentrantLock.isHeldByCurrentThread()) {
				reentrantLock.unlock();
			}
		}
	}
}

高并发

并行和并发。

并发concurrency 并行parallelism

并行:指多个操作同时执行,判断程序是否处于并行状态,就看同一个时刻是否有一个以上的工作单元在运行,单线程是永远无法达到并行状态的。 并发:指的是程序的结构,处理并行的能力。如果一个系统称之为并发系统,则表示该系统采用了支持并发的设计模式,所以并发并不是指多个线程同时执行,它指的是一种人为设计的程序结构,可以处理多线程的能力。

高并发是指我们设计的程序,可以支持海量的任务在同一时间段内同时执行。

高并发的标准: 1.QPS:每秒响应的http请求数量,QPS不等于并发量,并发数是指某时刻同时到到达服务器的请求数量。 2.吞吐量:单位时间内可以处理的请求数。 3.平均响应时间:系统对一个请求做出响应的平均时间。 QPS = 并发数/平均响应时间。 4.并发用户数量:系统在正常运行情况可以承载的用户数量。

提供系统并发能力的两种方式:1.垂直扩展,水平扩展。

垂直扩展

提升单机的处理能力。 1.增强单机硬件性能。 2.提升单机架构性能。

水平扩展

系统集群(分层架构,nginx反向代理分担web应用服务器的压力,数据层主从复制,读写分离,分表分库,减轻数据库服务器的压力)

线程池

当提交一个任务时,线程池会创建一个新的线程去执行该任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务会被存入阻塞队列,等待被执行;如果阻塞队列满了,那就创建一个新的线程来执行当前任务,直到线程池中的线程数等于maxmumPoolSize,这时如果再有任务提交过来,只能执行reject()方法来拒绝处理。

优点: 1.线程的创建和销毁是需要耗费资源的,使用线程池可以有效的减少创建和销毁线程的次数,每个工作线程都可以重复使用。 2.可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。

工作流程: (1)当提交一个任务时,线程池会创建一个新的线程执行任务,直到当前线程池中的线程数等于corePoolSize:线程池的大小。 (2)如果当前线程数量已经到达了corePoolSize,继续提交的任务被保存到阻塞队列中,等待获取执行完成的任务释放线程,从而执行等待任务。 (3)如果阻塞队列满了,那就再创建新的线程去执行任务,直到线程池中的线程数达到maximumPoolSize,这时候如果再有任务来,只能执行拒绝方案。

ThreadPoolExecutor类

java.util.concurrent.ThreadPoolExecutor类是Java描述线程池中最核心的一个类。

核心参数:

corePoolSize:核心池的大小。 ThreadPoolExecutor提供了动态修改线程池容量的方法: setCorePoolSize()修改corePoolSize setMaxmumPoolSize()修改maxmumPoolSize maxmumPoolSize:线程池的最大线程数,线程池中线程数量的上限。 keepAliveTime:空闲的线程可以存活的时间,线程池中的线程数大于corePoolSize,线程池进入了紧急预案状态,当线程数小于corePoolSize,keepAliveTime不起作用。例如,corePoolSize为5,keepAliveTime为10,线程池中有6个线程,如果某个线程的闲置时间达到了10s,则释放该线程。直到线程池中的线程数量小于等于corePoolSize,keepAliveTime失效。 unit:keepAliveTime参数的时间单位,TimeUnit中有7个常量来表示7种不同的时间单位。 TimeUnit.DAYS 天 TimeUnit.HOURS 小时 TimeUnit.MINUTES 分钟 TimeUnit.SECONDS 秒 TimeUnit.MILLSECONDS 毫秒 TimeUnit.MICROSECONDS 微秒 TimeUnit.NANOSECONDS 纳秒 workQueue:一个阻塞队列,用来存储等待执行的任务,一般阻塞队列有以下几种选择:

  • ArrayBlockingQueue:基于数组的先进先出队列,创建时必须制定大小。
  • LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定大小,默认Integer.MAX_VALUE (0x7fffffff)。
  • SynchronousQueue:不会保存提交的任务,而是直接创建一个新的线程来执行新的任务。
  • PriorityBlockingQueue:具有优先级的阻塞队列。

threadFactory:线程工厂,用来创建线程。 handler:表示拒绝处理任务所采取的策略,有以下四种取值。

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并且抛出异常。
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新执行当前任务。
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

线程池的状态

1.RUNNING:该状态下的线程池可以接收新的任务,并且处理阻塞队列中的任务。 2.SHUTDOWN:该状态下的线程池不接收新的任务,但是会处理阻塞队列中的任务。 3.STOP:该状态下的线程池不接收新的任务,也不会处理阻塞队列中的任务,并且会中断正在运行的任务。(暂停) 4.TIDYING:该状态表示线程池对线程进行整理优化。 5.TERMINATED:该状态表示线程池停止工作。(关闭)

ThreadPoolExecutor 继承了 AbstractExecutorService AbstractExecutorService是一个抽象类,实现了ExecutorService接口 ExecutorService继承了Executor

  • Executor是一个顶层接口,该接口中只声明了一个方法execute(Runnable),返回值为void,参数是Runnable类型的,就是用来执行传入的任务。
  • ExecutorService接口继承了Executor接口,并声明了一些方法:submit,invokeAll,shutDown。
  • 抽象类AbstractExecutorService实现了ExecutorService接口,基本上实现了ExecutorService中声明的所有方法。
  • ThreadPoolExecutor继承了AbstractExecutorService,ThreadPoolExecutor实现了全部的方法。

ThreadPoolExecutor类中常用的方法:

  • execute(),是一个核心方法,通过该方法可以向线程池提交一个任务,由线程池去安排执行。
  • submit(),ExecutorService接口中声明的方法,也是用来向线程池提交任务的,和execute()方法不同,可以返回执行任务的结果。
  • shutdown():关闭线程池,不会立即终止线程池,而是等待阻塞队列中的任务全部执行完毕之后在终止,同时也不会再接收新的任务。
  • shutdownNow():立即终止线程池,并中断正在执行的任务,并且清空阻塞队列,返回尚未执行的任务。

execute内部实现: 1.通过workCountof()方法获取当前线程池中的线程数。 如果小于corePoolSize,就通过addWorker()创建线程并执行该任务,否则,将该任务放入阻塞队列。

2.如果能够成功的放入到阻塞队列中,若当前线程池是非RUNNING状态,则将该任务从阻塞队列中移除,然后执行reject()拒绝处理该任务。 如果当前线程池处于RUNNING状态,则需要再次检查线程池,如果有空闲的线程则执行该任务。

3.如果不能将任务放入阻塞队列中,说明阻塞队列已满,就通过addWorker()方法尝试创建新的线程来执行该任务,如果addWorker()方法失败,则表示当前线程池中的线程数已经达到了maxmumPoolSize,执行reject()拒绝处理该任务。

submit内部实现: 会将提交的任务封装成一个FutureTask对象,FutureTask类实现了Runnable接口,这样就可以通过Executor.execute()提交FutureTask到线程池中等待被执行,最终执行的是FutureTask的run方法。

线程池的使用

例1

1.创建一个固定大小的线程池,任务超过10个时,将任务放入阻塞队列中。

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test2 {
	public static void main(String[] args) {
		// 实际开发中,不提倡直接实例化ThreadPoolExecutor,而是通过Executors类提供的静态方法来创建线程池。
		ExecutorService service =  Executors.newFixedThreadPool(10);
		for (int i = 0; i < 10; i++) {
			MyTask2 task = new MyTask2();
			service.execute(task);
		}
		service.shutdown();
	}
}

class MyTask2 implements Runnable{
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			Thread.currentThread().sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("...");
	}
}
例2

2.创建一个缓存线程池。 newCachedThreadPool(),线程数可以达到Integer.MAX_VALUE,2147483647,内部使用SynchronousQueue作为阻塞队列。

java
ExecutorService service =  Executors.newCachedThreadPool();
例3

3.创建单例线程池。 newSingleThreadExecutor(),线程池中只有一个线程。

java
public class Test3 {
	public static void main(String[] args) {
		ExecutorService sevice = Executors.newSingleThreadExecutor();
		for(int i = 0; i < 10; i++) {
			MyTask2 task = new MyTask2();
			sevice.execute(task);
		}
		sevice.shutdown();
	}
}
例4

4.任务调度线程池。 newScheduledThreadPool()

java
public class Test4 {
	public static void main(String[] args) {
		ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
		//延迟5秒执行,每隔3秒执行一次任务(任务,延迟时间,执行周期,单位)
		service.scheduleAtFixedRate(new MyTask2(), 5, 3, TimeUnit.SECONDS);
		service.shutdown();
	}
}