Java并发编程

线程可以调用interrupt方法请求终止进程,但线程被阻塞(如sleep)时不可终止,否则抛出java.lang.InterruptedException.

在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父
线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置为在MIN_ PRIORITY (在Thread类中定义为1)与MAX_ PRIORITY (定义为10)之间的
任何值。NORM _PRIORITY 被定义为5。

线程池

构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池( thread pool)。一个线程池中包含许多准备运行的
空闲线程。将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出
时,线程不会死亡,而是在池中准备为下一个请求提供服务。
另一个使用线程池的理由是减少并发线程的数目。创建大量线程会大大降低性能甚至使虚拟机崩溃。如果有一个会创建许多线程的算法,应该使用一个线程数“固定的”线程池以限制并发线程的总数。
执行器( Executors)类有许多静态工厂方法用来构建线程池:
在这里插入图片描述
以上方法将返回ExecutorService对象,该对象有:
1、execute(Runnable),执行一个任务,没有返回值。
2、submit(Runnable),提交一个线程任务,有返回值。

Callable接口

继承Thread和实现Runnable在执行完任务之后无法获取执行结果。
自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。

Future类方法:
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
isCancelled方法表示任务是否被取消成功
isDone方法表示任务是否已经完成
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回.如果任务被取消了将抛出异常.

public class CallableTest {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService executor=Executors.newCachedThreadPool();
		Future<Integer> result = executor.submit(new MyThread());
		System.out.println(result.isDone());
		System.out.println(result.get());
	}
}

class MyThread implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		int result=0;
		System.out.println("execute thread...");
		return result;
	}
	
}
FutureTask-不使用线程池时获取线程返回值
    @org.junit.Test
    public void test3() throws ExecutionException, InterruptedException {
        MyThread myThread=new MyThread();
        FutureTask<String> futureTask=new FutureTask<String>(myThread);
        Thread thread =new Thread(futureTask);
        thread.start();
        thread.join();
        System.out.println(futureTask.get());//获取线程返回值
    }

class MyThread implements Callable<String> {

    public String call() throws Exception {
        return "result";
    }
}
线程的状态

新建状态
当用new操作符创建-一个新线程时, 如new Thread®,该线程还没有开始运行。这意味
着它的状态是new。当-一个线程处于新创建状态时,程序还没有开始运行线程中的代码。在线程运行之前还有一些基础工作要做。

可运行
一旦调用start 方法,线程处于runnable状态。一个可运行的线程可能正在运行也叮能没
有运行,这取决于操作系统给线程提供运行的时间。(Java的规范说明没有将它作为-一个单独状态。一个正在运行中的线程仍然处于可运行状态。)
一旦一个线程开始运行,它不必始终保持运行。事实上,运行中的线程被中断,目的是
为了让其他线程获得运行机会。线程调度的细节依赖于操作系统提供的服务。抢占式调度系统给每一个可运行线程一个时间片来执行任务。当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程运行机会。

被阻塞和等待
当一个线程试图获取-一个内部的对象锁( 而不是java.util.concurrent库中的锁),而该
锁被其他线程持有,则该线程进人阻塞状态。当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态。
当线程等待另一个线程通知调度器一个 条件时,它自己进入等待状态。我们在第。在调用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent库中的Lock或Condition时,就会出现这种情况。实际上,被阻塞状态与等待状态是有很大不同的。
有几个方法有一个超时参数。调用它们导致线程进人计时等待( timed waiting) 状态。这一状态将一直保持到超时期满或者接收到适当的通知。

被终止
线程因如下两个原因之一而被终止:
●因为run方法正常退出而自然死亡。
●因为一个没有捕获的异常终止了run方法而意外死亡。

我们可以调用线程的stop方法杀死一个线程。但该方法不安全,已过时,可以调用thread.interrupt()终止线程,或thread.exit = true设置标志为结束线程.

在这里插入图片描述

线程池的状态
  1. RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处理。

  2. SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转变为 SHUTDOWN 状态。

  3. STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。

  4. TIDYING:

SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。
线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。
线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。

  1. TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为 TERMINATED 状态。

在这里插入图片描述






守护线程:为其他线程提供服务,thread.setDaemon(true)设置为守护线程.守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

并发的临界区操作

锁对象(ReentrantLock)和synchronized关键字,Semaphore等可以解决互斥问题.
如多个线程同时操作一个数:

public class UnsynchBankTest {
	public static final int NACCOUNTS=100;
	public static final double INITIAL_BALANCE=1000;
	public static final double MAX_AMOUNT=1000;
	public static final int DELAY=10;
	
	public static void main(String[] args) {
		Bank bank=new Bank(NACCOUNTS, INITIAL_BALANCE);
		for (int i = 0; i < NACCOUNTS; i++) {
			int fromAccount=i;
			Runnable r=()->{
				try {
					while(true) {
						int toAccount=(int)(bank.size()*Math.random());
						double amount=MAX_AMOUNT*Math.random();
						bank.transfer(fromAccount, toAccount, amount);
						Thread.sleep((long) ((int)DELAY*Math.random()));
					}
				} catch (InterruptedException e) {
					// TODO: handle exception
				}
			};
			Thread t=new Thread(r);
			t.start();
		}
	}

}

class Bank{
	private final double[] accounts;
	
	public Bank(int n,double initialBalance) {
		accounts=new double[n];
		Arrays.fill(accounts, initialBalance);
	}
	
	public void transfer(int from,int to,double amount) {
		if(accounts[from]<amount)
			return;
		System.out.println(Thread.currentThread());
		accounts[from]-=amount;
		System.out.printf("%10.2f from %d to %d",amount,from,to);
		accounts[to]+=amount;
		System.out.printf("Total Balance:%10.2f%n",getTotalBalance());
	}

	private double getTotalBalance() {
		double sum=0;
		for(double a:accounts)
			sum+=a;
		
		return sum;
	}
	
	public int size() {
		return accounts.length;
	}
	
}

输出结果:
在这里插入图片描述
未同步时使总金额数不正确.
使用对象锁:

class Bank{
	private final double[] accounts;
	private ReentrantLock bankLock=new ReentrantLock();
	
	public Bank(int n,double initialBalance) {
		accounts=new double[n];
		Arrays.fill(accounts, initialBalance);
	}
	
	public void transfer(int from,int to,double amount) {
		if(accounts[from]<amount)
			return;
		bankLock.lock();
		System.out.println(Thread.currentThread());
		accounts[from]-=amount;
		System.out.printf("%10.2f from %d to %d",amount,from,to);
		accounts[to]+=amount;
		System.out.printf("Total Balance:%10.2f%n",getTotalBalance());
		bankLock.unlock();
	}

	private double getTotalBalance() {
		double sum=0;
		for(double a:accounts)
			sum+=a;
		
		return sum;
	}
	
	public int size() {
		return accounts.length;
	}
	
}

运行结果:
在这里插入图片描述
锁保证了同时只有一个线程操作数据.
lock.newCondition返回Condition对象(条件对象),为什么要条件对象?因为该线程获得锁后可能还需要满足其他条件才能继续工作(比如还要获取其他独占资源),如果一直等下去该线程又占着一个锁,所以需要条件对象来阻塞该线程并释放锁.condition.await()阻塞该线程并释放对象锁,被阻塞的线程只能被其他线程唤醒,使用condition.signalAll()企图唤醒所有线程,condition.signal()随机唤醒线程.如果所有线程都被阻塞了,就没有任何线程能用condition对象唤醒其他线程,此时产生了死锁.

  • 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
  • 锁可以管理试图进人被保护代码段的线程。
  • 锁可以拥有一个或多个相关的条件对象。
  • 每个条件对象管理那些已经进人被保护的代码段但还不能运行的线程。

sychronized关键字
从1.0版开始,Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。换句话说,

public synchronized void method(){
	method body
}

等价于

public void method(){
	this.intrinsiclock.lock();
	try{
		method body
	}
	finally { this.intrinsiclock.unlock(); }
}

使用wait()和notifyAll()阻塞和唤醒线程.
例如解决什么存取款问题可加synchronized:

public synchronized void transfer(int from,int to,double amount) {
	...
}

synchronized不应该用在run方法上,否则失效,run方法随时可被中断.

有时使用一个对象的锁来实现额外的原子操作,实际上称为客户端锁定( clientside locking)。如:

public void transfer(int from,int to,double amount) {
	synchronized(new Object()){
		//临界区操作
	}
}

Object的创建仅仅使对象获得锁.

对象锁和synchronized只是保证同时只有一个线程操作临界区,并不是原子操作,该线程还是可能在操作临界区时被剥夺运行权,但剥夺它运行权的线程无法操作该临界区,等线程又获取运行权后继续操作临界区.

volatile
使用volatile关键字会强制将修改的值立即写入主存;
使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

原子操作
java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如,AtomicInteger 类提供了方法incrementAndGetdecrementAndGet,它们分别以原子方式将一个整数自增或自减。 这些操作可以认为不会被中断(虽然实际会被中断),原子操作可解决并发访问数据问题.
atomicInteger.compareAndSet(int expect, int update),如果atomicInteger是expect值则设置为update.
atomicInteger.get(),获取该值.
atomicInteger.updateAndGet(int x)更新并返回.

死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。

解决死锁问题的方法是:一种是用synchronized,一种是用Lock显式锁实现。

而如果不恰当的使用了锁,且出现同时要锁多个对象时,会出现死锁情况

例如:

public class DeadLockTest {
	static int resource1,resource2;//独占资源
	static ReentrantLock lock1=new ReentrantLock();
	static ReentrantLock lock2=new ReentrantLock();
	
	public static void main(String[] args) {
		new Thread(new ThreadA()).start();
		new Thread(new ThreadB()).start();
	}
}

class ThreadA implements Runnable{

	@Override
	public void run() {
		System.out.println("thread A start");
		DeadLockTest.lock1.lock();
		System.out.println("thread A lock 1");
		DeadLockTest.resource1=1;

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		DeadLockTest.lock2.lock();
		System.out.println("thread A lock 2");
		DeadLockTest.resource2+=DeadLockTest.resource1;
		DeadLockTest.lock1.unlock();
		System.out.println("thread A unlock 1");
		DeadLockTest.lock2.unlock();
		System.out.println("thread A unlock 2");
		System.out.println("thread A finish");
	}
	
}
class ThreadB implements Runnable{
	
	@Override
	public void run() {
		System.out.println("thread B start");
		DeadLockTest.lock2.lock();
		System.out.println("thread B lock 2");
		DeadLockTest.resource2=1;
		DeadLockTest.lock1.lock();
		System.out.println("thread B lock 1");
		DeadLockTest.resource1+=DeadLockTest.resource2;
		DeadLockTest.lock1.unlock();
		System.out.println("thread B unlock 1");
		DeadLockTest.lock2.unlock();
		System.out.println("thread B unlock 2");
		System.out.println("thread B finish");
	}
	
}

在这里插入图片描述
线程A锁住了1,线程B锁住了2,A等待B释放2,B等待A释放1,形成了永远等待的死锁.
解决以上问题的方法有多种,可以使用条件对象,信号量等.

使用条件对象

当没获取到其他锁时主动释放已获取的锁并等待.

使用条件对象可能又会产生死锁,当所有线程都被阻塞时就没有线程来执行condition.signal()来唤醒其他线程了,但其实死锁本身发生的概率就较小,有了condition.signal()后所有线程都被阻塞的概率就更小了.

public class DeadLockTest {
	static int resource1,resource2;//独占资源
	static ReentrantLock lock1=new ReentrantLock();
	static ReentrantLock lock2=new ReentrantLock();
	static Condition condition2=lock2.newCondition();
	
	public static void main(String[] args) {
		new Thread(new ThreadA()).start();
		new Thread(new ThreadB()).start();
	}
}

class ThreadA implements Runnable{

	@Override
	public void run() {
		System.out.println("thread A start");
		DeadLockTest.lock1.lock();
		System.out.println("thread A lock 1");
		DeadLockTest.resource1=1;

		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		DeadLockTest.lock2.lock();
		System.out.println("thread A lock 2");
		DeadLockTest.resource2+=DeadLockTest.resource1;
		DeadLockTest.lock1.unlock();
		System.out.println("thread A unlock 1");
		DeadLockTest.condition2.signal();//唤醒阻塞的线程B,需要在2unlock之前调用,否则报错java.lang.IllegalMonitorStateException
		DeadLockTest.lock2.unlock();
		System.out.println("thread A unlock 2");
		
		
		System.out.println("thread A finish");
	}
	
}
class ThreadB implements Runnable{
	
	@Override
	public void run() {
		System.out.println("thread B start");
		DeadLockTest.lock2.lock();
		System.out.println("thread B lock 2");
		DeadLockTest.resource2=1;
		if (DeadLockTest.lock1.isLocked()) {
			try {
				DeadLockTest.condition2.await();//如果获取不到1就阻塞,释放2
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		DeadLockTest.lock1.lock();
		System.out.println("thread B lock 1");
		DeadLockTest.resource1+=DeadLockTest.resource2;
		DeadLockTest.lock1.unlock();
		System.out.println("thread B unlock 1");
		DeadLockTest.lock2.unlock();
		System.out.println("thread B unlock 2");
		System.out.println("thread B finish");
	}
	
}

在这里插入图片描述

使用信号量

不使用lock,信号量可以指定去获取的超时时间,semaphore.tryAcquire(1, TimeUnit.SECONDS),如果超过一定时间还没获取到可以做一些处理:退出任务过一段时间再处理或先释放已占用的锁.

设置加锁时限

lock.tryLock(timeout, unit)可以设置加锁时限

顺序加锁

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就更不可能发生。

死锁检测

检测到死锁时回退所有相关线程,重新执行.

ThreadLocal

在Thread中有一个成员变量ThreadLocals,该变量的类型是ThreadLocalMap,也就是一个Map,它的键是threadLocal,值为就是变量的副本。通过ThreadLocal的get()方法可以获取该线程变量的本地副本,在get方法之前要先set,否则就要重写initialValue()方法。

ThreadLocal是一个线程局部变量.
threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据.
如果不同的线程对同一个ThreadLocal设置了不同的值,线程在获取时将获取到自己设置的值,线程之间不会相互影响.
ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。在用完ThreadLocal后一定要记得remove().
每个线程持有一个ThreadLocalMap对象。
ThreadLocal是采用哈希表的方式来为每个线程都提供一个变量的副本,单它采用开发地址法来解决哈希冲突.
ThreadLocal保证各个线程间数据安全,每个线程的数据不会被另外线程访问和破坏

线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式

//不同的线程设置num,然后获取
public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal<Integer> num=new ThreadLocal<>();
        AtomicInteger integer=new AtomicInteger(0);
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    num.set(integer.incrementAndGet());
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(num.get());
                    num.remove();//不用了就记得移除
                }
            }).start();
        }

    }

}

线程间的协作

阻塞队列

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
在这里插入图片描述
常用的有PriorityBlockingQueue,LinkedBlockingQueue,ArrayBlockingQueue.

同步器
CyclicBarrier

允许线程集等待直至其中预定数目的线程到达一个公共障栅( barrier),然后可以选择执行一个处理障栅的动作.
cyclicBarrier.await()使该线程等待.
等待的线程达到一定数目时执行.

public class CyclicBarrierTest implements Runnable{
	private CyclicBarrier barrier;
	public CyclicBarrierTest(CyclicBarrier barrier) {
		this.barrier=barrier;
	}
	public static void main(String[] args) {
		CyclicBarrier barrier=new CyclicBarrier(5);
		for (int i = 0; i < 5; i++) {
			new Thread(new CyclicBarrierTest(barrier)).start();
		}
	}
	@Override
	public void run() {
		try {
			System.out.println(Thread.currentThread()+" waiting");
			barrier.await();
			System.out.println(Thread.currentThread()+" running");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
对于一个CyclicBarrier对象barrier ,当barrier.await()调用次数达到指定数目之前将一直等待,直到barrier.await()调用次数达到后所有等待barrier对象的 线程开始执行.

CountDownLatch

一个或多个线程需要等待直到计数器为0时开始执行.
countDownLatch.countDown()计数器减一
countDownLatch.await()时线程一直等待直到计数器为0

public class CountDownLatchTest {
	//建造完地板、墙、门后才能开始建造房顶
	
	public static void main(String[] args) {
		CountDownLatch finishCount=new CountDownLatch(3);
		new Thread(new BuildDoor(finishCount)).start();
		new Thread(new BuildFloor(finishCount)).start();
		new Thread(new BuildWall(finishCount)).start();
		new Thread(new BuildTop(finishCount)).start();
	}
}

class BuildFloor implements Runnable{
	private CountDownLatch finishCount;
	public BuildFloor(CountDownLatch count) {
		finishCount=count;
	}
	@Override
	public void run() {
		System.out.println("building floor....");
		try {
			Thread.sleep(3000);
			System.out.println("floor finished");
			finishCount.countDown();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

class BuildWall implements Runnable{
	private CountDownLatch finishCount;
	public BuildWall(CountDownLatch count) {
		finishCount=count;
	}
	@Override
	public void run() {
		System.out.println("building wall....");
		try {
			Thread.sleep(3000);
			System.out.println("wall finished");
			finishCount.countDown();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

class BuildDoor implements Runnable{
	private CountDownLatch finishCount;
	public BuildDoor(CountDownLatch count) {
		finishCount=count;
	}
	@Override
	public void run() {
		System.out.println("building door....");
		try {
			Thread.sleep(3000);
			System.out.println("door finished");
			finishCount.countDown();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

class BuildTop implements Runnable{
	private CountDownLatch finishCount;
	public BuildTop(CountDownLatch count) {
		finishCount=count;
	}
	@Override
	public void run() {
		try {
			System.out.println("waiting to build top");
			finishCount.await();
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		System.out.println("building top....");
		try {
			Thread.sleep(3000);
			System.out.println("top finished");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

在这里插入图片描述

Semaphore

允许线程集等待直到被允许继续运行为止,通常用来限制访问资源的线程总数。

semaphore.acquire()请求获取信号量,在获取到之前将一直等待.
semaphore.release()释放信号量.

public class SemaphoreTest {
	//限制一个线程使用打印机
	public static void main(String[] args) {
		Semaphore semaphore=new Semaphore(1);
		for (int i = 0; i < 10; i++) {
			new Thread(new Print(semaphore)).start();
		}
	}
}

class Print implements Runnable{
	private Semaphore semaphore;
	public Print(Semaphore s) {
		semaphore=s;
	}
	@Override
	public void run() {
		try {
			semaphore.acquire();
			System.out.println(Thread.currentThread()+" printing");
			Thread.sleep(1500);
			System.out.println(Thread.currentThread()+" finish");
			semaphore.release();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

在这里插入图片描述

SynchronousQueue

同步队列.同步队列是一种将生产者 与消费者线程配对的机制。当一个线程调用SynchronousQueue的put方法时,它会阻塞直到另一个线程调用take方法为止。
take()将从队列中取出对象并返回,所以利用SynchronousQueue可以一个线程把对象交给另一个线程.

public class SynchronousQueueTest {
//生产面包,每生产一个要吃完后才能继续生产
	public static void main(String[] args) {
		SynchronousQueue<Bread> synchronousQueue=new SynchronousQueue<Bread>();
		new Thread(new Produce(synchronousQueue)).start();
		new Thread(new Customer(synchronousQueue)).start();
	}
}

class Bread{}

class Produce implements Runnable{
	private SynchronousQueue<Bread> synchronousQueue;
	public Produce(SynchronousQueue<Bread> s) {
		synchronousQueue=s;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= 10; i++) {
			try {
				Thread.sleep(500);
				synchronousQueue.put(new Bread());
				System.out.println("produced "+i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
	}	
}
class Customer implements Runnable{
	private SynchronousQueue<Bread> synchronousQueue;
	public Customer(SynchronousQueue<Bread> s) {
		synchronousQueue=s;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= 15; i++) {
			try {
				Thread.sleep(1500);
				synchronousQueue.take();
				System.out.println("eat "+i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
		
	}	
}

在这里插入图片描述

线程安全的数据结构

java.uilconcurrent包提供了映射、有序集和队列的高效实现: ConcurrentHashMap 、
ConcurrentSkipListMap、ConcurrentSkipListSet 和ConcurrentLinkedQueue。

HashMap是非线程安全的,高并发的put和get可能会出错,Hashtable使用synchronized来保证线程安全,但效率低下,ConcurrentHashMap高效有安全.
Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的。在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。
应该根据具体的应用场景选择合适的HashMap。

CopyOnWriteArrayList和CopyOnWriteArraySet
CopyOnWriteArrayList和CopyOnWriteArraySet是线程安全的集合,其中所有的修改线
程对底层数组进行复制。写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
CopyOnWrite并发容器用于读多写少的并发场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值