天天看点

JUC多线程1. volatile 关键字 内存可见性2. 原子变量与CAS算法3. 同步容器类 ConcurrentHashMap4.CountDownLatch闭锁5.实现Callable接口6.同步锁 Lock

JUC多线程

  • 1. volatile 关键字 内存可见性
  • 2. 原子变量与CAS算法
    • 原子变量
    • CAS算法
  • 3. 同步容器类 ConcurrentHashMap
  • 4.CountDownLatch闭锁
  • 5.实现Callable接口
  • 6.同步锁 Lock

1. volatile 关键字 内存可见性

  • 内存可见性问题:当多线程操作共享数据时,彼此不可见。(解决方法1:使用synchronize 同步 锁让每一次读取数据都从内存中去读 保证了数据的及时的更新,但是效率还是比较低;2:使用volatile关键字)
  • volatile作用:当多个线程操作共享数据时,可以保证内存中的数据是可见的。(相教于synchronized 是一种较为轻量级的同步策略)
  • volatile与synchronized不同点:1. volatile不具备“互斥性”(互斥性当前资源有且只能一个线程访问;2. volatile 不能保证变量的“原子性”)

2. 原子变量与CAS算法

原子变量

i++

的原子性问题:i++ 的操作实际包含了三个步骤“读 该 写”

  • 原子变量:在jdk1.5以后提供了

    java.util.concurrent.atomic

    包含了常用的原子变量AtomicXXX(原子变量:volatile的特性,它里面封装的变量有volatile修饰,故而内存可见性;CAS算法 compare-and-swap保证了数据的原子性。)

CAS算法

  • CAS算法 是硬件对于并发操作共享数据的共享支持
  • 包含了三个操作数 内存值 V 预估值 A 更新值 B
  • 特点:当且仅当V==A时,才V=B;否则,不做任何操作。
  • 每一次都先从内存值值取数据,(预估值与内存值进行比较,如果相同 才将更新的值 赋值非内存值,否则不做任何操作)

3. 同步容器类 ConcurrentHashMap

  • 在java5.0以后 在

    java.util.concurrent

    包下提供了许多种并发的容器来改进同步容器的性能
  • ConcurrentHashMap

    :是线程安全的 锁的分段机制 默认有16个级别的

    segment

    concurrentLevel

    每一个段都有一个独立的锁,支持同时多个线程同时访问,效率比hashtable高(增加了一个线程安全的hash表,分段锁代替了hashtable的独占锁,进而提高了性能)
    JUC多线程1. volatile 关键字 内存可见性2. 原子变量与CAS算法3. 同步容器类 ConcurrentHashMap4.CountDownLatch闭锁5.实现Callable接口6.同步锁 Lock

4.CountDownLatch闭锁

  • 在java 5.0在java.util.concurrent包中,提供了多种并发容器类来改进同步容器的性能
  • CountDownLatch一个同步的辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
  • 闭锁 可以延迟线程的进度知道到达终止状态,可以确保某些活动到其他的活动都完成才继续执行

计算10个线程的操作时间:

/**
 * 闭锁:先完成某些运算时,只有其他所有线程的运算全部完成时,当前的原酸才能继续执行
 * 
 */
public class TestCountDownLatch {
	public static void main(String[] args) {
		// 目的:计算10个线程的总时间

		// 错误示范1:下面的方式没有办法计算时间,一个主线程 10个分线程,只有等10个分线程都执行完了才计算他们的时间差,才能达到目的
		/*
		 * final CountDownLatch latch=new CountDownLatch(5); LacthDemo lacthDemo=new
		 * LacthDemo(latch); //开启多线程 计算10个线程的时间 long start=System.currentTimeMillis();
		 * for(int i=0;i<10;i++) { new Thread(lacthDemo).start();//开启10个线程 } long
		 * end=System.currentTimeMillis(); System.out.println("消耗的时间:"+(end-start));
		 */

		final CountDownLatch latch = new CountDownLatch(10);// 底层有一个count来控制,每此都要减少1,当为0的时候,就继续执行
		LacthDemo lacthDemo = new LacthDemo(latch);
		// 开启多线程 计算10个线程的时间
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10; i++) {
			new Thread(lacthDemo).start();// 开启10个线程
		}
		try {
			latch.await();// 要它等待,等他为0就继续执行
		} catch (InterruptedException e) {// 用于中断的
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("消耗的时间:" + (end - start));
	}
}

class LacthDemo implements Runnable {
	private CountDownLatch latch;
	public LacthDemo(CountDownLatch latch) {
		this.latch = latch;
	}
	@Override
	public void run() {
		// 但是多线程可能存在线程安全问题,故而加一个synchronize
		synchronized (this) {
			try {
				for (int i = 0; i < 5000; i++) {
					if (i % 2 == 0) {
						System.out.println(i);
					}
				}
			} finally {
				latch.countDown();// 买次执行完就减1,当为0的时候,就继续执行,且必须执行
			}
		}
	}
}
           

5.实现Callable接口

创建线程的有4种方式:

1. 继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。此方式没有返回值。

2. 实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。此方法2较之方法1好,将线程对象和线程任务对象分离开。降低了耦合性,利于维护。此方式没有返回值。

3. 创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。 创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。该方法使用Runnable功能更加强大的一个子类.这个子类是具有返回值类型的任务方法。

4. 线程池
           

Callable创建线程:

1.相较于Runnable:Callable可以有返回值,并且可以抛出异常

2.执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果,FutureTask是Future的实现类

//创建线程 
public class TestCallable {
	public static void main(String[] args) {
		ThreadDemo treadDemo=new ThreadDemo();
		//因为有返回值,执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果
		FutureTask<Integer> res=new FutureTask<>(treadDemo);
		new Thread(res).start();//启动线程
		
		try {
			//接受线程运算后的结果
			Integer sum=res.get();//当在线程的运行过程中,并不会执行,当new Thread(res).start()运行完成后才会执行
			//故而:FutureTask也用于闭锁的操作
			System.out.println(sum);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}	
	}
}

//class TreadDemo implements Runnable {
//	@Override
//	public void run() {
//		// TODO Auto-generated method stub
//	}
//}
/*
 * 1.相较于Runnable:Callable可以有返回值,并且可以抛出异常
 * 2.执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果,FutureTask是Future的实现类
 * 
 */
class ThreadDemo implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {//重写call()方法
		int sum=0;
		for (int i = 0; i<Integer.MAX_VALUE; i++) {
			System.out.println(i);
			sum+=i;
		}
		return sum;
	}	
}
           
  • 所在的包:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
           

6.同步锁 Lock

  • 用于解决多线程安全的方式
  1. 同步代码块 关键字 synchronized(隐示锁)
  2. 同步方法 关键字synchronized (隐示锁)
  3. jdk1.5以后 同步锁Lock 是显示锁,(需要通过

    lock()

    方式进行上锁,通过

    unlock()

    来释放锁)更加灵活,但是要注意调用unlock()释放锁,建lock实例,因为lock是接口,所以要使用来的实现类

    ReentrantLock

public class TestLock {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		new Thread(ticket, "1号窗口").start();
		new Thread(ticket, "2号窗口").start();
		new Thread(ticket, "3号窗口").start();
	}
}

class Ticket implements Runnable {
	private int tick = 100;
	private Lock lock = new ReentrantLock();// 创建lock实例,因为lock是接口,所以要使用来的实现类ReentrantLock

	@Override
	public void run() {
		while (true) {
			lock.lock();// 上锁
			try {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
				}
				if (tick > 0) {
					System.out.println(Thread.currentThread().getName() + ": 完成售票,余票为" + --tick);
				}
			} finally {
				lock.unlock();// 释放锁 必须有最好写到finally
			}
		}
	}
}
           
  • 类所在的包:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
           
  • 利用wait(),notify()等待唤醒机制解决生产消费者问题
//生产者 消费者案例
//生产者 消费者案例
public class TestProductAndConsume2 {
	public static void main(String[] args) {
		clerk clerk=new clerk();
		Productor productor=new Productor(clerk);
		Consumer consumer=new Consumer(clerk);
		//如果生产者消费者不使用等待唤醒机制 可能就会一段时间内一直消费 或生产 没有进行生产消费的过程
		new Thread(productor,"生产者A").start();
		new Thread(productor,"生产者C").start();
		new Thread(consumer,"消费者B").start();
		new Thread(consumer,"消费者D").start();
	}
}

//店员
class clerk{
	private int product=0;
	//进货
	public synchronized void get() {
		//假设最多可以存放30个商品
		while(product>=30) {//解决虚假唤醒  wait应该总是使用在while中
			System.out.println("产品满。。。");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+":"+ ++product);//进货
		this.notifyAll();
		
	}
	//出售
	public synchronized void sale() {
		while(product<=0) {
			System.out.println("缺货");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+":"+ --product);//进货
		this.notifyAll();
	
	}
}

//生产者
class Productor implements Runnable {//生产者可能有多个
	private clerk clerk;
	public Productor(clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}

//消费者
class Consumer implements Runnable{
	private clerk clerk;
	public Consumer(clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			clerk.sale();
		}
	}
}
           
  • 通过lock来完成等待唤醒机制,生产者消费者模型
  • Condition

    :可以控制通信,与

    wait,notify,notifyAll

    对应的分别是

    await,signal,singalAll

    具体代码:
//生产者 消费者案例
public class TestProductAndConsumeLock {
	public static void main(String[] args) {
		clerk clerk = new clerk();
		Productor productor = new Productor(clerk);
		Consumer consumer = new Consumer(clerk);
		// 如果生产者消费者不使用等待唤醒机制 可能就会一段时间内一直消费 或生产 没有进行生产消费的过程
		new Thread(productor, "生产者A").start();
		new Thread(productor, "生产者C").start();
		new Thread(consumer, "消费者B").start();
		new Thread(consumer, "消费者D").start();
	}
}

//店员
class clerk {
	private int product = 0;
	// 使用同步锁来解决
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();// 通过Condition来进行通信
	// 进货
	public void get() {
		lock.lock();
		try {
			// 假设最多可以存放30个商品
			while (product >= 30) {// 解决虚假唤醒
				System.out.println("产品满。。。");
//				try {
//					this.wait();
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				try {
					condition.await();// 等待
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":" + ++product);// 进货
			condition.signalAll();// 唤醒

		} finally {
			lock.unlock();
		}
	}

	// 出售
	public void sale() {
		lock.lock();
		try {
			while (product <= 0) {
				System.out.println("缺货。。。");
//				try {
//					this.wait();lock也有自己的等待唤醒
//					
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":" + --product);// 进货
			condition.signalAll();
		} finally {
			lock.unlock();
		}

	}
}
//生产者
class Productor implements Runnable {// 生产者可能有多个
	private clerk clerk;

	public Productor(clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			clerk.get();
		}
	}
}
//消费者
class Consumer implements Runnable {
	private clerk clerk;

	public Consumer(clerk clerk) {
		this.clerk = clerk;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			clerk.sale();
		}
	}
}
           

线程按序交替

1.A B C交替打印 2.假设A B C线程将自己的ID打印出来个10次,然后按照一定的顺序显示 3.A--打印5次 B打印15次 C--打印20次 并且按序交替

实现代码:

//A B C交替打印
//假设A B C线程将自己的ID打印出来个10次,然后按照一定的顺序显示
//A--打印5次   B打印15次 C--打印20次  并且按序交替
public class TestABCAlternate {
	public static void main(String[] args) {
		AlternateDemo alternateDemo=new AlternateDemo();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {
					alternateDemo.loopA(i);
				}
			}
		},"A").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i <=20; i++) {
					alternateDemo.loopB(i);
				}
			}
		},"B").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {
					alternateDemo.loopC(i);
					System.out.println("------------------");
				}
			}
		},"C").start();
	}
}
class AlternateDemo{
	private int number=1;//表示当前正在执行线程的标记
	private Lock lock=new ReentrantLock();//要实现按序交替 需要有锁
	//还需要线程之间进行通信
	private Condition conditionA=lock.newCondition();
	private Condition conditionB=lock.newCondition();
	private Condition conditionC=lock.newCondition();
	public void loopA(int totallLoop) {//打印的第几轮
		lock.lock();
		try {
			//1.判断线程是否应该是1,1为A打印  2--B,3--C
			if(number!=1) {
				//如果不该A打印 则自己就进行等待的操作
				try {
					conditionA.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 打印
			for (int i = 1; i <=5; i++) {
				System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.唤醒
			number=2;
			conditionB.signal();//唤醒2打印
		} finally {
			lock.unlock();
		}
	}
	public void loopB(int totallLoop) {//打印的第几轮
		lock.lock();
		try {
			//1.判断线程是否应该是1,1为A打印  2--B,3--C
			if(number!=2) {
				//如果不该A打印 则自己就进行等待的操作
				try {
					conditionB.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 打印
			for (int i = 1; i <=15; i++) {
			System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.唤醒
			number=3;
			conditionC.signal();//唤醒2打印
		} finally {
			lock.unlock();
		}
	}
	public void loopC(int totallLoop) {//打印的第几轮
		lock.lock();
		try {
			//1.判断线程是否应该是1,1为A打印  2--B,3--C
			if(number!=3) {
				//如果不该A打印 则自己就进行等待的操作
				try {
					conditionC.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			//2. number==1 打印
			for (int i = 1; i <=20; i++) {
			System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
			}
			//3.唤醒
			number=1;
			conditionA.signal();//唤醒2打印
		} finally {
			lock.unlock();
		}
	}
}
           

继续阅读