多线程之synchronized学习(一)

      在并发编程中,我们会经常看到synchronized的使用;这次我会用两个例子来表达我对synchronized的一些理解,如果不对的地方还请大家指正,毕竟搞技术的一直都是一个学习的过程,有时候是无法避免进到了误区;


一、count初始值是3,此时三个线程对count进行自减操作, 期望的打印count的顺序是2、1、0

例子一主要是为了对多线程有一个初步的认识

public class MyThread01 extends Thread{
    private int count = 3;
    @Override
    public void run() {
        count --;
        System.out.println(Thread.currentThread().getName() + ",count : " + count);
    }
}
       MyThread01比较简单,就继承了Thread并且Override了run方法,在run方法就对count做了自减操作,接着就打印了当前线程的名称和count的值。
public class ThreadSimple01 {
    public static void main(String[] args) {
        MyThread01 myThread = new MyThread01();
        Thread thread1 = new Thread(myThread,"thread1");
        Thread thread2 = new Thread(myThread, "thread2");
        Thread thread3 = new Thread(myThread, "thread3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}
       此时在ThreadSimple01的main方法中创建了三个线程,分别操作myThread对象中count成员变量。运行完之后的结果:

thread1,count : 1
thread3,count : 0
thread2,count : 1
       从结果来看,打印的顺序是错乱的,当然也有概率性出现正确的打印结果;在多线程的情况下去操作同一个成员变量的count的时候,此时的count是线程不安全的,当第一个线程个刚好拿到了count值是3,同时又刚好准备进行自减操作时,第二个线程立马执行获取count的值,因为第一个线程还没有对count进行减一操作,所以第二个线程获取到count的值统一也是3,这种情况下就导致预期的结果跟实际打印的结果不一致。没有假设锁的时候,多个线程执行时,线程之间不是互斥的,是异步执行的,下一个线程不需要等上一个线程执行完之后才可以执行;对count自减操作的代码加上synchronized之后,就可以实现线程之间互斥的效果,因为我们知道线程之间会出现锁竞争的情况,谁竞争到了锁,则其它线程需要等待锁释放之后,在同时竞争锁,直到线程执行完。比如线程thread1、thread2、thread3;当thread2竞争到锁之后,thread1和thread3会等待thread2执行完释放锁之后,thread1和thread3再次竞争锁,以此类推。我们来看加上synchronized之后的代码:

public class MyThread01 extends Thread{
    private int count = 3;
    @Override
    public void run() {
        synchronized (MyThread01.class){
            count --;
            System.out.println(Thread.currentThread().getName() + ",count : " + count);
        }
    }
}
运行的结果:

thread2,count : 2
thread3,count : 1
thread1,count : 0
二、在多线程情况下,保证每个线程中业务流程执行完之后,在执行下一个线程的业务流程。
例子二是为了进一步更好的了解synchronized。

public class Student {
    private String name;

    /**
     * 线程是不安全的
     * @param state 状态
     */
    public synchronized void sayNameByUnsafe(int state){
        sayName(state);
    }

    /**
     * 线程是安全的,独占锁
     * @param state 状态
     */
    public void sayNameBySecurity(int state){
        synchronized(Student.class){
            sayName(state);
        }
    }
    private void sayName(int state){
        if(state == 1){
            name = "zhangsan";
            System.out.println("set name , value is " + name);
            try {
                Thread.sleep(2000);
            }catch (Exception e){}
        }else{
            name = "lisi";
            System.out.println( "set name , value is " + name);
        }
        System.out.println("my name is " + name);
    }
}
       Student类里面写的代码,从表面上看并没有什么问题,可是在多线程情况下,如果使用不当还是会出现预期的结果跟实际出现的结果不一致的情况:

public class ThreadSimple02 {

    public static void main(String[] args) {
        final Student student1 = new Student();
        final Student student2 = new Student();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                student1.sayNameByUnsafe(1);
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                student2.sayNameByUnsafe(2);
            }
        });
        thread2.start();
    }
}
 运行的结果:

set name , value is zhangsan
set name , value is lisi
my name is lisi
my name is zhangsan
 在ThreadSimple02类中,就会出现thread1流程还没结束完,thread2就开始执行了,跟我想要的结果不一样;其实在第一个例子就提到过线程锁竞争的问题,因为每一个对象都有自己的一个锁,对象锁是作用在实例化对象上的,因为对象可以实例化很多个,所以就会有很多对象锁;而从代码上看student1和student2相当于两个对象锁,thread1拿到的锁是student1的锁,thread2拿到的锁是student2的锁;
 明白这个道理的话,主要共用同一个锁就可以了。众所周知在在JVM在加载类文件的时候,会创建一个独一无二的 java.lang.Class对象,而通过获取类的字节码就可以拿到Class对象,也就是所谓的类锁。其实对象锁和类型从概念上理解的话跟互斥锁(也叫内置锁)也是一样的,只是为了更好的区分对象锁和类锁而已,但是对象锁和类锁实际上还是有很大的区别的。这里在大概的概括一下:
  •  对象锁:是作用在实例化对象上的,因为对象可以实例化很多个,所以就会有很多对象锁。
  •  类锁:上面也提到过,对象锁其实锁的是Class对象。因为对象可以很多个,但是一个类只能有一个Class对        象;同时在JVM在加载类文件的时候,会创建一个独一无二的

     从其它资料上了解到“JVM其实会维护一个锁计算器,每获取一次锁就加1,释放一次锁就减1,当锁计算器为0时,则释放锁”这句话说的应该是比较合理的,因为我们知道在栈中会对线程的调用做记录,而且每一个线程都有一个独立的栈,同时会使用帧(或者存储单元)记录线程新方法的调用情况,比如方法的返回地址、参数等等。明白这些之后 在调整 ThreadSimple02中线程调用的方法:

public class ThreadSimple02 {

    public static void main(String[] args) {
        final Student student1 = new Student();
        final Student student2 = new Student();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                student1.sayNameBySecurity(1);
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                student2.sayNameBySecurity(2);
            }
        });
        thread2.start();
    }
}

运行结果:

set name , value is zhangsan
my name is zhangsan
set name , value is lisi
my name is lisi
 此时thread1执行完之后,thread2在执行,这样就得到我们预期要的结果了。有些同学如果对Thread类比较熟的话,就会发现
Thread有三个join方法,一个是不带参数的另外两个是带参数的。不带参数的join()就是说等待子线程所有操作执行完成
之后主线程在继续执行;

public class ThreadSimple02 {

    public static void main(String[] args) {
        final Student student1 = new Student();
        final Student student2 = new Student();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                student1.sayNameByUnsafe(1);
            }
        });
        thread1.start();
        try {
            thread1.join();
        }catch (InterruptedException e){}

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                student2.sayNameByUnsafe(2);
            }
        });
        thread2.start();
    }
}
巧妙的使用hread1.join();也可以满足例子二的要求的,运行结果:

set name , value is zhangsan
my name is zhangsan
set name , value is lisi
my name is lisi
以上就是对多线程的一些理解,如果所错的地方,麻烦可以指正~!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值