Spring中的事务

先抛问题

看以下示例1

@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {

    @Resource
    TeacherMapper teacherMapper;

    @Override
    @Transactional
    public void insertTeacher() {
        teacherMapper.insert(new Teacher(1L,"张三"));
        throw new RuntimeException("抛异常测试");
    }

    @Override
    public void method(){
        insertTeacher();
    }
}

@SpringBootTest
class TeacherServiceImplTest {

    @Resource
    TeacherService teacherService;

    @Test
    void method() {
        teacherService.method();
    }
}

是否会插入?

会插入,因为 method()方法没有事务注解,导致 insertTeacher方法事务不生效

###示例2

@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher> implements TeacherService {

    @Resource
    TeacherMapper teacherMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertTeacher() {
        teacherMapper.insert(new Teacher(1L,"张三"));
        throw new RuntimeException("抛异常测试");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void method(){
        teacherMapper.insert(new Teacher(2L,"李四"));
        try {
            insertTeacher();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

@SpringBootTest
class TeacherServiceImplTest {

    @Resource
    TeacherService teacherService;
    @Resource
    StudentService studentService;

    @Test
    void method() {
        teacherService.method();
    }
}

是否会回滚?

两者都插入

Hexo+github搭建静态博客(新手入门)

前言

能来到这,说明你应该对博客有了些认知。相信你应该熟悉WordPress吧,或者说以前就是用的WordPress搭建的博客。本文针对于初学者,教程十分详细。我已经无法吐槽网上那些建博客教程,不是不能用,就是缺很多东西,到最后装也装不好,也不知道哪错了。在这我把我建博客的步骤写下来。希望能你给你们一些帮助。

准备工作

  • window8.1开发平台 楼主是在window下装的,虽然说是最烂的开发平台,但还是蛮好用的
  • 安装node.js node官网下载最新版本,一路安装就好
  • 安装git get官网下载与你电脑相对应的版本,安装
  • github账号,用户名注册之后便不可修改,用来做博客的远程仓库的,
  • 域名(可不需要也行) 可以通过域名来访问你的博客

安装hexo

打开Git命令行,执行如下命令

$  npm install -g hexo-cli

初始化在电脑里建一个空文件夹(不要在C盘),我是在 E 盘建了一个 git 文件夹,然后右键打开 Git Bash Here,键入如下命令

Dreamer@Dream MINGW64 /d/test
$ hexo init #初始化文件夹,会在目录下生成一些文件,随后按照提示运行   
INFO Copying data to D:\test  
INFO  You are almost done! Don't forget to run 'npm install' before you   start blogging with Hexo!
 ##按照提示输入
$ npm install 耐心等待会,不要着急,然后运行下面命令
$ hexo server`
INFO  Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop.

表明Hexo Server已经启动了,在浏览器中打开 http://localhost:4000/, 这时可以看到Hexo已为你生成了一篇blog。你可以按Ctrl+C 停止Server。

安装hexo插件(当时我就是因为没有装这些插件,导致各种失败)

npm install hexo-generator-index --save
npm install hexo-generator-archive --save
npm install hexo-generator-category --save
npm install hexo-generator-tag --save
npm install hexo-server --save
npm install hexo-deployer-git --save
npm install hexo-deployer-heroku --save
npm install hexo-deployer-rsync --save
npm install hexo-deployer-openshift --save
npm install hexo-renderer-marked@0.2 --save
npm install hexo-renderer-stylus@0.2 --save
npm install hexo-generator-feed@1 --save
npm install hexo-generator-sitemap@1 --save

hexo常用命令

hexo new "文章名字" #生成一篇文章,在source目录下能看到,可简写  hexo n
hexo new page"pageName"  #新建页面
hexo generate  #生成静态页面至public目录   可简写 hexo g
hexo server    #开启预览访问端口			 可简写 hexo s
hexo deploy    #将.deploy目录部署到GitHub  可简写  hexo d
hexo help      # 查看帮助
hexo version   #查看Hexo的版本

配置并部署到github

首先登录github,创建你用户名对应的仓库,仓库名必须是如图格式,他们都说是必须,我也不知道为什么,你如果知道了原因告诉我声。

如图

配置ssh密钥参考 官方文档(可能需要翻墙才能访问)如果你英文不错的话可以去看这个,这个写的很详细。

SSH密钥是一种方法来确定受信任的计算机,而不涉及密码。下面的步骤将引导您完成生成SSH密钥并添加公共密钥到你的GitHub上的帐户。

  1. 生成ssh 密钥

    $ ssh-keygen -t rsa -b 4096 -C “your_email@example.com

  2. 输入要保存的文件的路径,保持默认即可 然后 回车

    Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]

  3. 输入密码,密码是暗文的,输入即可

    Enter passphrase (empty for no passphrase): [Type a passphrase]
    Enter same passphrase again: [Type passphrase again]

完成以上工作之后 去文件目录应该会有id_rsa 和id_rsa.pub两个官钥
id_rsa 是私有的,不可公开
id_rsa.put 可公开

去github仓库中把ssh密钥加入进去 打开头像下的 Setting 目录

点 Add SSH key
title 随你写
key 打开id_rsa.put 粘贴里面全部内容。保存即可

要配置的ssh-agent程序使用你的SSH密钥

  • 确保ssh 代理已启用

    $ ssh-agent -s

    $ eval $(ssh-agent -s)

  • 添加您的SSH密钥对的ssh-agent ,应该会让你输入密码

    $ ssh-add ~/.ssh/id_rsa (路径要改相应位置)

测试连接

$ ssh -T git@github.com
Hi zDream! You've successfully authenticated, but GitHub does not provide shell access.

出现上图所示既表示添加成功

配置文件并上传到远程仓库中

如图是我的配置文件,_config.yml

注意: 配置文件语法 xxx: content 冒号后的空格必不可少

# Hexo Configuration
## Docs: http://hexo.io/docs/configuration.html
## Source: https://github.com/hexojs/hexo/

# Site
title: 我的记忆
subtitle: 点点滴滴
description: 生命的足迹
author: Dream
language:
timezone:

# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: http://www.drayy.com
root: /
permalink: :year/:month/:day/:title/
permalink_defaults:

# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:

# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link: true # Open external links in new tab
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
  enable: true
  line_number: true
  auto_detect: true
  tab_replace:

# Category & Tag
default_category: uncategorized
category_map:
tag_map:

# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## http://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss

# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page

# Extensions
## Plugins: http://hexo.io/plugins/
## Themes: http://hexo.io/themes/
theme: jacman
stylus:
     compress: true

# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
  type: git
  repository: git@github.com:zDream/zDream.github.io.git
  branch: master
  message: '站点更新: \{\{ now("YYYY-MM-DD HH:mm:ss") \}\}'

每次部署都需要执行如下命令,如果不出错可以不clean,

hexo clean 
hexo g
hexo d

注意看命令后的那些代码,看有没有错误,如何上述全部都成功的话,

去访问你的博客应该就好了 http://zDream.github.com ,改成你的名字即可

三. 垃圾收集器

一,概述

垃圾收集器就是内存回收的具体实现。虚拟机包含的所有收集器如图:

展示了七种作用于不同分代的收集器,如果两个收集器之间存在 连线,说明可以搭配使用。所处的区域,表示属于新生代还是老年代收集器。

没有最好的收集器,更加没有万能的收集器,要能根据具体应用选取最合适的收集器。

二,Serial 收集器

最基本,发展历史最悠久的收集器。是一个单线程的收集器,单线程并不意味着它只会使用一个cpu或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,会经常 Stop TheWorld

基本上已经被淘汰的收集器,但依然是虚拟机运行在Client模式下的默认新生代收集器,简单而高效(与其他收集器的单线程比)

Stop TheWorld:JVM在后台自动发起和自动完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉,即GC停顿;
会带给用户不良的体验;

从JDK1.3到现在,从Serial收集器-》Parallel收集器-》CMS-》G1,用户线程停顿时间不断缩短,但仍然无法完全消除;

三,ParNew收集器

是Serial收集器的多线程版本。

四,Parallel Scavenge收集器

五,Serial Old收集器

六,Parallel Old收集器

七,CMS收集器

八,Gl收集器

四. synchronized同步语句块

1. 数据类型String 的常量池特性

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.print("AA");
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.print("AA");
    }
}

public class Service {
    public static void print(String stringParam) {
        try {
            synchronized (stringParam) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        Service service = new Service();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
运行效果
A
A
A
出现这样的情况原因是String的两个值都是AA,两个线程持有相同的锁,所以造成了B线程不能执行。因此在大多数情况下,同步sync代码块都不使用String作为锁对象。而改用其他,比如new Object()实例化一个Object对象,但不放入缓存中。
例子如下:
public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.print(new Object());
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.print(new Object());
    }
}

public class Service {
    public static void print(Object object) {
        try {
            synchronized (object) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        Service service = new Service();

        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
运行结果如下:
A
B
A
B
可以看到交替打印,说明持有的锁不是一个。

2. 同步sync方法无限等待与解决

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.methodA();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.methodB();
    }
}

public class Service {
    synchronized public void methodA() {
        System.out.println("methodA begin");
        boolean isContinueRun = true;
        while (isContinueRun) {
        }
        System.out.println("methodA end");
    }

    synchronized public void methodB() {
        System.out.println("methodB begin");
        System.out.println("methodB end");
    }
}

public class Run {
    public static void main(String[] args) {
        Service service = new Service();

        ThreadA athread = new ThreadA(service);
        athread.start();

        ThreadB bthread = new ThreadB(service);
        bthread.start();
    }
}	
运行结果:
methodA begin

线程B永远得不到运行的机会,锁死了。这时可以用同步块来解决,更改后的如下:
public class Service {
    Object object1 = new Object();

    public void methodA() {
        synchronized (object1) {
            System.out.println("methodA begin");
            boolean isContinueRun = true;
            while (isContinueRun) {
            }
            System.out.println("methodA end");
        }
    }

    Object object2 = new Object();

    public void methodB() {
        synchronized (object2) {
            System.out.println("methodB begin");
            System.out.println("methodB end");
        }
    }
}
运行结果如下:
methodA begin
methodB begin
methodB end

3. 多线程的死锁

死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁。在多线程技术中,死锁是必须避免的,因为这会造成程序的假死。
例子
public class DealThread implements Runnable {
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();

    public void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按 lock1->lock2 代码顺序执行了");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按 lock2->lock1代码顺序执行了");
                }
            }
        }
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            t1.setFlag("a");

            Thread thread1 = new Thread(t1);
            thread1.start();

            Thread.sleep(100);

            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
运行结果:
username = a
username = b
结论:死锁是程序的bug,在设计程序的时候要避免双方互相持有对方的锁的情况。
只要互相等待对方释放锁就有可能出现死锁。

4. 内置类与静态内置类

(1) 内置类与同步:实验1

在内置类中有两个同步的方法,但使用的是不同的锁,打印的结果也是异步的。

public class OutClass {
    static class Inner {
        public void method1() {
            synchronized ("其它的锁") {
                for (int i = 1; i <= 10; i++) {
                    System.out.println(Thread.currentThread().getName() + " i="
                            + i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }
            }
        }

        public synchronized void method2() {
            for (int i = 11; i <= 20; i++) {
                System.out
                        .println(Thread.currentThread().getName() + " i=" + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
        }
    }
}

public class Run {
    public static void main(String[] args) {
        final Inner inner = new Inner();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                inner.method1();
            }
        }, "A");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                inner.method2();
            }
        }, "B");

        t1.start();
        t2.start();
    }
}

运行结果:
A i=1
B i=11
B i=12
A i=2
A i=3
B i=13
B i=14
A i=4
B i=15
A i=5
B i=16
A i=6
B i=17
A i=7
B i=18
A i=8
B i=19
A i=9
B i=20
A i=10
由于持有不同的对象监视器,所以打印结果就是乱序的。

(2) 静态内置类与同步:实验2

public class OutClass {
    static class InnerClass1 {
        public void method1(InnerClass2 class2) {
            String threadName = Thread.currentThread().getName();
            synchronized (class2) {
                System.out.println(threadName + " 进入InnerClass1类中的method1方法");
                for (int i = 0; i < 10; i++) {
                    System.out.println("i=" + i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {

                    }
                }
                System.out.println(threadName + " 离开InnerClass1类中的method1方法");
            }
        }

        public synchronized void method2() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " 进入InnerClass1类中的method2方法");
            for (int j = 0; j < 10; j++) {
                System.out.println("j=" + j);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
            }
            System.out.println(threadName + " 离开InnerClass1类中的method2方法");
        }
    }

    static class InnerClass2 {
        public synchronized void method1() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " 进入InnerClass2类中的method1方法");
            for (int k = 0; k < 10; k++) {
                System.out.println("k=" + k);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {

                }
            }
            System.out.println(threadName + " 离开InnerClass2类中的method1方法");
        }
    }
}

public class Run {
    public static void main(String[] args) {
        final InnerClass1 in1 = new InnerClass1();
        final InnerClass2 in2 = new InnerClass2();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                in1.method1(in2);
            }
        }, "T1");
        Thread t2 = new Thread(new Runnable() {
            public void run() {
                in1.method2();
            }
        }, "T2");
        // //
        // //
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                in2.method1();
            }
        }, "T3");
        t1.start();
        t2.start();
        t3.start();
    }
}
运行结果如下:
T1 进入InnerClass1类中的method1方法
i=0
T2 进入InnerClass1类中的method2方法
j=0
i=1
j=1
i=2
j=2
i=3
j=3
i=4
j=4
i=5
j=5
i=6
j=6
i=7
j=7
i=8
j=8
i=9
j=9
T1 离开InnerClass1类中的method1方法
T3 进入InnerClass2类中的method1方法
k=0
T2 离开InnerClass1类中的method2方法
k=1
k=2
k=3
k=4
k=5
k=6
k=7
k=8
k=9
T3 离开InnerClass2类中的method1方法
结论:
同步代码块sync(class2)对class2上锁后,其他线程只能以同步的方式调用 class2中的静态同步方法。

5. 锁对象的改变

在将任何数据类型作为同步锁时,需要注意的是:是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。

二. 垃圾算法和HotSpot算法实现

一,对象已死吗?

在堆里存放着所有的java对象实例。如下算法判断哪个对象存活,哪个已经死去。

1. 引用计数算法

概念:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用时,计数器就减1;任何计数器为0的对象就是不可能再被使用的。

算法实现简单,效率最高。

2. 可达性分析算法

基本思想是通过一系统称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连时,则证明对象是不可用的。
在java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

3. 再谈引用

无论是引用计数还是可达性分析,判定对象是否存活都与引用有关。
在jdk1.2以前,java对引用的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,则称为这块内存代表着一个引用。然尔这样有一些缺点:我们希望能描述这样一种对象:当内存空间还足够时,则能保存在内存这中;如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。

所以在jdk1.2之后,java对这些引用进行了扩充。分为如下类型,引用强度逐渐减弱:

  • 强引用(Strong Reference):是指在程序代码中普遍存在的,只要强引用还存在,就永远不会回收到被引用的对象。比如 Object obj = new Object();
  • 软引用(Soft Reference):用来描述一些还有用但并非必须的对象。在发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次还没有足够的内存,才会抛出内存溢出异常
  • 弱引用(Weak Reference):用来描述非必须的对象。比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用(Phantom Reference):称为幽灵引用或者幻影引用,是最弱的一种引用。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

4. 回收方法区

方法区又被人称为永久代

在方法区中进行垃圾收集的性价比比较低;在堆中尤其是在新生代中,一次垃圾收集一般可以 回收70%-95%的空间。

永久代垃圾收集主要回收两部分内容:废弃常量和无用的类。判定一个常量是否废弃比较简单。而要判定一个类是否是无用的类的条件相对苛刻许多。类需要满足以下3个条件:

  1. 该类所有的实例都已经被回收,也就是java堆 中不存在该类的任何实例
  2. 加载该类的ClassLoader已经被回收
  3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

二. 垃圾收集算法

1. 标记-清除算法

算法分为标记和清除两个阶段:首先标记出需要回收的对象,在标记完成后统一回收所有被标记的对象。

缺点:

  1. 效率不高
  2. 空间问题。标记清除后会产生大量不连续的内存碎片,碎片太多可能会导致程序在运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。

2. 复制算法

为解决效率问题,复制算法出现了:将内存按容量划分为大小相等的现场,每次只使用其中的一块,当这一块用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行回收,内存分配时也不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价将内存缩小为了原来的一半,未免太高了一点。现在的商业模式都采用这种收集算法来回收新生代。

3. 标记-整理算法

复制收集算法在对象存活率较高时就要进行较多我的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要额外的空间进行分配担保,以应对被使用的内存中所有对象都100%戚的极端情况,所以在老年代一般不能直接选用这种算法。

根据老年代的特点,提出了标记-整理算法,与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有对象都向一端移动,然后直接清理掉端边界以外的内存。

4. 分代收集算法

当前商业虚拟机的垃圾收集都采用分代收集算法,没有什么新的思想,一般把java堆分为新生代和老年代,这样就可以 根据各个年代的特点采用合适的收集算法。 在新生代中每次垃圾收集时都发现有大批对象死去,少量存活,就选用复制算法。而老年代中因为对象存活率高,没有额外空间对它进行担保,就必须使用 标记-清理算法,或者标记整理算法

三. HotSpot算法实现

1. 枚举根节点

2. 安全点

3. 安全区域

三. synchronized同步语句块

1. synchronized方法的弊端

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间,在这种情况下可以使用synchronized同步语句块来解决。

这句话好理解,demo 略,

2. 用同步代码块解决同步方法的弊端

public class CommonUtils {
    public static long beginTime1;
    public static long endTime1;
    public static long beginTime2;
    public static long endTime2;
}

public class Task {
    private String getData1;
    private String getData2;
    public void doLongTimeTask() {
        try {
            System.out.println("begin task");
            Thread.sleep(3000);

            String privateGetData1 = "长时间处理任务后的返回的值 1 threadName="
                    + Thread.currentThread().getName();
            String privateGetData2 = "长时间处理任务后的返回的值 2 threadName="
                    + Thread.currentThread().getName();
            synchronized (this) {
                getData1 = privateGetData1;
                getData2 = privateGetData2;
            }
            System.out.println(getData1);
            System.out.println(getData2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class MyThread1 extends Thread {
    private Task task;
    public MyThread1(Task task) {
        super();
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime1 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime1 = System.currentTimeMillis();
    }
}

public class MyThread2 extends Thread {
    private Task task;
    public MyThread2(Task task) {
        super();
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime2 = System.currentTimeMillis();
        task.doLongTimeTask();
        CommonUtils.endTime2 = System.currentTimeMillis();
    }
}

public class Run {

    public static void main(String[] args) {
        Task task = new Task();

        MyThread1 thread1 = new MyThread1(task);
        thread1.start();

        MyThread2 thread2 = new MyThread2(task);
        thread2.start();

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long beginTime = CommonUtils.beginTime1;
        if (CommonUtils.beginTime2 < CommonUtils.beginTime1) {
            beginTime = CommonUtils.beginTime2;
        }

        long endTime = CommonUtils.endTime1;
        if (CommonUtils.endTime2 > CommonUtils.endTime1) {
            endTime = CommonUtils.endTime2;
        }

        System.out.println("耗时: " + ((endTime - beginTime) / 1000));
    }
}
运行效果:
begin task
begin task
长时间处理任务后的返回的值 1 threadName=Thread-0
长时间处理任务后的返回的值 2 threadName=Thread-0
end task
长时间处理任务后的返回的值 1 threadName=Thread-1
长时间处理任务后的返回的值 2 threadName=Thread-1
end task
耗时: 3

通过运行结果可以看出耗时变成了3S. 当一个线程访问object的一个snychronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块.

3. 多个sync代码块间的同步性

public class ObjectService {
    public void serviceMethodA() {
        try {
            synchronized (this) {
                System.out.println("A begin time=" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end    end=" + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void serviceMethodB() {
        synchronized (this) {
            System.out.println("B begin time=" + System.currentTimeMillis());
            System.out.println("B end    end=" + System.currentTimeMillis());
        }
    }
}

public class ThreadA extends Thread {

    private ObjectService service;

    public ThreadA(ObjectService service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.serviceMethodA();
    }
}

public class ThreadB extends Thread {
    private ObjectService service;

    public ThreadB(ObjectService service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.serviceMethodB();
    }
}

public class Run {

    public static void main(String[] args) {
        ObjectService service = new ObjectService();

        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}
运行结果:
A begin time=1521628118097
A end    end=1521628120097
B begin time=1521628120098
B end    end=1521628120098
同步运行,另一个被阻塞

当一个线程访问一个对象的sync(this)同步代码块时,其他线程对同一个对象中的其他sync同步代码块的访问将被阻塞.这说明sync使用的是对象监视器

4. 将任意对象作为对象监视器

多个线程调用同一个对象的不同名称的sync同步方法或sync(this)同步代码块时,调用的效果就是按顺序执行,也就是同步,阻塞的。

sync同步方法或sync(this)同步代码块分别有两种作用:

  1. sync同步方法
    1. 对其他sync同步方法或sync(this)同步代码块调用呈阻塞状态。
    2. 同一时间只有一个线程可以执行sync同步方法中的代码。
  2. sync(this)同步代码块
    1. 对其他sync同步方法或sync(this)同步代码块调用呈阻塞状态。
    2. 同一时间只有一个线程可以执行sync(this)同步代码块中的代码。

根据前面对sync(this)同步代码块的作用总结可知,sycn(非this对象)格式的作用只有一种:sync(非this对象x)同步代码块:

  1. 当多个线程持有对象监视器为同一个对象的前提下,同一时间只有一个线程可以执行sync(this对象x)同步代码块中的代码
  2. 当持有对象监视器为同一个对象的前提下,同一时间只有一个线程可以执行sync(非this对象x)同步代码块中的代码

5. 细化验证3个结论

sync(非this对象x)格式的写法是将x对象本身作为 对象监视器 这样就可以得出以下3个结论:

  1. 当多个线程同时执行sync(x){}同步代码块时呈同步效果
  2. 当其他线程执行x对象中的sync同步方法时呈同步效果
  3. 当其他线程执行x对象方法里面的sync(this)代码块时也呈现同步效果

(1) 验证第一个结论

当多个线程同时执行sync(x){}同步代码块时呈同步效果

public class MyObject {
}

public class ThreadA extends Thread {
    private Service service;
    private MyObject object;

    public ThreadA(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }
}

public class ThreadB extends Thread {
    private Service service;
    private MyObject object;

    public ThreadB(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }
}

public class Run1_1 {
    public static void main(String[] args) {
        Service service = new Service();
        MyObject object = new MyObject();

        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service, object);
        b.setName("b");
        b.start();
    }
}

public class Service {
    public void testMethod1(MyObject object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1 ____getLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("testMethod1 releaseLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
运行效果:
testMethod1 ____getLock time=1523523486828 run ThreadName=a
testMethod1 releaseLock time=1523523488828 run ThreadName=a
testMethod1 ____getLock time=1523523488829 run ThreadName=b
testMethod1 releaseLock time=1523523490829 run ThreadName=b
由结果可看到同一个对象监视器, 结果同步输出

换不同的对象监视器

public class Run1_2 {
    public static void main(String[] args) {
        Service service = new Service();
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();

        ThreadA a = new ThreadA(service, object1);
        a.setName("a");
        a.start();

        ThreadB b = new ThreadB(service, object2);
        b.setName("b");
        b.start();
    }
}
运行结果如下:
testMethod1 ____getLock time=1523523808562 run ThreadName=a
testMethod1 ____getLock time=1523523808564 run ThreadName=b
testMethod1 releaseLock time=1523523810582 run ThreadName=a
testMethod1 releaseLock time=1523523810637 run ThreadName=b
结果异步输出

(2) 验证第二个结论

当其他线程执行x对象中的sync同步方法时呈同步效果

public class MyObject {
    synchronized public void speedPrintString() {
        System.out.println("speedPrintString ____getLock time="
                + System.currentTimeMillis() + " run ThreadName="
                + Thread.currentThread().getName());
        System.out.println("-----------------");
        System.out.println("speedPrintString releaseLock time="
                + System.currentTimeMillis() + " run ThreadName="
                + Thread.currentThread().getName());
    }
}	

public class ThreadA extends Thread {
    private Service service;
    private MyObject object;

    public ThreadA(Service service, MyObject object) {
        super();
        this.service = service;
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        service.testMethod1(object);
    }
}

public class ThreadB extends Thread {
    private MyObject object;

    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }

    @Override
    public void run() {
        super.run();
        object.speedPrintString();
    }
}

public class Service {
    public void testMethod1(MyObject object) {
        synchronized (object) {
            try {
                System.out.println("testMethod1 ____getLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("testMethod1 releaseLock time="
                        + System.currentTimeMillis() + " run ThreadName="
                        + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Run {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        MyObject object = new MyObject();

        ThreadA a = new ThreadA(service, object);
        a.setName("a");
        a.start();

        Thread.sleep(100);

        ThreadB b = new ThreadB(object);
        b.setName("b");
        b.start();
    }
}
运行结果如下:
testMethod1 ____getLock time=1523524079664 run ThreadName=a
testMethod1 releaseLock time=1523524084664 run ThreadName=a
speedPrintString ____getLock time=1523524084664 run ThreadName=b
-----------------
speedPrintString releaseLock time=1523524084664 run ThreadName=b

(3) 验证第3个结论

当其他线程执行x对象方法里面的sync(this)代码块时也呈现同步效果

只需要把上述项目中的MyObject替换一下即可

public class MyObject {
    public void speedPrintString() {
        synchronized (this) {
            System.out.println("speedPrintString ____getLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
            System.out.println("-----------------");
            System.out.println("speedPrintString releaseLock time="
                    + System.currentTimeMillis() + " run ThreadName="
                    + Thread.currentThread().getName());
        }
    }
}
运行结果如下:
testMethod1 ____getLock time=1523524445528 run ThreadName=a
testMethod1 releaseLock time=1523524450529 run ThreadName=a
speedPrintString ____getLock time=1523524450529 run ThreadName=b
-----------------
speedPrintString releaseLock time=1523524450529 run ThreadName=b
同步输出

6. 静态同步sync方法与sync(class)代码块

(1) 静态同步方法sync

关键字sync还可以应用在static静态方法上,如果这样写,那就是对当前*.java文件对应的Class类进行持锁

public class ThreadA extends Thread {
    @Override
    public void run() {
        Service.printA();
    }
}

public class ThreadB extends Thread {
    @Override
    public void run() {
        Service.printB();
    }
}

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为: " + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + " 进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为: " + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + " 离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为: " + Thread.currentThread().getName() + " 在 "
                + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为: " + Thread.currentThread().getName() + " 在 "
                + System.currentTimeMillis() + "离开printB");
    }
}
public class Run {
    public static void main(String[] args) {
        ThreadA a = new ThreadA();
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB();
        b.setName("B");
        b.start();
    }
}
运行结果如下:
线程名称为: A 在 1523525481258 进入printA
线程名称为: A 在 1523525484259 离开printA
线程名称为: B 在 1523525484259 进入printB
线程名称为: B 在 1523525484259 离开printB

结论:都是同步的效果,和将sync关键字加到非static 方法上的使用效果是一样的。还是有本质的区别,sync加到static静态方法上是给class类上锁,而sync关键字加到static静态方法上是给对象上锁

例子略,

(2) 验证class类锁,和对象锁无关

public class Service {
    synchronized public static void printA() {
        try {
            System.out.println("线程名称为: " + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为: " + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + "离开rintA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB() {
        System.out.println("线程名称为: " + Thread.currentThread().getName() + " 在 "
                + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为: " + Thread.currentThread().getName() + " 在 "
                + System.currentTimeMillis() + "离开printB");
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service1 = new Service();
        Service service2 = new Service();

        ThreadA a = new ThreadA(service1);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service2);
        b.setName("B");
        b.start();
    }
}
结论:虽说是不同的对象,但sync加在静态方法上是对class上锁,所以还是同步执行。

(3) sync(class)代码和sync static 方法作用一样

public class Service {
    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + " 在 " + System.currentTimeMillis() + "进入printA");
                Thread.sleep(3000);
                System.out.println("线程名称为:" + Thread.currentThread().getName()
                        + " 在 " + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void printB() {
        synchronized (Service.class) {
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + "进入printB");
            System.out.println("线程名称为:" + Thread.currentThread().getName()
                    + " 在 " + System.currentTimeMillis() + "离开printB");
        }
    }
}

public class ThreadA extends Thread {
    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}

public class ThreadB extends Thread {
    private Service service;

    public ThreadB(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service1 = new Service();
        Service service2 = new Service();

        ThreadA a = new ThreadA(service1);
        a.setName("A");
        a.start();

        ThreadB b = new ThreadB(service2);
        b.setName("B");
        b.start();
    }
}

深入理解java I/O的工作机制

1. 基本架构

java 的io操作类在java.io包下,大概有将近80个类,可大概分为如下四组:

  • 基于字节操作的io接口:InputStream 和OutputStream
  • 基于字符操作的io接口:Writer和Reader
  • 基于磁盘的io接口:File
  • 基于网络操作的io接口:Socket

前两组主要是传输数据的数据格式,后两组主要是传输数据的方式

1.1 基于字节的操作接口

基于字节的输入的输出分别是InputStream和OutputStream

InputStream子类

AudioInputStream , ByteArrayInputStream , FileInputStream ,  
FilterInputStream , InputStream , ObjectInputStream ,   
PipedInputStream , SequenceInputStream , StringBufferInputStream

OutputStream子类

ByteArrayOutputStream , FileOutputStream , FilterOutputStream ,
ObjectOutputStream , OutputStream , PipedOutputStream 

1.2 基于字符的操作接口

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。

写类Writer,读Reader

Writer

BufferedWriter , CharArrayWriter , FilterWriter , OutputStreamWriter ,
PipedWriter , PrintWriter , StringWriter 

Reader

BufferedReader , CharArrayReader , FilterReader , InputStreamReader ,
PipedReader , StringReader 

1.3 字节和字符的转化接口

OutputStreamWriter:是Writer的子类。将输出的字符流变成字节流:即将字符流的输入对象变成字节流输入对象。

InputStreamReader:是Reader的子类。将输入的字节流变成字符流,即将一个字节流的输入对象变成字符流输入对象。

2. 磁盘io工作机制

2.1 标准访问文件的方式

读文件方式:当调用read()接口时,先检查缓存,如果存在,则从缓存中返回。如果没有,则从磁盘中读取,然后缓存在操作系统的缓存中。

写入的方式:用户调用writer()将数据复制到内核地址的缓存中,这时对用户来说已经完成,至于什么 时候再写到磁盘 中由操作系统决定,

2.2 直接io的方式

所谓直接io的方式就是应用程序直接访问磁盘 数据,而不经过操作系统内核数据缓冲。这样做的目的就是减少一次从内核缓冲区到用户程序缓存的数据复制。

但这样也有负面影响,如果访问的数据不在应用程序缓存中,那么每次数据都会直接从磁盘 进行加载,这种直接加载会非常缓慢。通常直接io与异步io结合使用,会得到比较好的性能。

2.3 同步访问文件的方式

数据的读取和写入都是同步操作的,它与标准访问文件的方式不同的是,只有当数据被成功写到磁盘 时才返回给应用程序成功的标志。

这种访问文件的方式性能都比较差,只有在一些对数据安全性要求比较高的场景中才会使用,而且通常这些操作方式的硬件都是定制的。

2.4 异步访问文件的方式

异步访问文件的方式就是当访问数据的线程发出请求之后,线程会接着去处理其他事情,而不是阻塞等待。这种可以提高应用程序的效率,但不会改变访问文件的效率。

2.5 内存映射的方式

指操作系统将内存中的某一块区域与磁盘中的文件关联起来,当要访问内存中的一段数据时,转换为访问文件的某一段数据。目的同样是减少数据从内核空间缓存到用户空间缓存的数据复制操作,因为这两个空间的数据是 共享的。

2.6 java序列化技术

java序列化就是将一个对象转化成一串二进制表示的字节数组,通过保存或者转移这些字节数据来达到持久化的目的。持久化,对象必须继承java.io.Serializable接口,反序列化则是相反的过程,将这个字节数组再重新构造成对象。

序列化情况总结:

  • 当父类继承Serializable接口,所有子类都可以被序列化。
  • 子类实现了Serializable接口,父类没有,父类中的属性不能序列化(不报错,数据会丢失),但是在子类中属性仍能正确序列化。
  • 如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错
  • 在反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错。
  • 在反序列化时,如果SerialVersionUID被修改,则反序列化时会失败。

3. 网络io工作机制

3.1 TCP状态转化

暂无详细了解

3.2 影响网络传的因素

  • 网络带宽: 所谓带宽就是一条物理链路在1s内能够传输的最大比特数。注意这里比特(bit)而不是字节数,也就是 b/s .
  • 传输距离: 也就是数据在光纤中要走的距离,虽然光的转播速度很快,但也有时间的,由于数据在光纤中的移动并不是走直线的,会有一个折射率,大概是光的2/3,这个就是我们说的传输延时。
  • TPC拥塞控制: TCP传输是一个 停-等-停-等 的协议。传输方的接受方的步调要一致,要达到步调一致就要通过拥塞控制来调节。

3.3 java Socket 的工作机制

Socket没有对应到一个具体的实体,它描述计算机之间完成相互通信的一种抽象功能。Socket连接必须由底层 Tcp/IP 来建立连接,建立Tcp连接需要底层IP来寻址网络中的主机。

3.4 建立通信链路

客户端socket实例创建过程:客户端创建一个socket实例,给实例分配没有使用过的端口号,并创建一个包含本地地址,远程地址和端口号的套接字数据结构,这个数据结构一直保存在系统中直到这个连接关闭,在创建socket构造函数正确返回之前,要进行tcp的3次握手协议,协议完成后socket实例对象将创建完成,否则交付抛出IOException错误。

服务器:服务器将创建一个ServerSocket实例,只要端口号没被占用,一般都会创建成功,同时操作系统将创建一个底层数据结构,这个数据结构包含指定监听的端口号和包含监听地址的通配符。之后调用acccept方法时,将进入阻塞状态,等待客户端的请求。

3.5 数据传输

当连接建立成功时,服务端和客户端都会拥有一个socket实例,每个Socket都有一个InputStream和OutputStream,并通过这两个对象来交换数据。