Java并发编程

    这几天看了《实战java高并发程序设计》这本书,在此做个简短的记录。首先是Java并发中的同步控制组件。

1.volatile

        当你用 volatile去申明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改。为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。但是volitile并不能保证一些复合操作的原子性。

2.synchronized

        关键字 synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每次,只能有一个线程进入同步块,从而保证线程间的安全性。
        关键字 synchronized可以有多种用法。这里做一个简单的整理。

  • 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run(){
        for(int j=0;j<1000000;j++){
            synchronized(instance){
            i++;
            }
        }
    }
}
  • 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁
public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run(){
        for(int j=0;j<1000000;j++){
            increase();
        }
    }

    private synchronized void increase(){
        i++;
    }
    
    public static void main(String[] args){
        AccountingSync instance=new AccountingSync();
        Thread thread1=new Thread(instance);
        Thread thread2=new Thread(instance);
        thread1.start();
        thread2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

        此时需要注意的是两个线程必须同时指向同一个Runnable对象,若是指向同一个类的两个不同对象便不能够达到加锁的效果,因为此时锁的粒度是针对一个对象的。

  • 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

        当两个线程指向同一个Runnable接口的两个不同对象时,如果要达到加锁的效果,只要将加锁的方法改成static静态方法便可以了,此时锁是针对这个类,而不是单个对象。

public static synchronized void increase(){
    i++;
}

3.重入锁--ReentrantLock

        与 synchronized相比,重入锁有着显示的操作过程。开发人员必须手动指定何时加锁,何时释放锁,因此重入锁的灵活性要高于synchronized,但是在这要声明,synchronized也是支持重入的。

public class ReentrantLockDemo implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
 
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo demo=new ReentrantLockDemo();
        Thread thread1=new Thread(demo);
        Thread thread2=new Thread(demo);
 
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(i);
    }
}

4.Condition--条件变量

        如果大家理解了 Object.wait()和 Object.notify()方法的话,那么就能很容易地理解 Condition对象了。它和 wait()和 notify()方法的作用是大致相同的。但是 wait()和notify()方法是和synchronized关键字合作使用的,而Condtion是与重入锁相关联的。

5.信号量--semaphore

        信号量为多线程协作提供了更为强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁 synchronized还是重入锁 Reentrantlock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。

6.读写锁--ReadWriteLock

        ReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效地帮助减少锁竞争,以提升系统性能。读写锁允许多个线程同时读,,但是,考虑到数据完整性,写写操作和读写操作间依然是需要相互等待和持有锁的。

  • 读-读不互斥:读读之间不阻塞
  • 读-写互斥:读阻塞写,写也会阻塞读

写-写互斥:写写阻塞

7.倒计时器--CountDownLatch

        Countdownlatch是一个非常实用的多线程控制工具类。“ Count Down”在英文中意为倒计数, Latch为门栓的意思。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。

        CountDownLatch只提供了一个构造器:

public CountDownLatch(int count){}

        然后下面这3个方法是CountDownLatch类中最重要的方法:

//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { }; 
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };  

        下面的例子能够比较清晰的介绍CountDownLatch的用法: 

public class CountDownLatchTest {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                    Thread.sleep(3000);
                    System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");
                     Thread.sleep(3000);
                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
 
         try {
             System.out.println("等待2个子线程执行完毕...");
            latch.await();
            System.out.println("2个子线程已经执行完毕");
            System.out.println("继续执行主线程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }
}

8.循环栅栏--CyclicBarrier

        CyclicBarrier是另外一种多线程并发控制实用工具。和 CountDownLatch非常类似,它也可以实现线程间的计数等待,但它的功能比 CountDownLatch更加复杂且强大。CyclicBarrier可以理解为循环栅栏。前面 Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏内在的含义。

        CyclicBarrier提供2个构造器:

public CyclicBarrier(int parties, Runnable barrierAction) {}
public CyclicBarrier(int parties) {}

        参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:

 

public int await() throws InterruptedException, BrokenBarrierException {}
public int await(long timeout, TimeUnit unit)throws             
    InterruptedException,BrokenBarrierException,TimeoutException {}

        下面举一个例子来详细介绍 CyclicBarrier的详细用法:

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
            try {
                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有线程写入完毕,继续处理其他任务...");
        }
    }
}

9.线程工具阻塞类--LockSupport

       LockSupport是一个非常方便实用的线程阻塞工具,它可以在线程内任意位置让线程阻塞。和Thread.suspend()相比,它弥补了由于 resume在前发生,导致线程无法继续执行的情况。和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出 InterruptedException异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值