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.不要以字符串常量作为锁定的对象


  • 相关阅读:
    c#+oracle存储过程实现分页
    C#中调用Matlab程序
    Oracle 自定义TYPE 的几种用法(转)
    oracle嵌套表示例
    矩阵的秩及矩阵的广义逆
    矩阵的定义及其运算规则
    矩阵微分
    matlab中取模(mod)与取余(rem)的区别
    hog源码分析
    矩阵的转置、求逆及分块
  • 原文地址:https://www.cnblogs.com/jpfss/p/9913466.html
Copyright © 2011-2022 走看看