并发编程1

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();

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值