Java并发编程(一)

synchronized同步方法

方法内的变量为线程安全

“非线程安全”问题存在于”实例变量”中,如果是方法内部的局部变量则不存在”非线程安全”问题,因为每个方法内部的变量都是私有的不存在共享的原因造成的。

实例变量非线程安全

假设有如下代码:

public class HasSelfPrivateNum {
    private int num = 0;

    public void add(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");
            }
        System.out.println(username+" " +num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

其中的num即表示是实例变量(区别于加了static关键字的类变量),当两个线程同时操作add函数,一个线程传入的username是a,另一个是b,此时对于变量num就存在非线程安全的问题了。

public class ThreadA extends Thread {

    private HasSelfPrivateNum hsp;

    public ThreadA(HasSelfPrivateNum hsp) {
        super();
        this.hsp = hsp;
    }

    @Override
    public void run() {
        super.run();
        hsp.add("a");
    }
}


public class ThreadB extends Thread {

    private HasSelfPrivateNum hsp;

    public ThreadB(HasSelfPrivateNum hsp) {
        super();
        this.hsp = hsp;
    }

    @Override
    public void run() {
        super.run();
        hsp.add("b");
    }
}

此时启动这两个线程就会出现对变量num的访问出现了问题。

a set over
b set over
b 200
a 200

此时的a的值也变成了由b设置的200。此时只需要在方法add的前面加上synchronized的关键字即可。此时的访问就是a在访问add函数的时候b就被阻塞了,等a访问完了之后才开始b的访问,打印结果就会正常(是同步的输出):

a set over
a 100
b set over
b 200

多个对象有多个锁

sychronized获得锁是当前对象的锁,不同的对象就会产生不同的锁,将不同的对象传入不同的线程,两个线程访问相应的同步方法的时候,产生的是异步的效果,因为即时加了锁,但是这个锁是对不同的对象的。

public static void main(String[] args) {
    HasSelfPrivateNum hsp = new HasSelfPrivateNum();
    HasSelfPrivateNum hsp2 = new HasSelfPrivateNum();
    ThreadA threadA = new ThreadA(hsp);
    ThreadB threadB = new ThreadB(hsp2);

    threadA.start();
    threadB.start();
}

输出的结果如下:

b set over
a set over
b 200
a 100

如果有多个同步方法

  • 如果线程A拥有object对象的锁,此时线程B如果访问的是同一个对象,则此时的B线程可以异步访问object对象中的非synchronized方法。
  • 如果在A拥有object的对象的锁之后,比如A访问object的同步方法methodA,此时线程B访问的是同一个对象的不同的同步方法methodB,此时的B需要等待获取到该对象的锁才能访问。

以上也就证明了sychronized的锁是对象的锁,而不是某个方法或者变量的锁。

如,在线程B中访问的是方法sub():

public void run() {
    super.run();
    hsp.sub("b");
}

在HasSelfPrivateNum中新增加了同步方法sub:

public synchronized void sub(String username) {
    try {
        if (username.equals("a")) {
            num = -100;
            System.out.println("sub a set over");

            Thread.sleep(2000);

        }else {
            num = -200;
            System.out.println("sub b set over");
        }
        System.out.println(username+" " +num);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

一下是输出的结果:

add a set over
a 100
sub b set over
b -200

从结果中可以看出来当线程A访问HasSelfPrivateNum的对象hsp的add方法的时候,获取了该对象的锁,此时线程B想要访问hsp对象的sub方法,但是此时没有hsp对象的锁,所以排队等候。等线程A访问结束之后将对像hsp的锁释放了之后线程B才能正常访问sub方法。

synchronized 锁重入

关键字sychronized具有锁重入的功能,当一个线程得到一个对象的锁之后,此时在还没有释放锁的时候是可以继续请求该对象的锁。比如,在一个同步方法service1中调用了另一个同步方法service2,如果是不可重入的的锁,在访问service1获取对象锁之后,如果再获取该对象的锁来访问service2就会出现死锁的情况。但是synchronized的可重入锁,就可以在一个同步方法中调用该对象的另一个同步方法。

可重入锁对于父子继承关系也是成立的。

比如,进程获取之类的实例锁之后,在访问子类实例的方法a的时候,可以在方法a中访问父类的同步方法b,此时也是可以直接获取到锁的。

比如,父类Main如下:

public class Main {
    public int i = 10;

    synchronized public void oper() {
        try {
            i--;
            System.out.println("main class i="+i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

子类Sub如下:

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

当线程获取Sub的对象锁之后,调用oper2方法,在给方法中调用了父类的oper()方法,由于sychronized是重入锁,所以可以正常访问父类的oper方法,如果是非可重入锁则会出现死锁。

public class MyThread extends Thread {

    @Override
    public void run() {
        super.run();
        Sub sub = new Sub();
        sub.oper2();
    }
}

输出结果为:

sub class i=9
main class i=8
sub class i=7
main class i=6
sub class i=5
main class i=4
sub class i=3
main class i=2
sub class i=1
main class i=0

同步方法内出现异常,锁会自动释放

如果线程A访问一个同步方法的时候就会获取这个方法所在的对象的锁,此时B线程也访问这个方法的时候就会排队等候。如果此时A线程访问的方法内出现了异常,此时A拥有的该对象的锁会立即被释放,此时B就可以获取到这个对象的锁了。

同步不具有继承性

当子类Sub继承了父类Main,且在父类中的方法oper是同步方法,继承过来之后如果没有重写父类的oper方法,则在调用父类的同步方法oper的时候依旧是正常的同步方法的调用,如果继承过来之后,子类重写了父类的oper方法,子类中的oper方法也必须加上sychronized关键字才能起到同步的作用。

使用sychronized方法,有明显的弊端就是,当同步方法中有耗时很长的操作,则会很影响多个线程的访问时间

synchronized同步代码块

synchronized代码块的使用

使用如下的形式将一段代码块进行包括:

synchronized(this) {
    //code here
    ...
}

当某个线程访问到这个代码块的时候,就获取到这个对象的锁,然后在这一段代码执行完之后才会释放这个对象的锁,其他的线程才能正常的访问这一段代码块。实际上使用synchronized将一段代码进行包围起来就是缩小了代码同步的范围。

不在synchronized同步块内的代码是异步执行的,在synchronized同步块里面的代码是同步执行的(即排队执行)。

synchronized 代码块间的同步性

synchronized(this)使用的也是对象监视器,即获取的锁是针对该类的对象来说的。也就是说,当一个线程访问某个对象object中的一个代码同步块的时候,其他的线程对同一个对象的所有的其他的synchronized(this)块的访问都会被阻塞。

和synchronized方法一样,synchronized(this)代码块锁定的是当前对象

等待通知机制

wait()、wait(n)、notify()、notifyAll()、sleep()

sleep(n)

睡眠n毫秒,通过Thread.sleep(n)方法调用,睡眠的时候不释放锁。

wait()

在wait方法处停止运行,等待notify或者中断为止。在进行wait()之前,必须获得Object对象级(wait是Object的方法,又因为Object是所有的父类,即其子类的对象即可)的锁,所以必须和synchronized一起使用。在使用wait之后会释放当前获得的对象锁。

Note:wait()是指无限制等待直到notify/notifyAll或者中断,wait(n)在时间n毫秒之后会自动唤醒

与wait相对应的就是notify了,两个一起构成了等待/通知机制来是先线程之间的通信:

notify()

也必须获得Object对象级的锁。调用成功后会随机选择一个处于等待状态中的线程,使其或者该对象的对象锁。

notifyAll()

就是对所有的线程都进行notify操作

注意:notify之后,当前线程不会立马释放当前获得的对象锁,所以被唤醒的线程也不会立马被唤醒获得对象锁,而是要等到notify所在的同步方法或者同步块执行完后才会释放锁。

所以:可知wait方法会释放锁,notify不会释放锁

过早就进行了通知

在通知之前如果都没有wait,则会导致在wait的地方一直处于等待,导致程序逻辑错误,此时可以通过设置标志位来进行使用,如果已经通知了,则不进行wait操作。

ReentrantLock类

相比sychronized而言,具有更多的功能和更为灵活的用法。

简单使用

1、先创建一个ReentrantLock lock对象
2、在需要加锁的地方使用lock.lock()即可
3、在释放锁的地方使用lock.unlock()即可

使用Condition对象来实现等待、通知

是什么

是一种对象监视器,每个lock对象可以创建多个监视器,在使用condition之前必须要获取到当前对象的锁

如何使用

和ReentrantLock使用一样,先需要创建一个Condition对象,创建的方式就是使用当前的lock对象来进行创建:

Condition con = lock.newCondition();

con.await()方法相当于Object中的wait()方法
con.await(long time, TimeUnit unit)方法相当于Object中的wait(long time)
con.signal()相当于Object中的notify()
con.signalAll()相当于Object中 的notifyAll()

Note : TimeUnit是一个枚举类型,用来指明传入的等待时间的单位,比如:TimeUnit.MILLISECONDS(毫秒)

使用Condition对象实现通知部分线程

由于一个lock对象可以获取多个Condion对象,所以,可以通过获取多个Condition对象,然后不同的方法中使用不一样的condition去进行await操作,这样,在多线程中,使用不同的方法的线程就可以调用不同的condition进行signal操作。

public class TestReentrantLock {

    private ReentrantLock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void methodA() {
        try {
            lock.lock();
            System.out.println("in A");
            conditionA.await();
            System.out.println("out A");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void methodB() {
        try {
            lock.lock();
            System.out.println("in B");
            conditionB.await();
            System.out.println("out B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void sigA(){
        lock.lock();
        System.out.println("sig A");
        conditionA.signal ();
        lock.unlock();
    }

    public void sigB(){
        lock.lock();
        System.out.println("sig B");
        conditionB.signal();
        lock.unlock();
    }

}

实现生产者和消费者模式:多对多交替打印

(有待实现)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值