Java多线程读书笔记(二)

来源:互联网 发布:机加工成本核算软件 编辑:程序博客网 时间:2024/06/09 19:02

对象及变量的并发访问

了解线程安全及同步问题

(一)synchronized同步方法

1.什么是线程安全?什么又是非线程安全?

  • “非线程安全”就是多个线对同一个对象中的实例变量进行并发访问时,取到的数据可能会被更改,就是产生“脏读”现象。
  • “线程安全”中,获取变量的值是会经过同步处理的,不会发生“脏读”现象。

2.这个对象中,什么样的实例变量会产生线程安全问题?

实例变量分为方法外的全局变量和方法内的私有变量。如果是方法内的私有变量则不会产生“线程安全”问题,永远是线程安全的。因为多个线程每次访问那个方法,实例变量就新建一个,根本不会产生多个线程访问同一变量的情况。
注意:不要随便把方法内的私有变量改为全局变量!很危险!我就曾经犯过这样的错误,差点导致项目归零,哭了一晚上,幸好我又改回来了。
两个线程访问同一个全局变量时,而执行方法又不是同步的,很可能出现“非线程安全问题”。
解决办法就是,在这个执行方法上加入关键字synchronized。

结论:两个线程访问 同一个对象 中的 同步方法 时一定是线程安全的。

3.synchronized方法获取的锁是什么级别的?

两个线程分别访问同一个类的两个不同实例中相同名称的同步方法时,系统中会产生两把锁,导致这两个线程的执行效果是异步的。说明,取得的锁是对象锁。

4.synchronized锁对象与方法?

结论:

1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized方法。

2)A线程先持有object对象的Lock锁,B线程如果在这是调用object对象中的任意synchronized方法时,需要等待,也就是同步。

5.object对象如果有设置方法和取值方法,那么这两个方法应该同时设为同步。否则会发生脏读。

6.什么是“锁重入”?

在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象锁的。大白话点就是,假如object对象有两个同步方法,线程A现在执行同步method1(),获取到了对象锁,method1()中可以调用synchronized method2()。

“可重入锁”:自己可以再次获取自己的内部锁。
当存在父子类继承时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

7.当某线程执行出现异常时,其持有的锁会自动释放。
8.同步不具备继承性

(二)synchronized同步语句块

1.使用synchronized方法有弊端吗?

有的。比如A线程调用同步方法,耗时长,那么B线程必须等待比较长的时间。

2.如何使用synchronized同步代码块解决程序执行效率低的问题?

同步代码块中的内容是同步的,前提是使用同一个“对象监视器”。和同步方法一样,synchronized(this)是锁定当前对象的。

  • synchronized(this)运行时,其他的同步方法和synchronized(this)呈现阻塞状态。

  • 同步方法执行时,其他的同步方法和synchronized(this)呈现阻塞状态。

对象监视器“可以是任意对象。格式:synchronized( 非this对象的*x )*

  • 在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized( 非this对象的x)中的代码块。
  • synchronized(x)和同步方法是异步的,和synchronized(this)也是异步的。可以用此来提高运行效率。

3.Class锁?

synchronized public static void printA()
synchronized关键字加在了静态方法上相当于给Class类上锁。和对象锁不是同一级别。Class锁可以对类的所有对象实例起作用。

4.“对象监视器”取String常量值时要注意?

JVM具有String 常量值缓存 的作用。synchronized(“aa”)具有同样的锁。一般,不使用String常量作为锁对象。我们可以采用:synchronized(new String(“aa”))作为锁。

5.同步的方法容易造成死循环,采用什么解决?

一个服务类中同步方法A出现死循环,同步方法B就得不到执行。如果这两个业务逻辑上允许异步,建议在这个服务类中使用同步代码块(new不同object)。那么,在main中使用俩线程分别执行A,B方法时,虽然调用的是同一个服务类的对象,也能有异步的效果。

6.线程死锁是什么?如何测试死锁?

死锁:不同的线程都在等待根本不可能被释放的锁,只要互相等待对方释放锁就有可能出现死锁。
例子:
DeadThread.java:

 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();                    }//等待B释放lock2.                    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();                    }//等待A释放lock1                    synchronized (lock1) {                        System.out.println("按lock2->lock1代码顺序执行了");                    }                }            }        }     }

Run.java:

    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();            }        }    }

测试方法:
1.进入cmd,进入JDK的安装文件夹bin/,执行jps命令。
2.得到运行线程(Run)的id,执行:jstack -l id号
3.查看是否有死锁情况。

7.内置类的实例化?

普通的内置类:

 PublicClass pc = new PublicClass();    PrivateClass  privateClass = pc.new PrivateClass();

静态内置类:

 PrivateClass privateClass = new PrivateClass();

内置类的两个不同锁的同步方法的执行也是异步的。

同步代码块synchronized(class2)对内部类class2上锁后,其他线程只能等待同步代码块把锁释放后才可以执行class2中的同步方法。

 public class OutClass {        static class InnerClass1 {            public void method1(InnerClass2 class2) {                String threadName = Thread.currentThread().getName();                //锁住了class2,class2的同步方法等待锁释放才能执行,                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方法");            }        }    }

测试类

 import test.OutClass.InnerClass1;    import test.OutClass.InnerClass2;    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();        }    }

(三)volatile关键字

1.volatile的作用是什么?

volatile(原为:易挥发的),主要的作用是是 变量 在多个线程中可见,该关键字只会修饰变量,不修饰方法。

2.为什么要让变量在多个线程中可见呢?volatile的原理是什么?

     先说原理,个人的理解是,由于内存和cpu的处理速度相差很大,为了提高运行的效率。JVM会为每个线程分配工作内存,同时整个程序也会有一个主内存(公共的)。
     如果没有加volatile,线程会在开始的时候从主内存中加载变量存入自己的工作内存,然后就一直在工作内存中获取该变量的值。这个时候,如果公共内存中变量改变了值,线程的工作内存中的变量不能及时得到这个改变了的值,最后造成的数据的不一致,即私有堆栈和公共堆栈中的值不同步。
     而加了volatile关键字之后,当线程想要访问这个变量是,会被强制性从公共堆栈中进行取值。
总结一点,就是volatile主要使用场合是在多个线程中可以感知实例变量被更改,并且可以获得最新的值使用,保证了数据的可见性

那为什么要让变量在多个线程中可见呢?其中一个作用就是解决死循环的问题。

 public class RunThread extends Thread {        volatile private boolean isRunning = true;        public boolean isRunning() {            return isRunning;        }        public void setRunning(boolean isRunning) {            this.isRunning = isRunning;        }        @Override        public void run() {            System.out.println("进入run了");            while (isRunning == true) {            }            System.out.println("线程被停止了!");        }    }

测试类:

 public class Run {        public static void main(String[] args) {            try {                RunThread thread = new RunThread();                thread.start();                Thread.sleep(1000);                thread.setRunning(false);                System.out.println("已经赋值为false");            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }

isRunning 这个变量在线程运行1S后改变了值,目的是想让线程停止,可实际并没有效果。
根据上面的原理可以知道,isRunning的值存在与公共堆栈和私有堆栈中,我们在公共堆栈中改变了这个值,但线程并没有将这种改变加载到它的私有堆栈中去,而是继续使用老值。所以,将线程类中的isRunning变量设为volatile就很必要啦。

3.volatile的缺点?

volatile关键字保证了同步数据的可见性,却不能处理数据的原子性,意思就是这是非线程安全的。所以,对于多个线程访问同一个实例变量,我们还是要使用加锁同步。
synchronized static 方法:可以代替volatile保证多线程中变量数据的一致。
synchronized代码块:可以使各线程间的数据值具有可视性。
举个栗子:

   public class Service {        private boolean isContinueRun = true;        public void runMethod() {            String anyString = new String();            while (isContinueRun == true) {                //添加了同步代码块                synchronized (anyString) {                }            }            System.out.println("停下来了!");        }        public void stopMethod() {            isContinueRun = false;        }    }

使用两个线程分别在run()中运行runMethod()和stopMethod(),测试类中实例化这俩线程,runMethod方法执行1s后去启动另一线程stopMethod()。没有添加上面代码中的同步代码块时,会死锁。添加完了呢,各线程之间的数据就会有可见性啦,死锁解决。

PS:本篇内容属于读书笔记,非常感谢高洪岩老师的《Java多线程编程的核心技术》。这本书非常实用,易懂。如果涉及任何侵权行为,请尽快告诉我。

原创粉丝点击