Thread-01 笔记

一.Synchronized关键字,原子操作。

/*
* synchonized关键字 互斥锁
* 对某个对象加锁 
*/
public class T {
    private int count=10;
    //private Object o=new Object();  //new 一个对象当锁 太浪费 可以使用this
    public void  m(){
        synchronized (this){  //任何线程要执行下面的代码,必须要先拿到o的锁 o指向堆内存里对象的锁,不是引用
            count--;
            System.out.println(Thread.currentThread());
        }
    }

}

synchronized 锁的是一个对象,不是代码块。

如果代码开始就要加锁,结束才会释放锁 可以写成下面的形式

public synchronized void m(){
...
}

如果是静态的属性或方法 是没有对象的,所以锁定的是当前的类

public static void mm(){  //静态方法时 锁的是当前的类
synchronized(T.class){

}
}

线程重入问题 : 打断另一个线程施法,也可以叫另一个线程断片。

解决方法: 在线程访问的方法上加 synchronized 锁 ,

public class T implements Runnable{
    private int count=10;
    public synchronized   void  run(){   //多个线程访问同一个对象造成线程重入问题,因此需要加锁
            count--;
            System.out.println(Thread.currentThread().getName()+"count="+count);
    }

    public static void main(String[] args) {
        T t = new T();  //new 了一个对象 多个线程访问
        for (int i = 0; i < 5; i++) {
        new Thread(t,"Thread"+i).start();  //五个线程访问的都是t里面的run方法
        }
    }

}

  1. 同步方法和非同步方法是否可以同时调用? —》 可以
package com.ThreadLearning;

/**
 * 同步方法和非同步方法是否可以同时调用?
 */
public class T1 {
    public synchronized void m1() {  //需要锁定当前对象的
        System.out.println(Thread.currentThread().getName() + "m1 start ....");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "m1 end ....");

    }

    public void m2() {  //不需要锁定当前对象的
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "m2  end ....");
    }

    public static void main(String[] args) {
        T1 t = new T1();
        new Thread(()->t.m1(),"t1:").start();
       // new Thread(()->t.m2(),"t2:").start();
        new Thread(t::m2(),"t2:").start();
    }

}
/**
 * t1:m1 start ....
 * t2:m2  end ....
 * t1:m1 end ....
 */

  1. 对业务写加锁,对业务读不加锁 产生脏读(dirtyRead)问题
import java.util.concurrent.TimeUnit;

/**
 * 对业务写加锁,对业务读不加锁
 * 产生脏读(dirtyRead)问题
 */
public class Count {
    String name;
    double balance;

    //银行账户 在多个线程访问时需要加锁
    public synchronized void set(String name, double balance) {
        this.name = name;
        /**
         * 放大了线程之间的时间差  会造成 脏数据 读不到数据
         * 时间差: 非同步方法
         *
         */
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;

    }
    //写加了锁  读没有加锁  
    // 具体根据代码的业务逻辑
    public double getBalance(String name) {
        return this.balance;
    }

    public static void main(String[] args) {
        Count count = new Count();
        new Thread(() -> count.set("zhangsan", 100.0)).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count.getBalance("zhangsan")); //主线程 读出来是0

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count.getBalance("zhangsan"));


    }
}


4.一个同步方法是否可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说Synchronized获得的锁是可重入的

import java.util.concurrent.TimeUnit;

public class T2 {
   synchronized void m1(){
       System.out.println("m1  start 。。。。");
       try {
           TimeUnit.SECONDS.sleep(1);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       m2();
   }
   synchronized  void  m2(){
       try {
           TimeUnit.SECONDS.sleep(2);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("m2");
   }
}

一个线程里 可以再次申请已经拥有的锁

结论 : Synchronized获得的锁是可重入的 ,

注意:可能会产生死锁 (互相拿到对方的锁)

扩展:子类调用父类的同步方法 √ this–>child


5.程序在执行过程中,如果出现异常,默认情况下锁就会被释放
所以在并发过程中,有异常要小心处理,不然可能会发生不一致的问题
如:在一个web app处理过程中,多个servlet线程共享访问同一个资源,这时如果异常处理不合适,
在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据。

处理方法: 要想不被释放,可以进行try catch 然后让循环继续

Thread类其实也实现了Runable接口。--->创建时直接new Thread(new Runnable(){public void run(){new T().m1()}}).start();

二 .Volatile关键字 : 保证线程之间变量 的可见性

import javax.sound.midi.SoundbankResource;
import java.util.concurrent.TimeUnit;

/**
 * volatile 关键字,使一个变量在多个线程间可见
 * A B线程都用到一个变量,java 默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
 * 使用Volatile关键字,会让所有的线程读到变量的修改值
 * 

* 在下面的代码中,running是存在于堆内存的t对象中 * 当线程t1开始运行的时候,会把running值从内存中读取到t1线程的工作区,在运行过程中直接使用这个copy,并不会 * 每次读取堆内存,这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行 *

* 使用volatile关键字,将会强制所有线程都去内存中读取running的值 */ public class VolatileDemo { volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别 //缓冲区里的值发生改变时 会给调用这个值的线程发送通知,数据已经发生改变 void m() { System.out.println("m start ..."); while (running) { } System.out.println("m end ..."); } public static void main(String[] args) { VolatileDemo t = new VolatileDemo(); new Thread(t::m,"t1").start(); //t1线程 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t.running=false; //主线程 } }

  1. volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized

    res: 可见性 保证读不保证写===> 写的时候会被覆盖 结果小于10000

package com.ThreadLearning;

import java.util.ArrayList;

/**
 * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
 */
public class VolatileDemo2 {
    volatile int count = 0;   //可见性 保证读不保证写===> 写的时候会被覆盖 结果小于10000

    void m() {
        for (int i = 0; i < 1000; i++) count++;
    }

    public static void main(String[] args) {
        VolatileDemo2 t = new VolatileDemo2();

        ArrayList<Thread> threads = new ArrayList<>();

        for (int i = 0; i < 10; i++) { //10个线程 都加到1000 共10000
            threads.add((new Thread(t::m, "Thread=" + i)));

        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }

}

如果只是对数据进行++ – java 提供了 AtomXXX 类本身方法都是原子性的

AtomicInteger atomicInteger=  new AtomicInteger(0).incrementAndGet()    //count++  不具备原子性

两个原子性操作是不具备原子性的,在两个原子性操作之间还会有其他操作。

AtomXXX 比 Synchronized 性能更好。

package com.ThreadLearning;

import java.util.concurrent.TimeUnit;

/**
 * 锁定某对象,如果o的属性发生改变,不影响锁的使用
 * 但是如果o变成另一个对象,则锁定的对象发生改变
 * 应该避免将锁对象的引用变成另外的对象
 */
public class T {
   Object o= new Object();
   void  m(){
       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) {
        T t = new T();
        //启动第一个线程
        new Thread(t::m,"t1").start();

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 启动第二个线程
        Thread t2 = new Thread(t::m, "t2");
        t.o=new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释这句代码 t2将永远不能执行
        t2.start();       //证明锁在堆内存的对象上的
    }
}

不要用字符串常量当锁对象

import java.util.concurrent.TimeUnit;

/**
 * 不要以字符串常量作为锁对象
 * 在下面的例子中,m1和m2 其实锁定的是同一个对象
 * 这种情况还会发生诡异的现象,比如你用到了一个类库,在该类库中锁定字符串"hello"
 * 但是你读不到源码,在自己的代码中也锁定了"hello",这时候可能就会发生诡异的死锁阻塞
 * 因为你的程序和你用到的类库不经意间使用了同意把锁
 */
public class T {
    String s1="hello";
    String s2="hello";

    void m1(){
        synchronized (s1){

        }
    }
    void m2(){
        synchronized (s2){

        }
    }
    //jetty 曾经有这样的bug

}
package com.ThreadLearning;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 面试题:
 * 实现一个容器:提供俩个方法,add size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时。线程给出提示并结束
 */
public class MyContainer {
  volatile   List list = new ArrayList();

    public void add(Object o) {
        list.add(o);

    }

    public int size() {
        return list.size();

    }

    public static void main(String[] args) {
        MyContainer c = new MyContainer();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add>>>" + i);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1").start();
        new Thread(() -> {
            while (true) {
                if (c.size() == 5) {
                    break;
                }
                System.out.println("t2 结束。。");
            }

        }, "t2").start();

    }

}

wati notify 是调用锁定对象的方法

先启动t2 不等于5时 wait  等于5 时 notify  执行T2 
notify 不释放锁 所以 notify后还需要在wait一下

最简单的方法 Latch(门闩)替代wait notify

好处是通信简单,同时也可以指定等待时间

CountDownLatch不涉及锁定,当count为0时当前线程继续执行

当不涉及同步,只是涉及线程通信的时候,用synchronzied_wait/notify就太重了

这时应该考虑countdownLatch/cyclicbarrier/semaphore

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 面试题:
 * 实现一个容器:提供俩个方法,add size
 * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5时。线程给出提示并结束
 */
public class MyContainer {
  volatile   List list = new ArrayList();

    public void add(Object o) {
        list.add(o);

    }

    public int size() {
        return list.size();

    }

    public static void main(String[] args) {
        MyContainer c = new MyContainer();
        CountDownLatch latch = new CountDownLatch(1);
        new Thread(() -> {
            System.out.println("t2启动");
            if (c.size()!=5){
                try {
                    latch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t2结束");
        }, "t2").start();

      
        new Thread(()->{
            System.out.println("t1 启动");
            for (int i = 0; i < 10; i++) {
                c.add(new Object());
                System.out.println("add--"+i);
                if (c.size()==5){
                    //打开门闩,让t2执行
                    latch.countDown();  // 0 就开了 countDown一次 减一次
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

你可能感兴趣的:(Thread-01 笔记)