多线程

多线程

	程序是静止的,只有真正运行时的程序,才被称为进程。
    单核CPU在任何时间点上,只能运行一个进程;宏观并行、微观串行。
		
	附:电脑核数查询操作   

cmd中查看CPU核数
查询语句

  1. 什么是线程?

    1.1 线程,又称为轻量级进程(Light Weight Process)程序中的一个顺序控制流程,同时也是CPU的基本调度单位。
    
    1.2 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。
    
    例如:迅雷是一个进程,当中的多个下载任务即是多线程。
         Java虚拟机(不同的虚拟环境下模拟相同环境)是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行。
    
  2. 线程的组成

    2.1 任何一个线程都具有基本的组成部分:
         CUP时间片:操作系统(OS)会为每个线程分配执行时间。
    2.2 运行数据:
        堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象
        栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。
    2.3 线程的逻辑代码
    	2.3.1 创建线程(1)
    	继承Thread类
    	覆盖run()方法
    	创建子类对象
    	调用start方法
    
public class TestExtendsThead {

	public static void main(String[] args) {
		MyThread t1 = new MyThread();//3.创建子类(线程)对象
		MyThread2 t2 = new MyThread2();
		//t1.run();//直接调用run方法,普通对象调用方法一样
		t1.start();//4.调用start方法
		t2.start();
		//由JVM来调用run方法
		
		for(int i = 1;i<50;i++) {
			System.out.println("Main--"+i);
		}
	}

}
class MyThread extends Thread{//1.继承Thread类

	@Override//2.覆盖run()方法
	public void run() {//线程的任务
		for(int i = 1;i<50;i++) {
			System.out.println("MyThread----"+i);
		}
	}
	
}
class MyThread2 extends Thread{//1.继承Thread类

	@Override//2.覆盖run()方法
	public void run() {//线程的任务
		for(int i = 1;i<50;i++) {
			System.out.println("MyThread2-------"+i);
		}
	}
}
        2.3.2 创建线程(2)
    	实现Runable接口
    	覆盖run()方法
    	创建线程对象
    	调用start方法
public class TestImplementsRunable {

	public static void main(String[] args) {
		//创建线程对象
		MyRunnable mr1 = new MyRunnable();
		//Thread线程类的有参构造方法
		Thread t1 = new Thread(mr1);
		Thread t2 = new Thread(mr1);
		t1.start();
		t2.start();
	}

}
//实现接口,只是将当前编程线程任务类,本身不是个线程
//任务是可以多个线程对象共享
//更灵活
class MyRunnable implements Runnable{
	@Override
	public void run() {
		for(int i = 1;i<50;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}
  1. 线程的状态


2.3.3 常见方法:
休眠:
public static void sleep(long millis)
throws InterruptedException
当前线程主动休眠millis毫秒

public class TestSleep {

	public static void main(String[] args) throws InterruptedException {
		MyThread t1 = new MyThread();
		t1.start();
		
		MyRunnable task = new MyRunnable();
		Thread t2 = new Thread(task);
		t2.start();
		
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+i);
			
			if(i==30) {//在特定条件下休眠,在指定时间内不去竞争时间片
				//此时用的是类名调用sleep方法,sleep为静态方法
				Thread.sleep(2000);//通知完t1后,main线程休眠2秒
			}			
		}	
	}
}
class MyThread extends Thread{//1.继承Thread类

	@Override//2.覆盖run()方法
	public void run() {//线程的任务
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+i);
		}
	}
}
class MyRunnable implements Runnable{
	@Override
	public void run() {
		for(int i = 1;i<=50;i++) {
			if(i%2==0) {
				System.out.println("线程2 休眠了");
				try {//不能使用抛出,因为父类并没有抛出异常
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}
放弃:
      	public static void yield()
      	当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public class TestYield {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Task());
		t1.start();
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i%10 == 0) {
				System.out.println("main主动放弃");
				Thread.yield();//放弃!主动放弃当前持有时间片,进入下一次的竞争
			}
		}

	}
}
class Task implements Runnable{
	@Override
	public void run() {
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}	
}
 结合:
 		public final void join(long millis)
 		throws InterruptedException
 		允许其他线程加入到当前线程。
public class TestJoin {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Task2());
		Thread t2 = new Thread(new Task2());
		
		t1.start();
		t2.start();
		
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i == 20) {
				System.out.println("main执行到20了,执行t1");
				t1.join();//将t1加入到main线程执行流程中,等待t1线程执行结束后,main再进行竞争时间片。
				//无限期等待:等待条件为调用join方法的线程完毕后!再进入就绪状态,竞争时间
			}
		}	
	}
}
class Task2 implements Runnable{
	@Override
	public void run() {
		for(int i = 1;i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}
}
	线程的状态(等待)


 4. **线程安全**
 
 		线程不安全:
				 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
 				 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
 				 原子操作:不可分割的多部操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
   
   	    线程安全:
   	
   ==同步方式(1)==
   同步代码块:
```java
synchronized(临界资源对象){//对临界资源对象加锁
	//代码(原子操作)
}
public class TestSynchronized {

	public static void main(String[] args) {
		//临界资源,被共享的对象
		Account acc = new Account("6002","1234",2000);
		//两个线程对象,共享同一银行卡对象.
		Thread husband = new Thread(new Husband(acc),"丈夫");
		
		Thread wife = new Thread(new Wife(acc),"妻子");
		
		husband.start();
		wife.start();
		
	}
}

class Husband implements Runnable{
	Account acc;
	
	public Husband(Account acc) {
		this.acc=acc;
	}
	@Override//线程任务:取款;
	public void run() {
		//synchronized(acc) {
			this.acc.withdrawal("6002", "1234",1200);
		//}
	}
}
class Wife implements Runnable{
	Account acc;
	public Wife(Account acc) {
		this.acc=acc;
	}
	@Override//线程任务:取款;
	public void run() {
		//synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作,那么妻子会等。
			this.acc.withdrawal("6002", "1234",1200);
		//}
	}
}

//银行账户 银行卡
class Account{
	String cardNo;
	String password;
	double balance;
	public Account(String cardNo, String password, double balance) {
		super();
		this.cardNo = cardNo;
		this.password = password;
		this.balance = balance;
	}
	
	
	//取款(原子操作,从插卡验证到取款成功一系列步骤,不可缺少或打乱)
	public void withdrawal(String no,String pwd,double money){
		synchronized(this){
			System.out.println(Thread.currentThread().getName()+"正在读卡。。。");
			
			if(no.equals(this.cardNo)&&pwd.equals(this.password)) {
				System.out.println(Thread.currentThread().getName()+"验证成功。。。");
				if(money <= this.balance) {
					
					try {
						Thread.sleep(1000);//模拟现实世界,ATM机在数钱
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
					this.balance = this.balance-money;
					System.out.println(Thread.currentThread().getName()+"取款成功!当前余额为:"+this.balance);
				}else {
					System.out.println(Thread.currentThread().getName()+"当前余额不足!:");
				}
			}else {
				System.out.println("卡号或密码错误!");
			}
		}			
	}
}

注:

  • 每个对象都有一个互斥锁标记,用来分配给线程的。
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
  • 线程退出同步代码块时,会释放相应的互斥锁标记。
    在这里插入图片描述

同步方式(2)
同步方法:

synchronized 返回值类型 方法名称(形参列表0){//对当前对象(this)加锁
	//代码(原子操作)
}
public class TestSynchronized{

	public static void main(String[] args){
		//临界资源,被共享的对象
		Account acc = new Account("6002","1234",2000);
		//两个线程对象,共享同一银行卡对象.
		Thread husband = new Thread(new Husband(acc),"丈夫");
		
		Thread wife = new Thread(new Wife(acc),"妻子");
		
		husband.start();
		wife.start();
		
	}
}

class Husband implements Runnable {
	Account acc;
	
	public Husband(Account acc) {
		this.acc=acc;
	}
	@Override//线程任务:取款;
	public void run() {
		//synchronized(acc) {
			this.acc.withdrawal("6002", "1234",1200);
		//}
	}
}
class Wife implements Runnable{
	Account acc;
	public Wife(Account acc) {
		this.acc=acc;
	}
	@Override//线程任务:取款;
	public void run() {
		//synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作,那么妻子会等。
			this.acc.withdrawal("6002", "1234",1200);
		//}
	}
}

//银行账户 银行卡
class Account{
	String cardNo;
	String password;
	double balance;
	public Account(String cardNo, String password, double balance) {
		super();
		this.cardNo = cardNo;
		this.password = password;
		this.balance = balance;
	}
	
	
	//取款(原子操作,从插卡验证到取款成功一系列步骤,不可缺少或打乱)
	public synchronized void  withdrawal(String no,String pwd,double money) {
		//synchronized(this){
			System.out.println(Thread.currentThread().getName()+"正在读卡。。。");
			
			if(no.equals(this.cardNo)&&pwd.equals(this.password)) {
				System.out.println(Thread.currentThread().getName()+"验证成功。。。");
				if(money <= this.balance) {
					
					
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}//模拟现实世界,ATM机在数钱
				
					
					this.balance = this.balance-money;
					System.out.println(Thread.currentThread().getName()+"取款成功!当前余额为:"+this.balance);
				}else {
					System.out.println(Thread.currentThread().getName()+"当前余额不足!:");
				}
			}else {
				System.out.println("卡号或密码错误!");
			}
		}
			
	//}
}

注:

  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。

  • 线程退出同步方法时,会释放相应的互斥锁标记。

    同步规则
    注意:

  • 线程池

    只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。

    如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

    已知JDK中线程安全的类:
    StringBuffer
    Vector
    Hashtable
    增删改操作加锁
    查不加锁
    以上类中的公开方法,均为synchronized修饰的同步方法。

    经典问题

死锁:

当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。

一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
public class TestDeadLock {
	public static void main(String[] args) throws InterruptedException {
		Object obj = new Object();
		MyThread1 t1 = new MyThread1(obj);
		MyThread2 t2 = new MyThread2(obj);
		t1.start();
		t2.start();
		Thread.sleep(3000);
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			
			//obj.wait();
			//obj.notify();//随机唤醒一个拿执行代码
			obj.notifyAll();//将obj中所有wait队列所有的线程都唤醒
			System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
		}
	}
}

//简单:一个线程持有A对象的锁,另一个对象也想要:阻塞
//复杂:一个对象持有A对象的锁,需要B对象的锁,另一个持有B,想要A
class MyThread1 extends Thread{
	Object obj;
	public MyThread1(Object obj) {
		this.obj = obj;
	}
	public void run() {
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			try {
				obj.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
	}
}
class MyThread2 extends Thread{
	Object obj;
	public MyThread2(Object obj) {
		this.obj = obj;
	}
	public void run() {
		synchronized(obj) {
			System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
			try {
				obj.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
	}
}
public class TestDeadLock1 {

	public static void main(String[] args) {
		LeftChopstick left  = new LeftChopstick();
		RightChopstick right = new RightChopstick();
		Thread b = new Thread(new Boy(left,right));
		Thread g = new Thread(new Girl(left,right));
		b.start();
		
		g.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
class LeftChopstick{
	String name = "左筷子";
}
class RightChopstick{
	String name = "右筷子";
}
class Boy implements Runnable{
	LeftChopstick left;
	RightChopstick right;
	public Boy(LeftChopstick left, RightChopstick right) {
		super();
		this.left = left;
		this.right = right;
	}

	@Override
	public void run() {
		System.out.println("男孩子要拿筷子");
		synchronized(left) {
			try {
				left.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("男孩子拿到左筷子,开始拿右筷子");
			synchronized(right) {
				System.out.println("男孩子拿到右筷子,开始吃饭");
			}
		}
		
	}	
}
class Girl implements Runnable{
	LeftChopstick left;
	RightChopstick right;
	public Girl(LeftChopstick left, RightChopstick right) {
		super();
		this.left = left;
		this.right = right;
	}
	@Override
	public void run() {
		System.out.println("女孩子要拿筷子");
		synchronized(right) {
			System.out.println("女孩子拿到右筷子,开始拿左筷子");
			synchronized(left) {
				System.out.println("女孩子拿到左筷子,开始吃饭");
				left.notify();
			}	
		}
		
	}
}

生产者、消费者:

若干个生产者在生产产品,这些产品将提供给若干消费者去消费,为了使生产者和消费者能并发执行,
在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,
显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取出产品,也不允许生产者向一个满的缓冲区中放入产品。
public class TestProductAndCustomer {
	public static void main(String[] args) {
	Shop shop = new Shop();
	Thread p = new Thread(new Product(shop),"生产者");
	Thread c = new Thread(new Customer(shop),"消费者");
	p.start();
	c.start();
	}
}
class Goods{
	private int id;
	public Goods(int id) {
		this.id = id;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	
}
class Shop{
	Goods goods;
	boolean flag;//标识商品是否充足
	//生产者调用的 存的方法
	public synchronized void saveGoods(Goods goods) throws InterruptedException {
		//1.判断商品是否充足
		if(flag == true) {//商品充足,生产者不用生产,而等待消费者买完!
			System.out.println("生产者:商品充足!要等待了!");
			this.wait();//商品充足,生产者不用生产,而等待消费者买完,进入等待状态
		}
		//商品不充足:生产者不生产商品,存到商城里
		System.out.println(Thread.currentThread().getName()+"生产并在商城里存放了"+goods.getId()+"件商品");
		this.goods = goods;
		flag = true;//已经有商品了;可以让消费者购买了
		//消费者等待
		this.notifyAll();将等待的消费者唤醒。前来购买商品
	}
	
	//消费者调用的取得方法
	public synchronized void buyGoods() throws InterruptedException {
		if(flag == false) {//没有商品了,消费者就要等待
			System.out.println("消费者:商品不充足!,要等待了");
			this.wait();//消费者进入等待队列,等到生产者生产商品后,唤醒
		}
		//正常购买
		System.out.println(Thread.currentThread().getName()+"购买了"+goods.getId()+"件商品");
		//商品买完了;标识没货
		this.goods =null;
		flag = false;
		//唤醒生产者去生产商品
		this.notifyAll();
	}
}
//生产者
class Product implements Runnable{
	Shop shop;
	public Product(Shop shop) {
		this.shop = shop;
	}
	@Override
	public void run() {
		//通过循环,生产商品存到shop
		for(int i =1; i<=30;i++) {
			try {
				//生产者线程调用存商品方法,传一个商品对象
				this.shop.saveGoods(new Goods(i));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
//消费者
class Customer implements Runnable{
	Shop shop;
	public Customer(Shop shop) {
		this.shop = shop;
	}
	@Override
	public void run() {
		for(int i =1; i<=30;i++) {
			try {
				this.shop.buyGoods();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

线程通信
等待

	public final void wait()
	
	public final void wait(long timeout)
	
	必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。
	同时此线程阻塞在obj的等待队列中。释放锁,进入等待队列

通知

	public final void notify()

	public final void notifyAll()
	
	必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响。

高级多线程

线程池概念

  • 现有问题:

     线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出
     
     频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
    
  • 线程池:

     线程容器,可设定线程分配的数量上限。
    
     将预先创建的线程对象存入池中,并重用线程池中的线程对象
    
     避免频繁的创建和销毁
    

线程池原理
在这里插入图片描述
获取线程池

常用的线程池接口和类(所在包java.util.concurrent):
 		Executor:线程池的顶级接口
 		
 		ExecutorService:线程池接口,可通过submit(Runnale task)提交任务代码
 		
 		Executors工厂类:通过此类可以获得一个线程池。

		通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。

		通过newCashedThreadPool()获取动态数量的线程池,如不够则创建新的,没有上限
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
	public static void main(String[] args) {
		//线程池(引用)  ---->Executors工具类 (工厂类)
		//ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池中线程数量
		ExecutorService es = Executors.newCachedThreadPool();//动态数量的线程池
		//1.创建任务对象
		MyTask task = new MyTask();
		
		//Thread t = new Thread(task);
		//es.submit(t);如此才是线程
		//2.将任务提交到线程池,由线程池调度、执行
		es.submit(task);//Runnable类型的对象
		es.submit(task);
		es.submit(task);
		es.submit(task);
		es.submit(task);
	}
}
//线程任务
class MyTask implements Runnable{

	@Override
	public void run() {
		for(int i = 1; i<=50;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		
	}
	
}

Callable接口

public interface Callable<V>{
	public V call() throws Exception;
}

JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值,可以声明异常。

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

public class TestCallable {

	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(3);
		MyTask1 task = new MyTask1();
		es.submit(task);
	}

}
class MyTask1 implements Callable<Integer>{//1.有泛型

	@Override
	public Integer call() throws Exception {//2.可以声明异常
		
		for(int i = 1;i<=50;i++) {
			if(i == 30) {
				Thread.sleep(1000);
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		return null;
	}
	
}

Future接口
概念:异步接收ExecutorService.submit()所返回的状态结果,当中包含了接收call()的返回值
方法:V get()以阻塞形式等待Future中的异步处理(call()的返回值)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestFuture {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		MyCall mc = new MyCall();
		Future<Integer> result =es.submit(mc);//通过submit执行提交的任务,Future接收返回的结果
		Integer value = result.get();//通过Future的get方法,获得线程执行完毕后的结果
		System.out.println(value);
		
		MyCall2 mc2 = new MyCall2();
		Future<Integer> result2 =es.submit(mc2);
		Integer value2 = result2.get();
		System.out.println(value2);
		
		System.out.println(value2+value);
		
		
	}
}
class MyCall implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("MyCall正在执行。。。");
		return 100;
	}
}
class MyCall2 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("MyCall2正在执行。。。");
		return 200;
	}
}

使用两个线程,并发计算1—50、51—100的和,在进行汇总统计

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestFuture {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		ExecutorService es = Executors.newFixedThreadPool(2);
		
		MyCall mc = new MyCall();
		Future<Integer> result =es.submit(mc);//通过submit执行提交的任务,Future接收返回的结果
		Integer value = result.get();//通过Future的get方法,获得线程执行完毕后的结果
		System.out.println(value);
		
		MyCall2 mc2 = new MyCall2();
		Future<Integer> result2 =es.submit(mc2);
		Integer value2 = result2.get();
		System.out.println(value2);
		
		System.out.println(value2+value);
	}
}
class MyCall implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("MyCall正在执行。。。");
		Integer sum = 0;
		for(int i = 1;i<=50;i++) {
			sum=sum+i;
		}
		return sum;
	}
}
class MyCall2 implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		System.out.println("MyCall2正在执行。。。");
		Integer sum = 0;
		for(int i = 51;i<=100;i++) {
			sum=sum+i;
		}
		return sum;
	}
}

线程的同步
同步:形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。单条执行路径
线程的异步
异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。多条执行路径。

Lock接口

  • JDK5加入,与synchronized比较,显示定义,结构更灵活
  • ‘提供更多实用性方法,功能更强大、性能更优越。
  • 常用方法:
    void lock()//获取锁,如锁被占用,则等待。
    boolean tryLock()//尝试获取锁(成功返回true,失败则false)
    void unlock() //释放锁

重入锁

  • ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能。

     使用Lock,需要明确的写上锁和释放锁
     为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁!
     应用try{}finally{}来保证,无论正确执行与否,最终都会释放锁。
    
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
	public static void main(String[] args) {
		Test obj = new Test();
		Thread t1 = new Thread(new MyTask(obj));
		Thread t2 = new Thread(new MyTask2(obj));
		
		t1.start();
		t2.start();
		
	}
}
class Test{
	Lock lock = new ReentrantLock();
	public void method() {
		//显示写上此处获得锁
		try {
		int i = 0;
		lock.lock();
		System.out.println(Thread.currentThread().getName()+"进入到上锁的方法里");
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		/*//模拟程序出错
		int a = 10/0;  //遇到异常,导致锁没有释放,采用try finally
						//使用try finally,抛两次异常,因为在遇到异常后执行了finally释放了锁。
						 */
		
		method();//无穷递归
	
		}finally {
		//显示写此处释放锁
		lock.unlock();
		}
	}
}
class MyTask implements Runnable{

	Test obj ;
	
	public MyTask(Test obj) {
		this.obj = obj;
	}
	@Override
	public void run() {
		//synchronized(obj) {//Lock锁不能在此处写。很明确对谁上锁。充当锁对象
		//}
		obj.method();
	}
}
class MyTask2 implements Runnable{

	Test obj ;
	
	public MyTask2(Test obj) {
		this.obj = obj;
	}
	@Override
	public void run() {
		obj.method();
	}
}

读写锁

  • ReentrantReadWriteLock:

    一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
    支持多次分配读锁,使多个读操作可以并发执行。
    
  • 互斥规则

    写-写:互斥,阻塞。
    读-写:互斥,读阻塞写、写阻塞读。
    读-读:不互斥、不阻塞。
    在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
    
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
//写和读都用lock锁,性能太低
//换读写锁,ReentrantReadWriteLock 写同步 读异步
public class TestReadWriteLock {
	public static void main(String[] args) {
		Student stu = new Student();//共享资源对象
		
		ExecutorService es = Executors.newFixedThreadPool(20);
		 
		WriteTask write = new  WriteTask(stu);//写线程任务
		ReadTask read = new  ReadTask(stu);//读线程任务
		//执行两个赋值的线程任务
		long start = System.currentTimeMillis();//开始时间,毫秒值
		//但是此时主线程没停止,时间不准确
		es.submit(write);
		es.submit(write);
		
		for(int i = 1;i<=18;i++) {
			es.submit(read);
		}//当读也加锁时,读一个1秒,共18秒,性能下降。读不应该同步,读应该异步,因为不会对数据修改
		
		//ExecutorService 中的方法
		//停止线程池,但是不停止已经提交的任务,等已提交任务都执行完
		es.shutdown();
		
		//询问线程池,任务结束了嘛?
		while(true) {
			System.out.println("结束了嘛?");
			if(es.isTerminated()==true) {//证明线程任务都执行完毕
				break;
			}
		}
		long end = System.currentTimeMillis();//结束时间
		System.out.println(end-start);
		
//		try {
//			Thread.sleep(2000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		
		//System.out.println(stu.getAge());
	}
}
class Student{
	private int age;
	//Lock lock = new ReentrantLock();
	ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
	ReadLock read = rrw.readLock();//读锁
	WriteLock write = rrw.writeLock();//写锁
	//读年龄
	public int getAge(int age) {//也加锁
		read.lock();
		try {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return age;
		}finally {
			read.unlock();
		}	
}
	//写年龄
	public void setAge(int age) {
		write.lock();
		try {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			this.age = age;
		}finally {
			write.unlock();
		}
	}
}
class WriteTask implements Callable{
	Student stu;

	public WriteTask(Student stu) {
		this.stu = stu;
	}
	public Object call() throws Exception {
		stu.setAge(100);
		return null;
	}
	
}
class ReadTask implements Callable{
	
	Student stu;

	public ReadTask(Student stu) {
		this.stu = stu;
	}
	public Object call() throws Exception {
		stu.getAge(100);
		return null;
	}
}

线程安全的集合
Collection体系集合下,除Vector以外的线程安全集合。
线程安全集合
Collections中的工具方法

  • Collections工具类提供了多个可以获得线程安全集合的方法

     public static <T> Collection<T> synchronizedCollection(Collection<T> c)
     public static <T> List<T> synchronizedList(List<T> list)
     public static <T> Set<T> synchronizedSet(Set<T> s)
     public static<K,V>Map<K,V>synchronizedMap(Map<K,V>m)
     public static <T>SortedSet<T>synchronizedSortedSet(SortedSet<T>s)
     public static<K,V>SortedMap<K,V>synchronizedSortedMap(SortedMap<K,V>m)
    
  • JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchronized实现。

Vecto中的add方法使用了synchronized方法

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
List<String> list = new ArrayList<String>();//0X1122233
List<String> safeList = Collections.synchronizedList(list);//0x34567
safeList.add("A");//SynchronizedList里的add方法。该方法里加了个锁
静态方法类名直接调用此方法传List类型的集合,其中SynchronizedList为Collections的静态内部类,不依赖外部类对象可直接通过类名访问,必要的功能组件。List下的实现类。将不安全的的集合传到有参构造方法里。最后将list传给SynchronizedList中定义的List<E> list,所以return回来的集合是SynchronizedList中定义的List <E> list
  public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

 static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
        private static final long serialVersionUID = -7754090372962971524L;

        final List<E> list;

        SynchronizedList(List<E> list) {
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
        .....
 		public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }

CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版读写分离
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用
  • 使用方式与ArrayList无异

CopyOnWriteArrayList alist = new CopyOnWriteArrayList();
alist.add(“A”);
jdk1.8底层代码:

 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);//拿到底层数组扩容
            newElements[len] = e;//newElements[0]=e
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    ...
      public E get(int index) {
        return get(getArray(), index);
    }

应用:

import java.util.concurrent.CopyOnWriteArrayList;

public class TestCopyOnWriteArrayList {
	public static void main(String[] args) {
		CopyOnWriteArrayList<String> alist = new CopyOnWriteArrayList<String>();
		//写操作
		alist.add("A");//将底层数组做一次复制,写的是新数组,写完操作后将新数组替换旧数组
		alist.add("B");//每调用一次,底层方法扩容一次
		//读操作
		alist.get(1);//读的是写操作完成之前的旧数组:写完之后,才能读到新数组的新值。
	}
}

CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
  • 如存在元素,则不添加(扔掉副本)
    JDK1.8底层:
	public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    ...
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }

//实现不重复

 public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

应用:

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

public class TestCopyOnWriteArrayList {
	public static void main(String[] args) {
		CopyOnWriteArrayList<String> alist = new CopyOnWriteArrayList<String>();
		//写操作
		alist.add("A");//将底层数组做一次复制,写的是新数组,写完操作后将新数组替换旧数组
		alist.add("B");//每调用一次,底层方法扩容一次
		//读操作
		alist.get(1);//读的是写操作完成之前的旧数组:写完之后,才能读到新数组的新值。
		
		//无序、无下标、不允许重复
		CopyOnWriteArraySet<String> aset = new CopyOnWriteArraySet<String>();
		//写操作,表面使用的是add方法,底层实际是用的CopyOnWriteArrayList的addIfAbsent()来判断要插入的新值是否存在
		aset.add("A");
		aset.add("B");
		aset.add("C");
		aset.add("E");
		aset.add("F");
		aset.add("G");
		for(String s:aset) {
			System.out.println(s);
		}
	}
}

ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。
	//分段锁设计 Segment 1.7的语法
	//CAS交换算法和同步锁 JDK1.8 V:要更新的变量  E:预期值   N:新值
	ConcurrentHashMap<String,String> map = new ConcurrentHashMap<String,String>();
	map.put("A","skfjhd");

Queue接口(队列)

  • Collection的子接口,表示队列FIFO(First In First Out)
  • 常用方法:
    抛出异常:
    boolean add(E e)//顺序添加一个元素(到达上限后,在添加则会抛出异常)
    E remove()//获得第一个元素并移除(如果队列没有元素时,则抛出异常)
    E element()//获取第一个元素但不移除(如果队列没有元素时,则抛出异常)
    返回特殊值:推荐使用
    boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会返回false)
    E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null)
    E keep()//获得第一个元素但不移除(如果队列没有元素时,则返回null)
import java.util.LinkedList;
import java.util.Queue;

public class TestQueues {
	public static void main(String[] args) {
		//Queue<String> q = new 
		//列表,尾部添加 (指定下标)
		//链表:头尾添加
		//队列:FIFO
		//可以使用接口指向实现类对象
		LinkedList<String>  link = new LinkedList<String>();//链表 
		link.offer("A");
		link.offer("B");
		link.offer("C");
		
		//用列表的方式打乱了FIFO队列的规则
		link.add(0,"D");
		
		System.out.println(link.peek());//
		
	}
}

ConcurrentLinkedQueue

  • 线程安全、可高效读写的队列,高并发下性能最好的队列。
  • 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
  • V: 要更新的变量 E: 预期值 N:新值
  • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
//严格遵守了队列的规则,其线程是安全的,采用了CAS算法
		Queue<String> q = new ConcurrentLinkedQueue<String>();
		//1.抛出异常  2.返回结果的
		q.offer("A");
		q.offer("B");
		q.offer("C");
		q.poll();//删除表头
		System.out.println(q.peek());

BlockingQueue接口(阻塞队列)

  • Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。

  • 方法:
    void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。
    E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。

  • 可用于解决生产者、消费者问题

阻塞队列

  • ArrayBlockingQueue:
    数组结构实现,有界队列。(手工固定上限)

  • LinkedBlockingQueue:
    链表结构实现,无界队列。(默认上限Integer.MAX_VALUE)

BlockingQueue<String> bq = new ArrayBlockingQueue<String>(3);//手动固定队列上限
BlockingQueue<String> bql = new LinkedBlockingQueue<String>();//无界队列。最大有Integer.MAX_VALUE
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值