【Java基础】线程笔记——synchronized

synchronized

public class ThreadA extends Thread{

    private Count count;

    public ThreadA(Count count){
        this.count = count;
    }

    @Override
    public void run() {
        count.add();
    }
}
public class Count {

    private int num = 0;
    /**
     * synchronized
     * 
     */
    public void add(){
        synchronized(this){
            try {
                Thread.sleep(51);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num += 1; 
            System.out.println("["+Thread.currentThread().getName()+"]" + ":" + num);
        }
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }


}

运行

public static void main(String[] args) {
        Count count = new Count();
        for (int i = 0; i < 5; i++) {
            ThreadA t = new ThreadA(count);
            t.start();
        }

        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("5个线程最后的值"+count.getNum());
}

console

[Thread-0]:1
[Thread-1]:2
[Thread-2]:3
[Thread-4]:4
[Thread-3]:5
5个线程最后的值5

如果不加synchronized关键字,数据会错误

[Thread-0]:1
[Thread-1]:2
[Thread-2]:3
[Thread-4]:4
[Thread-3]:5
5个线程最后的值3

保证同一时刻最多只有一个线程执行该段代码
可修饰于方法和代码块

修饰在方法上

synchronized methodName(){...}

修饰在代码块上

methodName(){
    synchronized(Object){
    }
}

synchronized隐式锁特性

① Object可以是参数本身、指定对象、this
② 不推荐使用this和方法参数,而是指定一个小的对象(隐式锁/同步锁/互斥锁)
③ 虽然修饰的地方不尽相同,最终都是锁在一个对象上面
④ 默认修饰对象本身,等同synchronized(this)
之所以叫隐式锁是因为指定一个Object对象作为锁,和它持有相同对象锁的地方将产生互斥性,而不是只有当前代码块或者方法体。另一个相对显示锁不需要加锁或者解锁操作

隐式锁规则

① 当两个并发线程访问同一个对象中的synchronized同步代码块时,一个时间内只有一个线程可以执行,另一个线程必须等待当前线程执行完这个代码块才能继续执行
②当一个线程访问一个对象中的synchronized同步代码块时,另外一个线程仍然可以访问该对象的非同步代码块
③ 以上规则对其他对象锁同样适用

为了适应高并发以及快速响应的要求,性能以及执行效率从低到高排序

方法体 < 方法块 (synchronized(this))
原因:方法体在方法外面排队加锁,进入方法体,分配资源还需要一定的时间


private byte[] lock = new byte[1];
synchronized(this) < synchronized(lock)
原因:锁的对象不一样,锁是对象,加锁和解锁都需要释放锁对象,对象越小越好(所以造一个1字节的对象最好)

总结

  1. 如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。
  2. 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的 synchronized 同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问 synchronized 同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁
  3. 访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响
  4. 当一个线程访问一个对象中的synchronized同步代码块时,另外一个线程仍然可以访问该对象的非同步代码块
  5. 类级别锁被特定类的所有示例共享,它用于控制对 static 成员变量以及 static 方法的并发访问。具体用法与对象级别锁相似
  6. 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized 关键字经过编译后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令。根据虚拟机规范的要求,在执行 monitorenter 指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加 1,相应地,在执行 monitorexit 指令时会将锁计数器减 1,当计数器为 0 时,锁便被释放了。由于 synchronized 同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁
  7. 使用 synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj 为对象的引用,如果获取了 obj 对象上的对象级别锁,在并发访问 obj 对象时时,便会在其 synchronized 代码处阻塞等待,直到获取到该 obj对象的对象级别锁。当 obj 为 this 时,便是获取当前对象的对象级别锁

扩展

synchronized 的另个一重要作用:内存可见性

加锁(synchronized 同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性。我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状态后,其他线程能够看到该变化


内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。为了确保所有的线程都能看到共享变量的最新值,可以在所有执行读操作或写操作的线程上加上同一把锁

这里写图片描述

当线程 A 执行某个同步代码块时,线程 B 随后进入由同一个锁保护的同步代码块,这种情况下可以保证,当锁被释放前,A 看到的所有变量值(锁释放前,A 看到的变量包括 y 和 x)在 B 获得同一个锁后同样可以由 B 看到。换句话说,当线程 B 执行由锁保护的同步代码块时,可以看到线程 A 之前在同一个锁保护的同步代码块中的所有操作结果。如果在线程 A unlock M 之后,线程 B 才进入 lock M,那么线程 B 都可以看到线程 A unlock M 之前的操作,可以得到 i=1,j=1。如果在线程 B unlock M 之后,线程 A 才进入 lock M,那么线程 B 就不一定能看到线程 A 中的操作,因此 j 的值就不一定是 1

public class  SynchronizedInteger  
{  
    private int value;  

    public synchronized int get(){  
        return value;  
    }  
    public synchronized void set(int value){  
        this.value = value;  
    }  
} 

对 set 和 get 方法进行了同步,加上了同一把对象锁,这样 get 方法可以看到 set 方法中 value 值的变化,从而每次通过 get 方法取得的 value 的值都是最新的 value 值

Conllection线程安全的API

集合本质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施

这里写图片描述

注意:在 Java 语言中,大部分的线程安全类都是相对线程安全的,它能保证对这个对象单独的操作时线程安全的,我们在调用的时候不需要额外的保障措施,但是对于一些特定的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。例如 Vector、HashTable、Collections的synchronizedXxxx()方法包装的集合等

//为了安全起见,仅使用同步列表的一个引用,这样可以确保控制了所有访问  
        //集合必须同步化,这里是一个List  
        List<String> nameList = Collections.synchronizedList(new ArrayList<String>());

        //nameList中的add方法是同步方法,会获取nameList实例的对象锁  
        nameList.add("zhangsan");  
        nameList.add("lisi");  
        nameList.add("wanger");  
        nameList.add("hanfei");  

        //获取nameList实例的对象锁,  
        //迭代时,阻塞其他线程调用add或remove等方法修改元素  
        synchronized ( nameList ) {  
            Iterator<String> iter = nameList.iterator();  
            while ( iter.hasNext() ) {  
                String s = (String) iter.next();  
                System.out.println("found string: " + s + ", length=" + s.length());  
            }  
}  

多线程文档参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值