什么是同步

  • 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条线程访问,一条线程在执行一个循环的过程中被中断,下一个线程则出现错误
  • 因此,线程任务中可能引起错误的地方应当被一次执行完毕

同步代码块

  • 用同步代码块改写上面的代码
  1. package testpack;
  2. public class Test1 {
  3. public static void main(String[] args){
  4. System.out.println("现在是主线程: "+Thread.currentThread());
  5. System.out.println("下面新建两个线程");
  6. A a=new A(500);
  7. new Thread(a,"线程A").start();
  8. new Thread(a,"线程B").start();
  9. new Thread(a,"线程C").start();
  10. }
  11. }
  12. class A implements Runnable{
  13. private int tickets;
  14. A (int tick){
  15. tickets=tick;
  16. }
  17. private Object obj=new Object(); //同步监视器
  18. public void run() {
  19. synchronized(obj){ //同步代码块
  20. for (;tickets>0;tickets--) {
  21. System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
  22. try{
  23. Thread.sleep(1); //让当前线程暂停1毫秒,其他线程也不能执行该同步代码块
  24. }catch(InterruptedException ex){
  25. ex.printStackTrace();
  26. }
  27. if (tickets==1){
  28. System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
  29. }
  30. }
  31. }
  32. }
  33. }
  • 同步监视器,就是一个普通的对象,就像一把锁,只有获得了同步监视器的线程才能执行同步代码块。
  • 同步代码块执行一次完毕后,将会释放锁,接下来是这条线程拿到同步锁,还是其他其他线程,则不一定,根据线程调度而定,但是在同步代码块执行过程中,不会被中断,一个同步任务会被一次执行完毕

同步方法

  • 在同步代码块中,synchonized关键字在run()方法内部,修饰的是一段代码,也可以用来修饰run()方法,也就是同步方法
  • synchronized不只可以修饰run()方法,还可以修饰其他方法,只要是需要一次同步完成的任务,然后再在run()方法中被调用
  • 同步方法中有一个隐式的同步监视器,就是this,也就是调用run()方法(或同步方法)的这个对象
  • 还是上面的实例,用同步方法改写
  1. package testpack;
  2. public class Test1 {
  3. public static void main(String[] args){
  4. System.out.println("现在是主线程: "+Thread.currentThread());
  5. System.out.println("下面新建两个线程");
  6. A a=new A(500);
  7. new Thread(a,"线程A").start();
  8. new Thread(a,"线程B").start();
  9. new Thread(a,"线程C").start();
  10. }
  11. }
  12. class A implements Runnable{
  13. private int tickets;
  14. A (int tick){
  15. tickets=tick;
  16. }
  17. public synchronized void run() { //同步方法
  18. for (;tickets>0;tickets--) {
  19. System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
  20. try{
  21. Thread.sleep(1);
  22. }catch(InterruptedException ex){
  23. ex.printStackTrace();
  24. }
  25. if (tickets==1){
  26. System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
  27. }
  28. }
  29. }
  30. }

释放同步监视器

  • 当前线程的同步任务(同步方法、同步代码块)执行完毕
  • 在同步任务中,遇到break、return,终止了同步任务
  • 在同步任务中,出现Error、Exception等,导致同步任务结束
  • 在同步任务中,执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器
  • 不会释放同步监视器的情况:
    • 同步任务中,调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
    • 同步任务中,其他线程调用了该线程的suspend()方法将该线程挂起

同步锁

  • 除了可以用new Object()和this作同步监视器往外,还可以定义专门的同步锁,且功能更加强
  • Lock接口
    • ReentrantLock实现类
  • ReadWriteLock
    • ReentrantReadWriteLock实现类
    • ReentrantReadWriteLock.ReadLock
    • ReentrantReadWriteLock.WriteLock
  • StampedLock
  • 示例:用ReentrantLock改写上面的代码
  1. package testpack;
  2. import java.util.concurrent.locks.ReentrantLock;
  3. public class Test1 {
  4. public static void main(String[] args){
  5. System.out.println("现在是主线程: "+Thread.currentThread());
  6. System.out.println("下面新建两个线程");
  7. A a=new A(50);
  8. new Thread(a,"线程A").start();
  9. new Thread(a,"线程B").start();
  10. new Thread(a,"线程C").start();
  11. }
  12. }
  13. class A implements Runnable{
  14. private final ReentrantLock lock=new ReentrantLock(); //定义一个同步锁
  15. private int tickets;
  16. A (int tick){
  17. tickets=tick;
  18. }
  19. public void run() {
  20. lock.lock(); //加锁
  21. for (;tickets>0;tickets--) {
  22. System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
  23. if (tickets==1){
  24. System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
  25. }
  26. }
  27. lock.unlock(); //释放锁
  28. }
  29. }

死锁

  • 两个线程各拿一把锁,下一步运行都需要对方手里那把锁,但都拿不到,则造成死锁,程序不能继续执行
  1. package testpack;
  2. public class Test1 {
  3. public static void main(String[] args){
  4. DeadLock dl=new DeadLock();
  5. new Thread(dl).start();
  6. dl.init();
  7. }
  8. }
  9. class DeadLock implements Runnable {
  10. A a=new A();
  11. B b=new B();
  12. public void init(){
  13. a.a1(b);
  14. System.out.println("进入主线程");
  15. }
  16. public void run(){
  17. b.b1(a);
  18. System.out.println("进入子线程");
  19. }
  20. }
  21. class A {
  22. public synchronized void a1(B b){
  23. System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行a1()");
  24. try{
  25. Thread.sleep(10);
  26. }catch(InterruptedException ex){
  27. ex.printStackTrace();
  28. }
  29. System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用b2()");
  30. b.b2(); //b2方法是同步方法,调用该方法要对调用的对象b加锁
  31. }
  32. public synchronized void a2(){
  33. System.out.println("这是a2()方法");
  34. }
  35. }
  36. class B{
  37. public synchronized void b1(A a){
  38. System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行b1()");
  39. try{
  40. Thread.sleep(10);
  41. }catch(InterruptedException ex){
  42. ex.printStackTrace();
  43. }
  44. System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用a2()");
  45. a.a2(); //a2方法是同步方法,调用该方法要对调用的对象a加锁
  46. }
  47. public synchronized void b2(){
  48. System.out.println("这是b2()方法");
  49. }
  50. }
  • 上面在调用a.a2()和b.b2()方法时,分别要对a对象和b对象加锁,但这时,a、b对象的锁都在对方手里,造成两个线程阻塞

其他

  • 可变类的线程安全是以降低程序的运行效率为代价的
  • 不要对线程安全类的所有方法都进行同步,只对那些改变共享资源的方法进行同步
  • 如果一个类有单线程和多线程运行环境,那么应该提供两种版本,就是StringBuilder(单线程)和StringBuffer(多线程)一样

0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁的更多相关文章

  1. 线程同步 synchronized 同步代码块 同步方法 同步锁

    一 同步代码块 1.为了解决并发操作可能造成的异常,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块.其语法如下: synchronized(obj){ // ...

  2. 0023 Java学习笔记-面向对象-初始化代码块

    初始化代码块 在18篇-类的基本要素中说到,类的三大成员:成员变量.构造方法.方法,初始化代码块是类的第4个成员 初始化块用于对类或者对象的初始化, 一个类的初始化块可以有0-多个,按先后顺序执行 跟 ...

  3. Java学习笔记 - 类方法与代码块的执行顺序

    类的初始化顺序 使用一个简单的父子类例子来做示范,代码执行顺序在代码后有标注. class Parent { public static String p_StaticField = "父类 ...

  4. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  5. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

  6. 线程的同步机制:同步代码块&同步方法

    解决存在的线程安全问题:打印车票时出现重票,错票 使用同步代码块的解决方案 TestWindow2 package com.aff.thread; /* 使用实现Runnable接口的方式,售票 存在 ...

  7. java学习笔记 --- 多线程(线程安全问题——同步代码块)

    1.导致出现安全问题的原因: A:是否是多线程环境 B:是否有共享数据 C:是否有多条语句操作共享数据 2.解决线程安全问题方法: 同步代码块: synchronized(对象){ 需要同步的代码; ...

  8. 0038 Java学习笔记-多线程-传统线程间通信、Condition、阻塞队列、《疯狂Java讲义 第三版》进程间通信示例代码存在的一个问题

    调用同步锁的wait().notify().notifyAll()进行线程通信 看这个经典的存取款问题,要求两个线程存款,两个线程取款,账户里有余额的时候只能取款,没余额的时候只能存款,存取款金额相同 ...

  9. JAVA学习笔记 -- 多线程之共享资源

    在多线程程序执行过程中,可能会涉及到两个或者多个线程试图同一时候訪问同一个资源.为了防止这样的情况的发生,必须在线程使用共享资源时给资源"上锁",以阻挡其他线程的訪问. 而这样的机 ...

随机推荐

  1. myeclipse学习总结二(myeclipse安装svn插件)

    1.在myeclipse中要安装svn插件,首先是下载svn插件. 下载地址:http://subclipse.tigris.org/servlets/ProjectDocumentList?fold ...

  2. 导入 cocoapods引入的第三方库头文件,提示找不到

    解决办法: 1,Build Settings ->Header Search Paths 2, 双击 Header Search Paths  添加一个, $(PODS_ROOT), 选择项选: ...

  3. Maven pom文件常用配置,转载

    什么是POM Project Object Model,项目对象模型.通过xml格式保存的pom.xml文件.作用类似ant的build.xml文件,功能更强大.该文件用于管理:源代码.配置文件.开发 ...

  4. WCF学习之旅—WCF服务配置(十四)

    一.概述 我们在前面章节中讲了寄宿,在前面的实例中也用到了配置文件,这一篇主要讲讲如何在应用配置文件,提高WCF程序的灵活性.在编写WCF服务应用程序时,编写配置项也是其中一项主要工作,在前面的几个示 ...

  5. ZooKeeper安装与配置

    一. 单机安装.配置: 1. 下载zookeeper二进制安装包下载地址:http://apache.dataguru.cn/zookeeper/zookeeper-3.4.3/zookeeper-3 ...

  6. JavaScript的同步与异步

    1.手绘一张图说明. 2.为什么JavaScript是单线程(这里引用阮一峰老师的话) JavaScript的单线程,与它的用途有关. 作为浏览器脚本语言,JavaScript的主要用途是与用户互动, ...

  7. MVC5 网站开发之三 数据存储层功能实现

    数据存储层在项目Ninesky.DataLibrary中实现,整个项目只有一个类Repository.   目录 奔跑吧,代码小哥! MVC5网站开发之一 总体概述 MVC5 网站开发之二 创建项目 ...

  8. 深入分析Spring 与 Spring MVC容器

    1 Spring MVC WEB配置 Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext类扩展ApplicationContext, ...

  9. .NET Core下的日志(2):日志模型详解

    NET Core的日志模型主要由三个核心对象构成,它们分别是Logger.LoggerProvider和LoggerFactory.总的来说,LoggerProvider提供一个具体的Logger对象 ...

  10. iOS引入JavaScriptCore引擎框架(二)

    为何放弃第一种方案 UIWebView的JSContext获取     上篇中,我们通过简单的kvc获取UIWebVIew的JSContext,但是实际上,apple并未给开发者提供访问UIWebVi ...