1、runable的出现是为了解决thread单继承的问题
java高并发三大知识点:
1、synchronizer
2、同步容器
3、ThreadPool,excutor
2. 并发编程的3个特性
2.1 原子性:
- 基本变量的读取和赋值是原子性的,结合上面的操作顺序,即可得知,因为每次read和write均是原子性的。但是java对于64位的变量,允许拆分成两部分的32位分别操作,因此对于double和long的读写,可能没有原子性。但实际上大多数jvm在具体实现时,也都将这两种类型的读写实现为原子性了。
- synchronized。必须先lock才能进入同步代码块,因此一个线程在unlock之前,其他线程无法进入同步代码块,保证了同步代码块的原子性。
2.2 可见性
- volatile。被volatile修饰的变量,在每次use之前,都需要先read和load,因此保证了每次读取的都是主内存的最新值;而在每次assign的时候,都需要store和write回主内存,因此保证了最新值可以刷新到主内存。
- synchronized。在同步代码块中,每次写变量,都会先lock住变量,然后清空工作内存中共享变量的值;写完变量之后,都会先将工作内存中的共享变量刷新到主内存中,然后unlock(关于synchronized的可见性,可以见:synchronized可见性)。
2.3 有序性
- volatile。JVM规定,在volatile之前的操作,不能重排序到volatile之后。
- synchronized。同步代码块对于不同线程来说串行进入的。
3、脏读
publicclassAccountimplementsRunnable{
Stringname;
doublebalance;
publicsynchronizedvoidset(Stringname,doublebalance){
this.name=name;
//这段时间就等同于业务执行时间,容易形成脏读。脏读形成,只是对写加锁,没有对读加锁。
//根据实际业务进行加锁。
try{
Thread.sleep(4000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
this.balance=balance;
}
publicdoublegetBalance(Stringname){
returnthis.balance;
}
publicstaticvoidmain(String[]args){
Accounta=newAccount();
a.set("zhangsan",100.0);
newThread(a).start();
System.out.println(a.getBalance("zhangsan"));
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(a.getBalance("zhangsan"));
}
@Override
publicvoidrun(){
}
}
4、锁重入
/**
*一个同步方法可以同步另外一个同步方法,一个线程已经拥有某个对象的锁
*再次申请的时候任然会得到该对象的锁。也就是说synchronized获得的锁可以重入的
*
*/
publicclassT{
synchronizedvoidm1(){
System.out.println("m1start");
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
m2();
}
synchronizedvoidm2(){
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println("m2start");
}
publicstaticvoidmain(String[]args){
newTT().m1();
}
/**
*这里说的是可能有子类继续父类的一个情形,锁依然可以重入
*/
}
classTTextendsT{
@Override
synchronizedvoidm1(){
System.out.println("childmstart");
super.m1();
System.out.println("childmend");
}
}
5、锁的异常处理
/**
*程序在执行过程中,如果出现异常,默认情况锁会被释放
*所以,在并发处理的过程中,有异常要多加小心,不然可能会发生情况不一致的情况
*比如,在一个webapp处理过程中,多个servlet线程共同访问同一个资源,这是如果异常处理不合适
*在第一个线程中抛出异常,其他线程就会进行同步代码区,有可能会访问到异常产生的时的数据;
*因此要非常小心的处理同步业务逻辑的异常
*/
publicclassT2implementsRunnable{
intcount=0;
synchronizedvoidm(){
System.out.println(Thread.currentThread().getName()+"start");
while(true){
count++;
System.out.println(Thread.currentThread().getName()+"count="+count);
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
if(count==5){
inti=1/0;
}
}
}
publicstaticvoidmain(String[]args){
T2t2=newT2();
Threadthread1=newThread(t2);
thread1.setName("t1");
thread1.start();
try{
TimeUnit.SECONDS.sleep(3);
}catch(InterruptedExceptione){
e.printStackTrace();
}
Threadthread2=newThread(t2);
thread2.setName("t2");
thread2.start();
}
@Override
publicvoidrun(){
m();
}
}
6、volatile
/**
*volatile关键字,使用一个变量在多线程可见。
*AB线程都用到一个变量,Java默认时A线程中保留一分copy,这样如果B线程修改了该
*变量,则A线程未必知道
*使用volatile关键字,会让线程都会督导变量的修改制
*
*使用volatile,将会强制所有线程都去堆内存中读取running的值
*可以阅读这篇文章进程更加深入的理解
*http://www.cnblogs.com/nexiyi/p/java_memory_and_thread.html
*volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
*也就是说volatile不能替代synchronized
*
*volatile保证了线程之间的可见性,并没有保证线程之间的原子性;
*synchronized保证了线程之间的可见性又保证了线程之间的原子性,但是性能比volatile低许多
*所有
*/
publicclassTVolatileimplementsRunnable{
volatileintcount=0;
voidm(){
for(inti=0;i<10000;i++){
count++;
}
}
publicstaticvoidmain(String[]args){
TVolatilet=newTVolatile();
ArrayList<Thread>list=newArrayList<>();
for(inti=0;i<10;i++){
list.add(newThread(t,"thread"+i));
}
for(Threadthread:list){
try{
thread.start();
thread.join();
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(t.count);
}
}
@Override
publicvoidrun(){
m();
}
}
7、原子类ATomicXXX
/**
*解决同样问题的更高效的方法,使用AtomXXX的原子类
*AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
*/
publicclassAtomClassimplementsRunnable{
AtomicIntegercount=newAtomicInteger(0);
@Override
publicvoidrun(){
m();
}
/*synchronized*/voidm(){
for(inti=0;i<10000;i++){
//ifcount.get()<1000
//TimeUnit.SECONDS.sleep(1);如果一个线程为999,另一个线程尽量为1000了。那么999的线程值就为1001了,
//所以几个线程还是存在原子性问题
count.incrementAndGet();
}
}
publicstaticvoidmain(String[]args){
AtomClasst=newAtomClass();
ArrayList<Thread>list=newArrayList<>();
for(inti=0;i<10;i++){
list.add(newThread(t,"thread"+i));
}
for(Threadthread:list
){
try{
thread.start();
thread.join();
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(t.count);
}
}
}
8、wait和notify
/**
*wait能够释放锁,notify不会释放锁,sleep也不会释放锁
wait和notify配套使用的原因是,当notify后不会立刻释放锁,当前线程依然会持有该锁,只有执行一次wait后,把锁释放;其他线程才能够获得锁
*/
publicclassMyContainer{
Listlist=newArrayList();
AtomicBooleanflag=newAtomicBoolean(false);
voidadd(Objecto){
list.add(o);
}
publicintsize(){
returnlist.size();
}
publicstaticvoidmain(String[]args){
MyCofntainermy=newMyContainer();
finalObjecto=newObject();
newThread(
newRunnable(){
@Override
publicvoidrun(){
for(inti=0;i<10;i++){
synchronized(o){
if(((MyContainer)my).size()!=5){
o.wait();
}
my.add(newObject());
if(flag){
break;
}
o.notify();
}
}
}
}
).start();
newThread(
newRunnable(){
@Override
publicvoidrun(){
synchronized(o){
o.notify();
if(my.size()==5){
System.out.println("已经保存2个,可以结束了");
flag=true;
}
o.wait();
}
}
}
).start();
}
}
9、Latch门闩
/*
*使用Latch(门闩)替代waitnotify进行通知
*好处是通信形式简单,同时可以指定等待实际
*使用await和countdown方法代替wait和notify
*CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行
*当不涉及同步,只是涉及通信的时候,使用synchronized+wait/notify就显得太中两人。这时应该考虑countDoenLatch/CyclicBarrier/semaphore
*/
CountDownLatch
1、类介绍
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
用给定的计数 初始化CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
2、使用场景
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
3、方法说明
countDown
public voidcountDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
await
public booleanawait(long timeout,
TimeUnit unit)
throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
- 由于调用 countDown() 方法,计数到达零;或者
- 其他某个线程中断当前线程;或者
- 已超出指定的等待时间。
如果计数到达零,则该方法返回 true 值。
如果当前线程:
- 在进入此方法时已经设置了该线程的中断状态;或者
- 在等待时被中断,
则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
参数:
timeout - 要等待的最长时间
unit - timeout 参数的时间单位。
返回:
如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException - 如果当前线程在等待时被中断
实例
publicclassMyContainer2{
volatileListlist=newArrayList();
publicvoidadd(Objecto){
list.add(o);
}
publicintsize(){
returnlist.size();
}
publicstaticvoidmain(String[]args){
MyContainer2c=newMyContainer2();
finalCountDownLatchlatch=newCountDownLatch(1);
newThread(newRunnable(){
@Override
publicvoidrun(){
System.out.println("t2启动");
if(c.size()!=5){
try{
latch.await(100,TimeUnit.SECONDS);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println("t2结束");
}
}
},"t2");
newThread(newRunnable(){
@Override
publicvoidrun(){
System.out.println("t1启动");
for(inti=0;i<10;i++){
c.add(newObject());
System.out.println("add"+i);
if(c.size()==5){
latch.countDown();
}
}
System.out.println("t1结束");
}
},"t1");
}
}
CyclicBarrier
/**
*CyclicBarrier是一个同步工具类,它允许一组线程在到达某个栅栏点(commonbarrierpoint)互相等待,
*发生阻塞,直到最后一个线程到达栅栏点,栅栏才会打开,处于阻塞状态的线程恢复继续执行.
*它非常适用于一组线程之间必需经常互相等待的情况。CyclicBarrier字面理解是循环的栅栏,
*之所以称之为循环的是因为在等待线程释放后,该栅栏还可以复用。
*/
publicclassCyclicBarrierTest{
publicstaticvoidmain(String[]args){
ExecutorServiceservice=Executors.newFixedThreadPool(5);
finalCyclicBarrierbarrier=newCyclicBarrier(4);//之前四个人进入游戏
for(inti=0;i<5;i++){//,最后进入的一个人又要等待4个人后才能进入游戏
service.execute(newPalyer("玩家"+i,barrier));
}
service.shutdown();
}
}
classPalyerimplementsRunnable{
privatefinalStringname;
privatefinalCyclicBarrierbarrier;
publicPalyer(Stringname,CyclicBarrierbarrier){
this.name=name;
this.barrier=barrier;
}
@Override
publicvoidrun(){
try{
System.out.println(name+"已准备,等待其他玩家准备。。。。");
TimeUnit.SECONDS.sleep(1+(newRandom().nextInt(3)));
barrier.await();
System.out.println(name+"已进入游戏");
TimeUnit.SECONDS.sleep(1+(newRandom().nextInt(3)));
}catch(InterruptedExceptione){
e.printStackTrace();
System.out.println(name+"已离开游戏");
}catch(BrokenBarrierExceptione){
e.printStackTrace();
System.out.println(name+"已立刻游戏");
}
}
}
Semaphore
Semaphore是计数信号量。Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。
其中参数permits就是允许同时运行的线程数目;
下面先看一个信号量实现单线程的例子,也就是permits=1:
package concurrent.semaphore;
importjava.util.concurrent.Semaphore;
public class Driver {
// 控制线程的数目为1,也就是单线程
private Semaphore semaphore = new Semaphore(1);
public void driveCar() {
try {
// 从信号量中获取一个允许机会
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " start at"+ System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " stop at"+ System.currentTimeMillis());
// 释放允许,将占有的信号量归还
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package concurrent.semaphore;
public class Car extends Thread{
privateDriver driver;
public Car(Driver driver) {
super();
this.driver= driver;
}
public void run(){
driver.driveCar();
}
}
public class Run {
public static void main(String[]args) {
Driver driver = new Driver();
for (int i = 0; i < 5; i++){
(new Car(driver)).start();
}
}
}
运行结果:
Thread-0 start at 1482664517179
Thread-0 stop at 1482664518179
Thread-3 start at 1482664518179
Thread-3 stop at 1482664519179
Thread-1 start at 1482664519179
Thread-1 stop at 1482664520179
Thread-4 start at 1482664520179
Thread-4 stop at 1482664521180
Thread-2 start at 1482664521180
Thread-2 stop at 148266452218
从输出可以看出,改输出与单线程是一样的,执行完一个线程,再执行另一个线程。
10、ReentranLock
/**
*reenttranLock用于替代synchronized本例中由于m1锁定this,只有m1执行完毕的时候,
*,m2才能执行,这里是复习synchronized最原始的语言
*
*使用reentranlock可以完成同样的功能
*需要注意的是,必须要手动释放锁
*使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放
*因此经常在finally中进行锁的释放
*
*使用reentranlock可以进行“尝试锁定”trylock,这样无法锁定,
*或者在指定时间内无法锁定,线程可以决定是否继续等待
*
*使用reentranlock还可以使用lockinterruptibly方法,可以对乡村unterrupt做出响应
*在一个线程等待锁的过程中可以被打断
*
*rerntranlock可以指定为公平锁,效率比较低
*/
publicclassReentranLock1implementsRunnable{
Locklock=newReentrantLock();
voidm1(){
for(inti=0;i<10;i++){
try{
lock.lock();
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
lock.unlock();
}
System.out.println(i);
}
}
/**
*使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
*可以根据trylock返回值来判定是否锁定
*也可以指定trylock的时间,由于trylock(time)抛出异常
*所以注意unlock的处理,必须放到finally
*/
voidm2(){
/*lock.lock();
System.out.println("m2.....");
lock.unlock();*/
booleanlocked=false;
try{
lock.tryLock(5,TimeUnit.SECONDS);//尝试锁定5秒
System.out.println("m2..."+locked);
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
if(locked){
lock.unlock();
}
}
}
publicstaticvoidmain(String[]args){
ReentranLock1lock1=newReentranLock1();
newThread(lock1).start();
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
Locklock=newReentrantLock();
Threadt1=newThread(newRunnable(){
@Override
publicvoidrun(){
lock.lock();
try{
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
System.out.println("t1end");
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
lock.unlock();
}
}
});
t1.start();
Threadt2=newThread(newRunnable(){
@Override
publicvoidrun(){
lock.lockInterruptibly();//可以对interrupt做出响应
try{
TimeUnit.SECONDS.sleep(5);
System.out.println("t2end");
}catch(InterruptedExceptione){
e.printStackTrace();
}finally{
lock.unlock();
}
}
});
t2.start();
try{
TimeUnit.SECONDS.sleep(1);
}catch(InterruptedExceptione){
e.printStackTrace();
}
t2.interrupt();//打断线程2的等待
}
@Override
publicvoidrun(){
m1();
}
}
classReetran5extendsThread{
privatestaticReentrantLocklock=newReentrantLock(true);//参数为true表示为公平锁,请对比输出结果
publicvoidrun(){
for(inti=0;i<100;i++){
lock.lock();try{
System.out.println(Thread.currentThread().getName()+"获得锁");
}finally{
lock.unlock();
}
}
}
publicstaticvoidmain(String[]args){
Reetran5r=newReetran5();
Reetran5r2=newReetran5();
r.start();
r2.start();
}
}