【Java并发学习】之线程的同步
2017-10-27 16:29
330 查看
【Java并发学习】之线程的同步
前言
在前面一个小节中,我们学习了线程的概念以及在Java中创建任务的方式,并且将任务委托给对应的线程进行执行,本小节我们主要来学习线程之间的关系之一的同步,包含临界区、临界资源、线程同步的两种主要方法线程的关系
从广义上来讲,线程之间有三种关系没有关系:多个线程之间相互独立,既不竞争资源,也没有任何的合作关系,只是各自完成自己的任务
竞争关系:两个及以上的线程之间存在对某个或者某些资源的竞争
合作关系:两个及以上的线程共同合作,完成某项任务
临界区及临界资源
学习线程之间的同步,必不可少会接触到临界区以及临界资源这两个概念,而线程之间存在竞争关系本质上就是由于临界资源的存在,而解决的方式就是使得多个线程之间能够序列化访问临界资源临界资源:临界资源指的是程序中会被多个线程共享的某个或者某些资源,可以是软件资源也可以是硬件资源,比如某个变量,某个数组,某个容器,打印机等等
临界区:临界区指的是访问临界资源的代码,同步操作的主要对象
线程的同步
线程同步是一个非常重要的概念,也是在并发编程中比不可少的关键操作,需要进行同步的本质原因在于,资源的有限,由于资源的数量少于线程的数量,于是线程在访问这些资源的时候需要进行同步处理,如果没有进行同步处理,或者同步处理时不恰当,轻则会导致数据出错,重则会出现严重的并发问题首先我们来看下没有进行同步处理所带来的后果
情景:假设现在一个公园有三个门,我们需要统计某个时刻公园里的人的总数,由于三个门的统计方式一样,所以我们可以直接采用相同的三个线程来进行统计即可
/** * 公园类,包含一个计数器,进入以及离开记录的操作 */ class Park{ private static int counter = 0; public void enter(){ counter++; } public void leave(){ counter--; } public int getCounter(){ return counter; } } /** * 公园的进出登记 */ class DoorWatcher implements Runnable{ private Park park; public DoorWatcher(Park park) { this.park = park; } @Override public void run() { while (true){ park.enter(); // 进入公园 try { Thread.sleep(1000);// 模式人留在公园中的操作 } catch (InterruptedException e) { e.printStackTrace(); } park.leave(); // 离开公园 } } }
从上面的操作可以看出,如果程序正常执行,那么每个时刻公园中的人数应该是总体上保持稳定的,毕竟每个人进入公园之后会离开公园
对应的测试类如下
public static void main(String[] args) throws InterruptedException { Park park = new Park(); // 模拟公园的门的计数器 int doorNumber = 3; Runnable jobs[] = new Runnable[doorNumber]; for (int i = 0; i < doorNumber; i++){ jobs[i] = new DoorWatcher(park); } // 执行对应的任务 ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < doorNumber; i++) { executor.submit(jobs[i]); } // 定时检查公园中的人数 while (true){ System.out.println("current number in the park is " + park.getCounter()); Thread.sleep(3000); // 每隔三秒检查一次 } }
测试的可能结果
current number in the park is 0 current number in the park is 2 current number in the park is 2 current number in the park is 2 current number in the park is 3 current number in the park is 3 .... current number in the park is 2 current number in the park is 1 current number in the park is 1 ....
执行测试代码之后,可能你会发现实际上程序的运行并不是想象中那样,而且不同次的运行可能结果还不一样,出问题的地方在于
counter++以及
counter--这两个操作,这两个操作在Java中并不是原子操作,关于原子操作,我们会在后面进行深入的学习,这两个操作都包含了取出数据,修改数据,写入数据这三个步骤,而如果没有进行同步处理,则在进行其中任何一个步骤的时候,当前线程可能被挂起,其他线程对counter进行修改,从而导致了数据的不一致,类似的情况还有很多,这里就不进行具体的分析。
由于出现问题的部分是对变量counter的操作,也就是说,这里的counter就是我们所说到的临界资源,而对应的enter以及leave方法则是对应的临界区,或者更详细的说
counter++,
counter--就是我们所指的临界区
解决线程同步问题的方法从广义上来讲只有一个,那就是序列化访问临界资源,也就是说,同一时刻只允许一个线程来对临界资源进行操作,这种方式有效地解决了同步问题,而具体的操作就是对临界区进行加锁处理
加锁的原理可以简单的理解为,某个线程要进入临界区之间,先申请对应的锁,如果获得该锁,则可以进入,并且将该锁上锁,离开临界区之后就将锁解开;如果没能申请到锁,说明当前时刻临界资源被其他线程占用,则自己进行阻塞,等待锁可以使用
同步方法之使用synchronized
synchronized时Java提供的一个重量级锁,或者称之为监视器,也称之为对象锁,可以用于修饰方法或者代码块,默认锁定的对象是this,也就是当前对象,也可以显示指定所要锁定的对象
修饰方法
class Park{ private static int counter = 0; public synchronized void enter(){ counter++; // ... } public synchronized void leave(){ counter--; // ... } // ... }
修饰代码块
class Park{ private static int counter = 0; public void enter(){ synchronized(this){ counter++; } // ... } public void leave(){ synchronized(this){ counter--; } // ... } // ... }
synchronizd的使用比较简单,只需要在需要进行同步的方法或者代码块加上该关键字即可,当然,synchronized还有一些比较复杂的原理,这个我们将在后面学习到
同步方法之使用locks
synchronized是在比较旧的JDK中所提供的用于同步的工具,在JDK5之后,还提供了另外的工具用于进行同步,即JUC中的Lockimport java.util.concurrent.locks.ReentrantLock; class Park{ private static int counter = 0; // 申请一个锁 private static Lock lock = new ReentrantLock(); public void enter(){ lock.lock();// 加锁 try { counter++; }finally { lock.unlock();//解锁 } } public void leave(){ lock.lock();// 加锁 try { counter--; }finally { lock.unlock();//解锁 } } public int getCounter(){ return counter; } }
从上面的代码中可以看到,使用Lock的操作比较繁琐,我们需要自己申请锁,并且在需要加锁的时候手动加锁,然后在离开的时候进行解锁,可能你会注意到使用时的
try...finally代码块,强烈建立在使用Lock的时候采用这种方式,因为在进行资源操作的时候,可能会发生异常,采用这种方式可以保证无论在什么时候都能将锁进行解锁,还记得
finally的作用吗?^_^
Lock的使用虽然比较繁琐,而且还需要自己手动加锁、解锁,但是Lock也有synchronized所不具备的特点,那就是灵活,关于这两者的具体区别,我们将在后面的内容中学习到
总结
本小节我们主要学习了线程同步的概念,临界资源、临界区的概念,没有加锁的可能带来的危害,以及常见的同步方式,synchronized的使用以及Lock使用相关文章推荐
- Java并发学习笔记(7)线程的同步 (锁)
- 学习和理解JAVA线程同步--生产者与消费者例子
- 线程高级应用-心得8-java5线程并发库中同步集合Collections工具类的应用及案例分析
- 线程高级应用-心得6-java5线程并发库中同步工具类(synchronizers),新知识大用途
- java线程学习5——线程同步之同步方法
- java线程学习5——线程同步之同步方法
- 学习和理解JAVA线程同步--生产者与消费者例子
- java----------线程学习同步锁
- Java核心知识点学习----多线程并发之线程间的通信,notify,wait
- java----------线程学习同步锁
- Java 并发(一)——线程和同步
- Java线程学习笔记之并发集合类
- Java学习经验(二):线程间的同步
- Java线程同步与单例模式的结合学习
- Java基础第十一天学习日记_线程、同步、死锁
- JAVA多线程并发同步,以及线程终止
- Java线程同步于并发
- 张孝祥java.concurrent线程并发学习笔记 - 传统线程回顾
- java学习笔记之线程并发库
- 张孝祥java.concurrent线程并发学习笔记 - concurrent简介