Java多线程编程核心技术--第2章 对象及变量的并发访问

2.1 synchronized同步方法

方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量私有特性造成的。

2.1.2 实例变量非线程安全

使用多个线程并发访问PrivateNum类中的addI方法。

public class PrivateNum {
    private int num = 0;

    synchronized public void addI(String username){
        try{
            if(username.equals("a")){
                num = 100;
                System.out.println("a set over");
                Thread.sleep(2000);
            }else{
                num = 200;
                System.out.println("b set over");
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread {
    private PrivateNum ref;

    public ThreadA(PrivateNum ref){
        super();
        this.ref = ref;
    }
    @Override
    public void run(){
        super.run();
        ref.addI("a");
    }
}

class ThreadB ...

class Run {
    public static void main(String[] args){
        PrivateNum ref = new PrivateNum();
        ThreadA a = new ThreadA(ref);
        a.start();
        ThreadB b = new ThreadB(ref);
        b.start();
    }
}

/*结果
a set over
a num = 100
b set over
b num = 200*/

只有共享资源的读写访问才需要同步化,如果不是共享资源,就没有同步的必要。

同一个类,有两个方法methodA(),methodB()。二者全都加锁synchronized。有两个线程ThreadA,ThreadB.分别调用methodA,methodB.这个线程执行在同一个main中。不同的方法加锁之后也是要按顺序执行的。对象锁只有一个。

但是如果对象锁的不是同一个对象,则会出现异步

public class Test {
    public static void main(String[] args) {
        Service service = new Service();    //不是同一个对象
        Service service1 = new Service();
        ThreadA a= new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b =new ThreadB(service1);
        b.setName("B");
        b.start();
    }
}

class Service {
    private String name;
    private String password;
    private String lockString = new String();

    public void setName2Password(String name,String password){
        try{
            synchronized (lockString){
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
                this.name = name;
                Thread.sleep(1000);
                this.password = password;
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }
    public void run(){
        service.setName2Password("a","aa");
    }
}

class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }
    public void run(){
        service.setName2Password("a","aa");
    }
}

2.1.5 脏读

之前同步是针对赋值,但在取值时仍可能出现意外。
在读取实例变量时,此值已经被其他线程更改过了。针对变量的set和get方法都要加锁。保证轮流执行。

2.1.6 synchronized锁重入

当一个线程得到一个对象锁之后,再次请求此对象锁是可以再次得到该对象的锁。

一个synchronized方法、块内部调用本类其他synchronized方法、块时,是永远可以得到锁的。

可重入锁概念:
有一条线程获得某对象的锁,此时对象锁没释放,当其再次要获得这个对象的锁的时候是可以再次获得的,如果不可以锁重入的话,就会造成死锁

可重入锁也支持父子类继承环境

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

class Main {
    protected int i = 10;

    synchronized public void method() {
        try {
            i--;
            System.out.println("main print i:" + i);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Sub extends Main {
    synchronized public void method() {
        try {
            while(i>0){
                i--;
                System.out.println("sub print i:" + i);
                Thread.sleep(100);
                this.method();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyThread extends Thread{
    public void run(){
        Sub sub = new Sub();
        sub.method();
    }
}

当父子类继承关系时,子类完全可通过“可重入锁”调用父类同步方法的。

扫描二维码关注公众号,回复: 870740 查看本文章

2.1.7 出现异常,锁自动释放

2.1.8 同步不具有继承性

必须在子类方法中添加synchronized关键字才可以。

2.2 synchronized同步语句块

声明在方法上某时候是有弊端的,例如:A线程调用同步方法执行一个长时间任务,B线程必须等待很长时间。

两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行。另一个线程必须等待当前线程执行完这个代码块后才能执行。说明synchronized使用的“对象监视器”是一个。

2.2.4 一半异步,一半同步

在synchronized块中就是同步执行,不在synchronized块中的代码就是异步执行。

synchronized同步方法和synchronized(this)同步代码块都是对同个类的其他同步进行阻塞;同一时间只有一个线程可执行synchronized同步方法或者synchronized(this)同步代码块。

2.2.7 将任意对象作为对象监视器

使用非this对象作为同步代码块的参数。

public class Test {
    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();
    }
}

class Service {
    private String name;
    private String password;
    private String lockString = new String();

    public void setName2Password(String name,String password){
        try{
            synchronized (lockString){
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
                this.name = name;
                Thread.sleep(3000);
                this.password = password;
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }
    public void run(){
        service.setName2Password("a","aa");
    }
}

class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }
    public void run(){
        service.setName2Password("a","aa");
    }
}

锁非this对象的优点
如果一个类有很多个synchronized方法,这时虽然能实现同步,但会阻塞,影响运行效率;
但如果用同步代码块锁非this对象,则synchronized(not this)代码块中的程序与同步方法是异步的,不和其他锁this的同步方法争抢this锁,则提高运行效率。

但是

public class Service {
    private String name;
    private String password;

    public void setName2Password(String name,String password){
        try{
            String lockString = new String();   //锁对象放在这里了
            synchronized (lockString){
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
                this.name = name;
                Thread.sleep(3000);
                this.password = password;
                System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

这样的话,同步就没有作用了。

2.2.8 synchronized(not this)3个结论

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

验证第一个结论

class MyObject {}

//锁
class Service {
    public void testMethod(MyObject my){
        synchronized(my){
            try{
                System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
            }catch(){
                e.printStackTrace();
            }
        }
    }
}

//Thread
class ThreadA extend Thread {
    private Service service;
    private MyObject my;

    public ThreadA(Service service,MyObject my){
        this.service = service;
        this.my = my;
    }

    public void run(){
        super.run();
        service.testMethod(my);
    }
}

public class Run_1 {
    public static void main(String[] args){
        Service service = new Service();
        MyObject my = new MyObject();
        ThreadA a= new ThreadA(service,my);
        a.setName("a");
        a.start();
        ThreadB b= new ThreadB(service,my);
        b.setName("b");
        b.start();
    }
}

如果MyObject不是同一个对象,则不是同步效果了。

第2个结论验证

class MyObject {
    synchronized public void speedPrintString(){
        System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
        Thread.sleep(2000);
        System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
    }
}

//锁
class Service {
    public void testMethod(MyObject my){
        synchronized(my){
            try{
                System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
                Thread.sleep(2000);
                System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
            }catch(){
                e.printStackTrace();
            }
        }
    }
}

class ThreadB extend Thread {
    private MyObject my;

    public ThreadA(MyObject my){
        this.my = my;
    }

    public void run(){
        super.run();
        service.testMethod(my);
    }
}

class ThreadA extend Thread {
    private Service service;
    private MyObject my;

    public ThreadA(Service service,MyObject my){
        this.service = service;
        this.my = my;
    }

    public void run(){
        super.run();
        my.speedPrintString();
    }
}

public class Run_1 {
    public static void main(String[] args){
        Service service = new Service();
        MyObject my = new MyObject();
        ThreadA a= new ThreadA(service,my);
        a.setName("a");
        a.start();
        ThreadB b= new ThreadB(my);
        b.setName("b");
        b.start();
    }
}

验证第三个结论

//上述代码中MyObject的方法加入代码块
class MyObject {
    public void speedPrintString(){
        synchronized(this){
            System.out.println("getlock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
            Thread.sleep(2000);
            System.out.println("releaselock time:"+System.currentTimeMillis() + "thread name:"+Thread.currentThread().getName());
        }
    }
}

2.2.9 synchronized(class)代码块

synchronized可以用在static静态方法上,就是对某个类的Class类进行加锁。

public class Test {
    public static void main(String[] args) {
        ThreadA a= new ThreadA();
        a.setName("A");
        a.start();
        ThreadB b =new ThreadB();
        b.setName("B");
        b.start();
    }
}

class Service {
    synchronized public static void printA(){
        try{
            System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
            Thread.sleep(1000);
            System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    synchronized public static void printB(){
        System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"进入同步块");
        System.out.println("线程名:"+Thread.currentThread().getName()+"在"+ System.currentTimeMillis()+"离开同步块");
    }
}

class ThreadA extends Thread{
    public void run(){
        Service.printA();
    }
}

class ThreadB extends Thread{
    public void run(){
        Service.printB();
    }
}

synchronized关键字加到static方法上是给Class类加锁,而针对非静态方法是给对象上锁。

在该类中如果有this锁的方法,那么和class锁会产生异步调用,因为class锁可对所有对象实例起作用。

2.2.10 String的常量池特性

注意synchronized(String)中,常量池带来的一些例外。

class Service {
    public static void print(String param){
        try{
            synchronized(param){
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }

    public void run(){
        service.print("AA");
    }
}

class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }

    public void run(){
        service.print("AA");
    }
}

public class Test {
    public static void main(String[] args) {
        Service ser = new Service();
        ThreadA a= new ThreadA(ser);
        a.setName("A");
        a.start();
        ThreadB b =new ThreadB(ser);
        b.setName("B");
        b.start();
    }
}

因为两个线程用的字符串都是“AA”,这个是常量,因此两线程持有相同的锁。造成线程B不能执行。这是String 常量池带来的问题。通常不使用string作为锁对象。而改用,比如new Object()作为锁对象。

2.2.11 同步synchronized方法无限等待的解决办法

死循环造成其他同步方法无法获得锁

//methodB永远不会获取锁
public class Serive {
    synchronized public void methodA(){
        System.out.println("A begin");
        boolean is = true;
        while(is){
        }
        System.out.println("A end");
    }
    synchronized public void methodB(){
        System.out.println("B begin");
        System.out.println("B end");
    }
}

可以用同步块来解决同步方法死锁的问题

public class Serive {
    Object object1 = new Object();
    public void methodA(){
        synchronized (object1){
            System.out.println("A begin");
            boolean is = true;
            while(is){
            }
            System.out.println("A end");
        }
    }

    Object object2 = new Object();
    public void methodB(){
        synchronized (object2){
            System.out.println("B begin");
        System.out.println("B end");
        }
    }
}

2.2.12 多线程死锁

不同的线程都在等待根本不可能被释放的锁,导致所有任务无法继续完成。造成线程“假死”。

死锁实例

public class DeadThread implements Runnable {
    public String name;
    public Object lock1 = new Object();
    public Object lock2 = new Object():
    public void setFlag(String name){
        this.name = name;
    }

    public void run(){
        if(name.equals("a")){
            synchronized(lock1){
                try{
                    System.out.println("name"+name);
                    Thread.sleep(3000);
                }catch(){
                    e.printStackTrace();
                }
                //互相持有对方的锁,造成死锁
                synchronized (lock2){
                    System.out.println("lock1->lock2死锁");
                }
            }
        }
        if(name.equals("b")){
            synchronized(lock2){
                try{
                    System.out.println("name"+name);
                    Thread.sleep(3000);
                }catch(){
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("lock2->lock1死锁");
                }
            }
        }
    }
}

2.3 volatile关键字

主要作用使变量在多个线程间可见。

作用:volatile强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

public class RunThread extends Thread {
    volatile private boolean isRun = true;
    public boolean isRun(){
        return isRun;
    }
    public void setRun(boolean isRun){
        this.isRun = isRun;
    }

    public void run(){
        System.out.println("进入run");
        while(isRun == true){
        }
        System.out.println("线程被停止了");
    }
}

public static void main(String[] args){
    RunThread th = new RunThread();
    th.start();
    Thread.sleep(1000);
    th.setRun(false);
}

缺点:不支持原子性。

synchronized和volatile比较

  1. volatile是线程同步的轻量级实现,只能修饰变量,synchronized可以修改方法、以及代码块。
  2. 多线程访问volatile不会阻塞,而synchronized会阻塞。
  3. volatile保证数据可见性,不保证原子性;synchronized保证原子性,间接保证可见性,因为它将私有内存和公共内存中数据进行同步。
  4. 解决方向不同:volatile解决的是变量在多个线程间的可见性;synchronized解决的是多个线程之间访问资源的同步性。

如果addCount方法加锁,count变量没必要加volatile

class MyThread extends Thread {
    //不用加
    volatile public static int count;

    synchronized private static void addCount(){
        for(int i=0;i < 100;i++){
            count++;
        }
        System.out.println("count="+count);
    }
    @Override
    public void run(){
        addCount();
    }
}

public class Run {
    public static void main(String[] args){
        MyThread[] myArray = new MyThread[100];
        for(int i=0;i<100;i++){
            myArray[i] = new MyThread();
        }
        for(int i=0;i<100;i++){
            myArray[i].start();
        }
    }
}

volatile出现非线程安全的原因

  1. read和load阶段:从主存复制变量到当前线程工作内存
  2. use和assign阶段:执行代码,改变共享变量值
  3. store和write阶段:用工作内存数据刷新主存对应变量值

load,use,asign都属于非原子性操作。

2.3.5 使用原子类进行i++操作

i++本身也是非线程安全的,分解步骤:

  1. 从内存中取出i的值
  2. 计算i的值
  3. 将i的值写到内存中

可以使用AtomicInteger保证原子性操作。

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 count = new AddCountThread();
        Thread t1= new Thread(count);
        t1.start();
        Thread t2= new Thread(count);
        t2.start();
        Thread t3= new Thread(count);
        t3.start();
    }
}

2.3.6 原子类不安全的时候

当有一个方法例如addNum()对AtomicInteger进行操作时,如果多个线程调用addNum()方法,并且addNum()方法本身不是synchronized的,那么就会出现异步调用的问题。虽然AtomicInteger本身是线程安全的。

猜你喜欢

转载自blog.csdn.net/a464700300/article/details/80209349