zoukankan      html  css  js  c++  java
  • 马士兵并发编程学习笔记1


    (一)
    public class Demo01 {

    private int count = 10;
    private Object object = new Object();
    @Test
    public void test(){
    synchronized (object) { //任何线程要执行下面的代码,必须先拿到object对象的锁
    count --;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
    }

    public static void main(String[] args) {
    Demo01 demo01=new Demo01();

    new Thread(()->demo01.test()).start();
    new Thread(()->demo01.test()).start();

    }
    1.synchronized关键字锁定的是对象不是代码块,demo中锁的是object对象的实例(堆内存中)
    2.锁定的对象有两种情况:①类的实例 ②类的字节码(.class)
    3.关于线程安全:加synchronized关键字之后不一定能实现线程安全,具体还要看
    锁定的对象是否唯一。
    (二)
    public class Demo02 {

    private int count = 10;
    @Test
    public void test(){
    synchronized (this) { //任何线程要执行下面的代码,必须先拿到Demo02对象实例的锁
    count --;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
    }
    }
    1.synchronized(this)锁定的是当前类的实例,demo中锁定的是Demo02类的实例
    2.此demo中如果Demo02类是单例的话可以保证在多线程访问时是线程安全的,
    如果存在有多个Demo02的实例的话在多线程中不能保证线程安全,因为方法中的锁不唯一了。(堆内存中的地址不一样)
    (三)
    public class Demo03 {

    private int count = 10;
    public synchronized void test(){//等同于synchronized(this),锁定的是Demo03对象的实例
    count --;
    System.out.println(Thread.currentThread().getName() + " count =" + count);
    }
    }
    1.synchronized关键字修饰普通方法等同于synchronized(this)
    (四)
    public class Demo04 {

    private static int count = 10;
    public synchronized static void test1(){ //这里等同于synchronized(Demo04.class)
    count --;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
    public static void test2(){ //考虑一下这里写synchronize(this)是否可以
    synchronized (Demo04.class) {
    count --;
    }
    }
    }
    1.synchronize关键字修饰静态方法锁定的是类的.class文件
    2.静态方法中synchronize锁定代码块,锁定的对象不能是类的实例,只能是类的.class文件。
    原理如同在静态方法中不能直接调用非静态方法
    3.类的.class文件是唯一的,所以说synchronize修饰静态方法或者锁定的对象是类的.class文件的时候
    在多线程中是可以实现线程安全的
    (五)
    public class Demo05 implements Runnable{

    private int count = 10;
    @Override
    public /* synchronized*/ void run(){
    count --;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
    public static void main(String[] args) {
    Demo05 demo05 = new Demo05();
    for (int i = 0; i < 5; i++) {
    new Thread(demo05,"THREAD" + i).start();
    }
    }
    }
    1.run()方法没加synchronized关键字时,多个线程同时访问count,线程是不安全的
    2.run()方法加上synchronized关键字后,锁定的是Demo05对象的实例,因为只创建了
    一个Demo05的实例,多个线程访问时都要拿到Demo05的锁标记才能执行,在多个线程同时访问时也是线程安全的
    (六)
    public class Demo06 implements Runnable{

    private int count = 10;

    @Override
    public synchronized void run() {
    count --;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void main(String[] args) {
    for (int i = 0; i < 5; i++) {
    Demo06 demo06 = new Demo06();//注意这里
    new Thread(demo06,"THREAD" + i).start();
    }
    }
    }
    1.执行可以知道,demo中虽然加上了synchronized关键字来修饰方法,但是线程是不安全的。为什么呢??
    分析一下:synchronized修饰的是普通方法,锁定的是Demo06实例,从Main方法中可以看到,在for循环中
    创建了多个Demo06的实例,也就是说每个线程对应都拿到各自的锁标记,可以同时执行。
    例子:
    多人同时上厕所,厕所门只有一把锁的时候是一个人上完之后把钥匙(锁标记)给到下一个人才可以开门上厕所
    如果厕所门的锁有多个钥匙的情况下,就是每个人都有锁的钥匙了,大家可以一起去打开门来上厕所。(归根结底还是堆内存上的地址)
    demo中就如同厕所门的锁有多把钥匙(锁标记),不能实现线程安全
    (七)
    public class Demo07 {

    public synchronized void test1(){
    System.out.println(Thread.currentThread().getName() + " test1 start..........");
    try {
    Thread.sleep(10 * 1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " test1 end........");
    }
    public void test2(){
    try {
    Thread.sleep(5 * 1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "test2 execute......");
    }
    public static void main(String[] args) {
    Demo07 demo07 = new Demo07();
    new Thread(demo07 :: test1,"t1").start(); //JDK1.8新特性
    new Thread(demo07 :: test2,"t2").start(); //JDK1.8新特性
    }
    }
    运行结果:
    t1 test1 start..........
    t2test2 execute......
    t1 test1 end........
    1.同步方法和非同步方法是可以同时调用的
    (八)
    package thread.demo_008;

    import java.util.concurrent.TimeUnit;

    /**
    * 对业务写方法加锁
    * 对业务读方法不加锁
    * 容易产生脏读问题
    * @author Jcon
    *
    */
    public class Demo08 {

    String name;
    double balance;
    public synchronized void set(String name, double balance){
    this.name = name;
    try {
    Thread.sleep(2 * 1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    this.balance = balance;
    }
    public synchronized double getBalance(String name){
    return this.balance;
    }

    public static void main(String[] args) {
    Demo08 demo08 = new Demo08();
    new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(demo08.getBalance("zhangsan"));
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(demo08.getBalance("zhangsan"));
    }
    }

    2.对业务写方法加锁,同时也要对业务读方法加锁,否则容易产生脏读问题
    (九)
    /**
    * 一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候
    * 仍然会得到该对象的锁
    * 也就是说synchronized获得的锁是可重入的
    * @author Jcon
    *
    */
    public class Demo09 {

    synchronized void test1(){
    System.out.println("test1 start.........");
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    test2();
    }
    synchronized void test2(){
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("test2 start.......");
    }
    public static void main(String[] args) {
    Demo09 demo09 = new Demo09();
    demo09.test1();
    }
    }
    1.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,
    再次申请的时候仍然会得到该对象的锁
    也就是说synchronized获得的锁是可重入的
    (十)
    package thread.demo_010;

    import java.util.concurrent.TimeUnit;

    /**
    * 一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,
    * 再次申请的时候仍然会得到该对象的锁,也就是说synchronize获得的锁是可重入的
    * 这里是继承中有可能发生的情形,子类调用父类的同步方法
    * @author Jcon
    *
    */
    public class Demo10 {

    synchronized void test(){
    System.out.println("test start........");
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("test end........");
    }
    public static void main(String[] args) {
    new Demo100().test();
    }
    }

    class Demo100 extends Demo10{
    @Override
    synchronized void test() {
    System.out.println("child test start.......");
    super.test();
    System.out.println("child test end.......");
    }
    }
    2.一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,
    再次申请的时候仍然会得到该对象的锁
    也就是说synchronized获得的锁是可重入的(这里是继承中有可能发生的情形,子类调用父类的同步方法

    (十一)
    /**
    * 程序在执行过程中,如果出现异常,默认情况锁会被释放
    * 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
    * 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
    * 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
    * 因此要非常小心的处理同步业务逻辑中的异常
    * @author Jcon
    *
    */
    public class Demo11 {

    int count = 0;
    synchronized void test(){
    System.out.println(Thread.currentThread().getName() + " start......");
    while (true) {
    count ++;
    System.out.println(Thread.currentThread().getName() + " count = " + count);
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    if (count == 5) {
    int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
    }
    }
    }
    public static void main(String[] args) {
    Demo11 demo11 = new Demo11();
    Runnable r = new Runnable() {
    @Override
    public void run() {
    demo11.test();
    }
    };
    new Thread(r, "t1").start();
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    new Thread(r, "t2").start();
    }
    }
    1.程序在执行过程中,如果出现异常,默认情况锁会被释放
    (十二)
    public class Demo12 {

    volatile boolean running = true;
    public void test(){
    System.out.println("test start.......");
    while (running) {
    /*try {
    TimeUnit.SECONDS.sleep(10);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }*/
    }
    System.out.println("test end........");
    }
    public static void main(String[] args) {
    Demo12 demo12 = new Demo12();
    new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    demo12.running = false;
    }
    }
    * volatile 关键字,使一个变量在多个线程间可见
    * A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
    * 使用volatile关键字,会让所有线程都会读到变量的修改值
    *
    * 在下面的代码中,running是存在于堆内存的t对象中
    * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去
    * 读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行
    *
    * 使用volatile,将会强制所有线程都去堆内存中读取running的值
    *
    * 可以阅读这篇文章进行更深入的理解
    *

    (十三)
    **
    * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
    * 也就是说volatile不能替代synchronize
    * 运行下面的程序,并分析结果
    * @author Jcon
    *
    */
    public class Demo13 {

    volatile int count = 0;
    public void test(){
    for (int i = 0; i < 10000; i++) {
    count ++;
    }
    }
    public static void main(String[] args) {
    Demo13 demo13 = new Demo13();
    List<Thread> threads = new ArrayList<Thread>();
    for (int i = 0; i < 10; i++) {
    threads.add(new Thread(demo13::test, "thread-" + i));
    }
    threads.forEach((o)->o.start()); //JDK1.8新特性
    threads.forEach((o)->{ //JDK1.8新特性
    try {
    o.join(); //等线程执行完毕之后才执行主线程main
    } catch (Exception e) {
    e.printStackTrace();
    }
    });
    System.out.println(demo13.count);
    }
    }
    * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
    (十四)
    public class Demo14 {

    int count = 0;
    public synchronized void test(){
    for (int i = 0; i < 10000; i++) {
    count ++;
    }
    }
    public static void main(String[] args) {
    Demo14 demo14 = new Demo14();
    List<Thread> threads = new ArrayList<Thread>();
    for (int i = 0; i < 10; i++) {
    threads.add(new Thread(demo14::test, "thread-" + i));
    }
    threads.forEach((o)->o.start()); //JDK1.8新特性
    threads.forEach((o)->{ //JDK1.8新特性
    try {
    o.join(); //等线程执行完毕之后才执行主线程main
    } catch (Exception e) {
    e.printStackTrace();
    }
    });
    System.out.println(demo14.count);
    }
    }

    * 对比上一个程序,可以用synchronized解决,synchronize可以保证可见性和原子性,volatile只能保证可见性
    (十五)
    public class Demo15 {

    //int count = 0;
    AtomicInteger count = new AtomicInteger(0);
    public /*synchronized*/ void test(){
    for (int i = 0; i < 10000; i++) {
    //count ++;
    count.incrementAndGet(); //count++
    // 注意下面则不构成原子性,因为在get时,线程a进行判断后,但是不执行下面代码
    // 线程b进行判断,执行完代码,此时代码是1000,然后线程a执行,此时结果是1001
    // if (count.get() > 1000) {
    // count.incrementAndGet();
    // }
    }
    }
    public static void main(String[] args) {
    Demo15 demo15 = new Demo15();
    List<Thread> threads = new ArrayList<Thread>();
    for (int i = 0; i < 10; i++) {
    threads.add(new Thread(demo15::test, "thread-" + i));
    }
    threads.forEach((o)->o.start()); //JDK1.8新特性
    threads.forEach((o)->{ //JDK1.8新特性
    try {
    o.join(); //等线程执行完毕之后才执行主线程main
    } catch (Exception e) {
    e.printStackTrace();
    }
    });
    System.out.println(demo15.count);
    }


    }
    * 解决同样的问题的更高效的方法,使用AtomXXX类
    * AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
    (十六)
    /**
    * synchronize优化
    * 同步代码快中的语句越少越好
    * 比较test1和test2
    * @author Jcon
    *
    */
    public class Demo16 {

    int count = 0;
    public synchronized void test1(){
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
    count ++;
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    public void test2(){
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
    //采用细粒度的锁,可以是线程争用时间变短,从而提高效率
    synchronized (this) {
    count ++;
    }
    try {
    TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    1.业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
    2.采用细粒度的锁,可以是线程争用时间变短,从而提高效率
    (十七)
    /**
    * 锁定某对象o,如果o的属性发生改变,不影响锁的使用
    * 但是如果o变成另外一个对象,则锁定的对象发生改变
    * 应该避免将锁定对象的引用变成另外一个对象
    * @author Jcon
    *
    */
    public class Demo17 {

    Object o = new Object();
    public void test(){
    synchronized (o) {
    while (true) {
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName());
    }
    }
    }
    public static void main(String[] args) {
    Demo17 demo17 = new Demo17();
    //启动第一个线程
    new Thread(demo17 :: test, "t1").start(); //JDK1.8新特性
    try {
    TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    //启动第二个线程
    Thread t2 = new Thread(demo17 :: test, "t2");
    demo17.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程t2将永远得不到执行机会
    t2.start();
    }
    }
    * 锁定某对象o,如果o的属性发生改变,不影响锁的使用
    * 但是如果o变成另外一个对象,则锁定的对象发生改变
    * 应该避免将锁定对象的引用变成另外一个对象
    (十八)
    /**
    * 不要以字符串常量作为锁定的对象
    * 在下面的例子中,test1和test2其实锁定的是同一个对象
    * 这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串"hello",
    * 但是你读不到源码,所以你在自己的代码中也锁定了"hello",这时候就有可能发生非常诡异的死锁阻塞,
    * 因为你的程序和你用的的类库不经意间使用了同一把锁
    * @author Jcon
    *
    */
    public class Demo18 {

    String s1 = "hello";
    String s2 = "hello";
    public void test1(){
    synchronized (s1) {
    }
    }
    public void test2(){
    synchronized (s2) {
    }
    }
    }
    1.不要以字符串常量作为锁定的对象


  • 相关阅读:
    129 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 03 饿汉模式 VS 懒汉模式 02 懒汉式的代码实现
    128 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 03 饿汉模式 VS 懒汉模式 01 饿汉式的代码实现
    127 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 02 单例模式概述 01 单例模式的定义和作用
    126 01 Android 零基础入门 02 Java面向对象 06 Java单例模式 01 设计模式概述 01 设计模式简介
    125 01 Android 零基础入门 02 Java面向对象 05 Java继承(下)05 Java继承(下)总结 01 Java继承(下)知识点总结
    leetcode-----121. 买卖股票的最佳时机
    leetcode-----104. 二叉树的最大深度
    Json串的字段如果和类中字段不一致,如何映射、转换?
    Mybatis-Plus的Service方法使用 之 泛型方法default <V> List<V> listObjs(Function<? super Object, V> mapper)
    模糊查询
  • 原文地址:https://www.cnblogs.com/jpfss/p/9913466.html
Copyright © 2011-2022 走看看