入坑JAVA多线程并发(三)同步锁synchronized

先来一个例子:

public class Main {

    public static void main(String[] args) {
        ThreadTest test1 = new ThreadTest("线程1");
        ThreadTest test2 = new ThreadTest("线程2");
        test1.start();
        test2.start();
    }
}

class ThreadTest extends Thread{
    static int num = 0;

    public ThreadTest(String name){
        super(name);
    }

    @Override
    public void run() {
        for(int i = 0;i<1000;i++){
            num++;
        }
        System.out.println(num);
    }
}

这里ThreadTest继承了Thread类,run方法是把类变量进行1000次相加,启动了两个线程,所以预期结果应该是打印出:

1000
2000

实际是多次运行的情况下才会出现预期的输出,输出有可能是:1234,2000或者1349,1890之类的,原因是因为在两个线程对num进行++操作的时候没有同步。
java内存模式:
这里写图片描述
如上为java内存工作模式,线程并不是直接操作内存,而是把主内存的变量复制一份到工作内存,进行操作,然后把工作内存的变量同步到主内存当中,

比如上面的程序,test1的工作内存有一个num的副本,test2的工作内存也有一个num的副本,比如当num=123的时候,假设此时主内存,和全部工作内存都同步,num的值都是123,这时候test1对num++,得到124,在test1还没有把变量同步到主内存的时候,这时候test2也对num++,也得到124,然后test1和test2无论按照上面顺序把num同步到主内存中,最终就是进行了两次相加,但是实际上num只是从123到124。所以会出现和预期的结果不一样的输出。

synchronized同步锁

synchronized是java提供的一种同步的锁,先看一下改进的代码:

@Override
    public void run() {
        synchronized(ThreadTest.class){
            for(int i = 0;i<1000;i++){
                num++;
            }
            System.out.println(num);
        }
    }

只是对run方法更改了,加了一行synchronized(ThreadTest.class),这里起到的作用就是在一个线程进入这个代码块的时候要持有ThreadTest.class这个对象的锁,在这个线程执行期间,其他任何线程不能进入这个代码块。这样子无论执行多少次输出都是1000,2000。

synchronized()里面可以是任何对象,任何对象都可以是一把锁,这里ThreadTest.class是ThreadTest的class对象,因为只有一个,所以test1和test2之后又一个线程进入,等先进入的线程执行完自动释放锁等待线程才能进入代码块执行。如果把锁换成this,也就是执行时的线程对象,此处是两个不同的对象,所以还是会出现与预期不同的输出。这种给代码块加锁的方式叫做同步代码块

synchronized同步方法

同步方法就是把synchronized加在方法上,把ThreadTest类改成如下:

class ThreadTest extends Thread{
    static int num = 0;

    public ThreadTest(String name){
        super(name);
    }

    @Override
    public void run() {
        add();
    }

    private synchronized static void add(){
        for(int i = 0;i<1000;i++){
            num++;
        }
        System.out.println(num);
    }
}

把加的操作抽成一个方法add,在方法上加上synchronized,这里是一个静态方法,静态方法是默认锁的类的class对象,也就是和上面的synchronzied(ThreadTest.class)作用一样,如果实例方法,实例方法的默认锁的类的实例,也就是和上面的synchronzied(this)效果是一样的。

wait和sleep的区别

wait方法:当前线程暂停,释放锁,直到被唤醒

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

如下代码:

class ThreadA extends Thread{

    private String lock;

    public ThreadA(String name,String lock){
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            System.out.println(Thread.currentThread().getName()+"进入等待");
            try {
                lock.wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出等待");
        }
    }
}


public static void main(String[] args) throws InterruptedException {
        String  str = "lock";
        ThreadA test1 = new ThreadA("线程1",str);
        ThreadA test2 = new ThreadA("线程2",str);
        test1.start();
        test2.start();
    }

输出是:

线程1进入等待
线程2进入等待
线程1退出等待
线程2退出等待

如果把lock.wait(1000)改成Thread.sleep(1000),输出为:

线程1进入等待
线程1退出等待
线程2进入等待
线程2退出等待

wait方法会释放锁,其他线程可以进入同步代码块里面,而sleep不会释放锁,只有等当前对象结束sleep,执行结束,其他线程才能进入同步代码块

wait,notify,notifyAll

notify方法:唤醒对应的锁的线程,随意唤醒一个
notify方法:唤醒对应的锁的全部线程
代码如下:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        String  str = "lock";
        ThreadA test1 = new ThreadA("线程A-1",str);
        ThreadA test2 = new ThreadA("线程A-2",str);
        ThreadB test3 = new ThreadB("线程B",str);
        test1.start();
        test2.start();
        //保证test1和test2先执行wait方法
        Thread.sleep(1000);
        test3.start();
    }
}

class ThreadA extends Thread{

    private String lock;

    public ThreadA(String name,String lock){
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            System.out.println(Thread.currentThread().getName()+"进入等待");
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出等待");
        }
    }
}

class ThreadB extends Thread{

    private String lock;

    public ThreadB(String name,String lock){
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized(lock){
            System.out.println(Thread.currentThread().getName()+"唤醒其他线程");
            lock.notify();
        }
    }
}

输出为:

线程A-1进入等待
线程A-2进入等待
线程B唤醒其他线程
线程A-1退出等待

这里有可能最后只有线程A-2退出等待,因为是随机唤醒一个。
如果把notify换成notifyAll,输出为:

线程A-1进入等待
线程A-2进入等待
线程B唤醒其他线程
线程A-1退出等待
线程A-2退出等待

会唤醒全部lock对象wait的线程。

猜你喜欢

转载自blog.csdn.net/qq_34101608/article/details/80631181