Java进程-同步与异步

java synchronized详解

Java中Synchronized的用法

知识点1、Synchronized同步静态方法和非静态方法总结

1、synchronized修饰代码块

1、两个并发的线程访问同一个对象中的synchronized(this)同步代码块时,同一时间内只有一个线程执行,另外一个线程需要等到当前线程结束之后才能执行。

package MapTest;

public class Thread1 implements Runnable{
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    /**
     * 一旦一个对象实现了runable接口,那么就意味着创建了一个线程,启动线程的时候该对象的run()方法
     * 也就被启动
     */
    @Override
    public void run() {
       synchronized (this){
           for(int i = 0; i < 10; i++){
               System.out.println(Thread.currentThread().getName() + "===" + i);
           }
       }
    }

    public static void main(String[] args){
        //实例化
        Thread1 thread1 = new Thread1();
        //创建两个线程
        Thread a = new Thread(thread1,"A");
        Thread b = new Thread(thread1,"B");
        //先启动b线程,再启动a线程
        b.start();
        a.start();
    }
    /**
     * A===0
     A===1
     A===2
     A===3
     A===4
     A===5
     A===6
     A===7
     A===8
     A===9
     B===0
     B===1
     B===2
     B===3
     B===4
     B===5
     B===6
     B===7
     B===8
     B===9
     */


}

2、当一个线程访问对象中的同步代码块(synchronized(this)),另外一个线程仍然可以访问该对象中的非同步代码块。

package MapTest;

public class Thread2 {

    //synchronized代码块
    public void fun1() throws InterruptedException {
        synchronized (this) {
            for(int i = 0; i < 5; i++){
                System.out.println(Thread.currentThread().getName() + "==" + i);
                Thread.sleep(100);
            }
        }
    }

    //非synchronized代码块
    public void fun2() throws InterruptedException {
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "==" + i);
            Thread.sleep(100);
        }
    }
    
    public static void main(String[] args){
        //实例化一个对象
        Thread2 thread2 = new Thread2();

        //创建一个进程a
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread2.fun1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"a");

        //创建一个进程b
        //因为该对象没有继承runable类,所以实例化该对象的时候,创建线程并不会call里面的方法
        //所以需要new一个runable(),通过对象来调用方法(因为是静态方法调用非静态方法)
        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread2.fun2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"b");

        a.start();
        b.start();
    }

    /**
     * a==0
     b==0
     b==1
     a==1
     a==2
     b==2
     a==3
     b==3
     b==4
     a==4
     */
}

3、当一个线程访问对象的同步代码块(synchronized(this)),另外一个线程对该对象中的其他同步代码块(synchronized(this))的访问将被阻塞。

    //非synchronized代码块===》改成synchronized代码块
    public void fun2() throws InterruptedException {
        synchronized (this) {
            for(int i = 0; i < 5; i++){
                System.out.println(Thread.currentThread().getName() + "==" + i);
                Thread.sleep(100);
            }
        }
    }
b==0
b==1
b==2
b==3
b==4
a==0
a==1
a==2
a==3
a==4
需要注意的是哪个线程先被执行,不是看线程调用start方法的顺序,也不是创建线程的顺序。而是由CPU自动随机调用哪个线程先执行,当然如果针对一个线程组得话,就有先后顺序了,如下面得存钱和取前得操作

4、存钱和取钱的操作

account对象

package MapTest;

public class Account {
    private String name;
    private float amount;

    public Account(String name, float amount) {
        this.name = name;
        this.amount = amount;
    }

    //存钱
    public void deposit(float amt) throws InterruptedException {
        amount += amt;
        Thread.sleep(1000);
    }

    //取钱
    public void withdraw(float amt) throws InterruptedException {
        amount -= amt;
        Thread.sleep(1000);
    }

    public float show(){
        return amount;
    }

}

账户操作对象类

package MapTest;

public class AccountOperator implements Runnable {

    private Account account;

    public AccountOperator(Account account) {
        this.account = account;
    }

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        try {
            synchronized (this) {
                account.deposit(1000);
                account.withdraw(100);
                System.out.println(Thread.currentThread().getName() + "==" + account.show());
            }
            /**
             * thread1==1400.0
             thread4==2300.0
             thread3==3200.0
             thread2==4100.0
             thread0==5000.0
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}

创建线程组操作

package MapTest;

public class AccountTest {

    public static void main(String[] args){
        //实例化一个账户
        Account zs = new Account("zs",500);
        //创建一个账户操作对象
        AccountOperator zsOperator = new AccountOperator(zs);

        //创建五个线程来操作该账户
        final int THREAD_NUM = 5;
        //创建一个线程组
        Thread[] threads = new Thread[THREAD_NUM];
        for(int i = 0; i < THREAD_NUM; i++){
            //利用账户操作对象创建线程
            threads[i] = new Thread(zsOperator,"thread" + i);
            threads[i].start();
        }
    }
}

最后打印的结果如下

thread0==1400.0
thread3==2300.0
thread4==3200.0
thread2==4100.0
thread1==5000.0
可见每个线程都是在前一个线程所有操作结束的基础之上才进行操作,也就是说我每次存1000取500的操作都是原子性的。都必须处理完才能进行第二次操作。因为这里的存和取得操作都属于同步代码块synchronized,用官方一点得说法就是说一个线程访问该对象得同步代码块时,其他进程对该代码块得访问将被阻塞。

2、synchronized修饰方法

1、修饰非静态方法

package MapTest;

public class SynFun {
    //创建一个synchronized修饰得方法
    public synchronized void fun() throws InterruptedException {
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "==" + i);
            Thread.sleep(1000);
        }
    }
}
package MapTest;

public class TestFun {
    public static void main(String[] args){

        SynFun synFun = new SynFun();
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A");

        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B");

        b.start();
        a.start();

    }
    /**
     * A==0
     A==1
     A==2
     A==3
     A==4
     B==0
     B==1
     B==2
     B==3
     B==4
    这里和同步代码块得意思差不多
     */
}

对于非静态方法,实际上是对该方法得对象加锁,也就是“对象锁”。也就是说同一个对象中所有得非静态方法共用一把对象锁,不同得进程不能同时访问该对象中得所有非静态方法。这时候访问该对象静态方法得所有进程是互斥得。其实和代码块也差不多。只有在两个进程访问不同对象中的非静态方法时,才不是互斥的,因为这时有两把对象锁。

2、修饰静态方法

package MapTest;

public class SynFun {
    //创建一个synchronized修饰得方法
    public synchronized static void  fun1() throws InterruptedException {
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "=静态=" + i);
            Thread.sleep(1000);
        }
    }

    //创建一个synchronized修饰得方法
    public synchronized  void  fun2() throws InterruptedException {
        for(int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "=非静态=" + i);
            Thread.sleep(1000);
        }
    }
}
package MapTest;

public class TestFun {
    public static void main(String[] args){

        SynFun synFun = new SynFun();
        //访问静态方法得进程A1
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A1");

        //访问静态方法得进程A2
        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A2");

        //访问非静态方法得进程B1
        Thread c = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B1");

        //访问非静态方法得进程B2
        Thread d = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synFun.fun2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B2");

        b.start();
        a.start();
        c.start();
        d.start();

    }
    /**
     *A2=静态=0
     B2=非静态=0
     B2=非静态=1
     A2=静态=1
     A2=静态=2
     B2=非静态=2
     A2=静态=3
     B2=非静态=3
     A2=静态=4
     B2=非静态=4
     A1=静态=0
     B1=非静态=0
     A1=静态=1
     B1=非静态=1
     B1=非静态=2
     A1=静态=2
     A1=静态=3
     B1=非静态=3
     B1=非静态=4
     A1=静态=4
     */
}
可见,不同的进程在访问同一个对象中得静态与非静态方法之间不是互斥的,因为一个使用的时对象锁,一个使用的是类对象锁。不同的进程在访问同一个对象的非静态方法是互斥的,因为synchronized修饰静态方法实际上是对类对象枷锁。也就是永远只有一把锁,因为一个对象只对应一个类对象(.class)。



阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页