Java多线程编程核心技术 第二章

Java多线程编程核心技术 第二章

摘自《Java多线程编程核心技术》一书

对象及变量的并发访问

synchronized同步方法

“非线程安全”有可能导致“脏读”,即取到的数据被更改过的。

  • 当多个线程共同访问1个对象的实例变量,可能会不安全。

“线程安全”——获得的实例变量的值经过同步处理

  • 把变量放在方法里面,这时变量是私有的,就是安全的。
  • synchronized()生成的是对象锁

*为什么实例化不同的对象,线程就会异步操作?

因为当多个线程访问多个对象时,JVM会创建多个锁,比如,A线程要调用对象X的锁的方法,B线程要调用对象M的锁的方法,B不用等A执行完,就可以调用M的锁。

*只有共享资源的读写访问才需要同步化

如果没有多个线程对同一个对象的实例变量操作,就不会出现线程不安全问题,不需要用同步。

*两个线程访问一个同步方法,一个非同步方法,访问非同步的线程可以用异步方式调用非synchronized类型的方法

脏读

读取实例变量时,此值已经被其他线程更改过了。

  • 用synchronized解决

A线程调用synchronized X方法时,B线程不能调用X方法,但是B可以其他非synchronized的方法。如果B线程也要调用其他synchronized的非X方法,要等A线程调用完X方法,B才能调用其他synchronized的非X方法。

锁重入

在锁内部调用本类的其他synchronized方法,永远可以得到锁。

出现异常,锁自动释放

同步不具有继承性:父类的方法加了synchronized,不代表子类不用加synchronized就能上锁。——子类的方法也需要加synchronized才有效

synchronized同步语句块

  • 弊端:当A线程长时间调用synchronized的方法,B线程就需要等待那么长的时间
  • 解决:使用synchronized同步语句块——半同步,半异步
  • 同步性:一个线程访问synchronized(this)时,其他线程对同一个类的其他synchronized(this)代码块的访问被阻塞
  • 同步语句块是锁定当前对象
  • synchronized(非this对象)
    • 锁定非this对象的优点:一个类中如果有很多synchronized方法,synchronized(非this)方法和synchronized(this)方法是异步的,提高了运行效率

例如:

public class Service {
    private String usernameParam;
    private String passwordParam;
    public void setUsernamePassword (String username,String password) {
        try {
            String anyString = new String();
            synchronized (anyString) {
                System.out.println ("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"进入同步块");
            usernameParam = username;
            Thread.sleep(3000);
            passwordParam = password;
            System.out.println("线程名称为:"+Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"离开同步块");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果

线程名称为:A在1403594007194进入同步块
线程名称为:B在1403594007194进入同步块
线程名称为:B在1403594010194离开同步块
线程名称为:A在1403594010194离开同步块

从结果可看出是异步的,因为不是同一个锁,这时候很容易出现“脏读”

3个结论

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

静态同步synchronized方法与synchronized(class)代码块

synchronized用在static静态方法上就是对当前的*.java文件对应的Class类进行持锁(整个类锁上了)。

与加到非static方法的使用效果是一样的
本质上的不同是synchronized加到static方法是给Class类上锁,synchronized加到非静态方法上是给对象上锁

例如:

public class Service {
    synchronized public static void printA(){
        try{
            System.out.println("进入printA");
            Thread.sleep(3000);
            System.out.println("离开printA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public static void printB(){
        System.out.println("进入printB");
        System.out.println("离开printB");
    }

    synchronized public void printC() {
        System.out.println("进入printC");
        System.out.println("离开printC");
    }
}

上面的printA方法和printB方法都是对Service类上锁的,而printC方法是对对象上锁的,所以同样运行时,printC是异步的,printA和printB是同步的。
Class锁对类的所有对象实例起作用,即不同对象但是静态的同步方法仍是同步运行
同步synchronized(class)代码块的作用和synchronized static 方法的作用一样,看下面例子如何上锁——

public class Service {
    public static void printA(){
        synchronized (Service.class) {
            try{
                System.out.println("进入printA");
                Thread.sleep(3000);
                System.out.println("离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

数据类型String的常量池特性

  • JVM中具有String常量池缓存的功能
  • 将synchronized(string)同步块与String联合使用时,要注意常量带来的一些例外。(P103有例子)
  • 大多数情况下,synchronized代码块不用String作为锁对象,而改用其他。
  • 比如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 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 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
B
A
A
B
B
A
A
B
B
A
交替打印,持有的锁不是一个。

同步synchronized方法无限等待解决

  • 同步方法容易造成死循环
  • 可以通过同步块来解决
    例如:
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");
}

上面代码会造成死循环使其他同步方法无法运行,修改成用同步块

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

死锁

“死锁”必须避免,不同线程在等待不可能被释放的锁会导致所有任务无法完成,造成线程的“假死”。

用JDK的工具监测是否有死锁现象
1. 进入CMD工具
2. 进入JDK安装文件夹的bin目录
3. 执行jps命令
4. 执行jstack命令

这里写图片描述

内置类与静态内置类

什么是内置类
看例子:

public class PublicClass {
    private String username;
    class PrivateClass {
        private String age;
        public String getAge(){
            return age;
        }
        public void setAge(String age){
            this.age = age;
        }
    }
    public String getUsername(){
        return username;
    }
    public void setUsername(String username){
        this.username = username;
    }
}
public class Run {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("a");
        System.out.println(publicClass.getUsername());

        PrivateClass privateClass = publicClass.new PrivateClass();
        //是这样实例化的,如果PublicClass.java和Run.java不在同一个包中,则需要将PrivateClass内置声明成public
        privateClass.setAge("18");
        System.out.println(privateClass.getAge());
    }
}

内置类中还有一种叫静态内置类,看以下例子:

public class PublicClass {
    static private String username;
    static class PrivateClass {
        static private String age;
        public String getAge(){
            return age;
        }
        public void setAge(String age){
            this.age = age;
        }
    }
    public String getUsername(){
        return username;
    }
    public void setUsername(String username){
        this.username = username;
    }
}
public class Run {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("a");
        System.out.println(publicClass.getUsername());

        PrivateClass privateClass = new PrivateClass();
        //实例化
        privateClass.setAge("18");
        System.out.println(privateClass.getAge());
    }
}

内置类与同步

  • 同步代码块synchronized(class2)对class2上锁,其他线程只能用同步的方式调用class2的静态同步方式。
public class OutClass{
    static class InnerClass1{
        public void method1(InnerClass2 class2){
            synchronized (class2){

            }
        }
    }
    static class InnerClass2 {
        public synchronized void method2(){

        }
    }
}
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");
    Thread2 t2 = new Thread(new Runnable(){
        public void run(){
            in2.method2();
        }
    },"T2");
    t1.start();
    t2.start();  
}

结果,要在method1()运行完之后method2()才能运行。

锁对象的改变

  • 将任何数据类型作为同步锁时,注意是否有多个线程同时持有锁对象,如果持有相同的锁对象,则线程之间就是同步的;如果分别获得锁对象,线程之间是异步的。
  • 只要对象不变,即使对象的属性被改变,运行结果还是同步。(就是如果对象变了,运行结果就会是异步)

volatile关键字

作用:(使变量在多个线程间可见)强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

如果有“多继承”的情况,则也要用实现Runnable接口的方式来处理多线程的问题。

P121 在JVM设置为Server服务器的环境中,线程会一直在私有堆栈中取得值为true,而thread.setRunning(false)更新的是公共堆栈的变量值false,所以会出现死循环。
问题是–私有堆栈中的值和公共堆栈中的值不同步造成的。
解决问题–要用volatile关键字
当线程访问isRunning时,强制性从公共堆栈中取值

这里写图片描述

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){
            e.printStackTrace();
        }
    }
}

volatile最致命的缺点是不支持原子性。

volatile和synchronized比较:

  1. volatile是线程同步的轻量级实现
    • volatile比synchronized性能好
    • volatile只能修饰变量,synchronized可以修饰方法、代码块
  2. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
  3. volatile能保证数据可见性,但不能保证原子性;
    synchronized可以保证原子性,也可间接保证可见性(因为它会将私有内存和公共内存中的数据做同步)
  4. volatile解决多个线程之间的可见性,而synchronized解决多个线程访问资源的同步性。

volatile非原子的特性

线程安全包含原子性和可见性两个方面。

volatile不具备同步性,也就不具备原子性。
主要用于多线程读取共享变量时,获取最新值使用。

注意:如果改变实例变量中的数据,比如i++,也就是i=i+1
这样的操作不是一个原子操作,也就是非线程安全。
步骤分解如下:
1. 从内存中取出i的值
2. 计算i的值
3. 将i的值写到内存

假如在第二步计算值时,另一个线程也修改i的值,这时会出现“脏读”,这是需要用synchronized来解决

这里写图片描述

多线程环境中,use和assign是多次出现,这一操作不是原子性,所以在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会发生对应的变化,也就是私有内存和公共内存的变量不同步,所以计算出来的和预期的不一样,就出现非线程安全问题。

AtomicInteger原子类

一个原子类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全。

public class AddCountThread extends Thread {
    private AtomicInteger count = new AtomicInteger(0);
    @Override
    public void run(){
        for(int i = 0;i < 10000; i++) {
            System.out.println(count.incrementAndGet());
        }
    }
}
public class Run {
    public static void main (String[] args) {
        AddCountThread countService = new AddCountThread();
        Thread t1 = new Thread(countService);
        t1.start();
        Thread t2 = new Thread(countService);
        t2.start();
        Thread t3 = new Thread(countService);
        t3.start();
        Thread t4 = new Thread(countService);
        t4.start();
        Thread t5 = new Thread(countService);
        t5.start();
    }
}

运行结果成功累加到50000

原子类也并不完全安全

原子类在具有逻辑性的情况下输出结果也具有随机性。
- addAndGet()这些原子类的方法是原子的,但是方法与方法之间的调用不是原子的。

synchronized的特性:互斥性和可见性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值