为什么我用了synchronized还是线程不安全是什么意思?

为了账号安全,请及时绑定邮箱和手机
为什么我的synchronized依然不能保持线程安全?
package com.imooc.concurrent.public class Test { public static void main(String[] args)
final aquryI a = new aquryI();
while(true){
new Thread("Thread A") {
public void run() {
a.write();
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}.start();
new Thread("Thread B") {
public void run() {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}.start();
System.out.println("\n");
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }class aquryI {
public synchronized void write(){
System.out.println(Thread.currentThread().getName());
public synchronized void read(){
System.out.println(Thread.currentThread().getName());
System.out.println(i);
}}运行结果如下:Thread A0Thread AThread A10Thread B10Thread A10Thread AThread A20Thread B20Thread A20Thread AThread A30Thread B30Thread A30Thread AThread A40Thread B40Thread A40Thread AThread A50Thread B50Thread A50Thread AThread A60Thread B60Thread A60Thread B60Thread AThread A70Thread A70Thread AThread A80Thread B80Thread A80Thread AThread A90Thread B90Thread A90Thread B90Thread AThread A100.....Thread A120Thread B120Thread AThread A130Thread A130Thread B130Thread AThread A140Thread A140Thread AThread A150Thread B150Thread A150Thread AThread A160Thread B160Thread B160Thread A160Thread AThread A170虽然大部分时候可以保证
但是有时候还是会出现A B交替运行的现象 这是为什么?
想必你对线程安全的理解不一样啊。你说的AB线程交替运行现象是并行运算,这就是正常现象,只要是多核计算机,多线程同时运行,就会出现交替的运行。代码中加上 synchronized
,执行结果来看,确实是一路+10,并且读取也是同步增大,并没有看出来有线程安全性问题。你的多线程启动的方式有点吓人,while(true){} 你想启动多少个线程啊,2秒钟估计能启动几十万上百万的线程了。关于并发编程,可以看下我的一系列文章,可能会有些帮助。
你还没有登录,请先登录或注册慕课网帐号
79461人关注
Copyright (C) 2018 imooc.com All Rights Reserved | 京ICP备 号-11实例解析Java中的synchronized关键字与线程安全问题
&更新时间:日 14:54:28 & 作者:yaerfeng
首先要清楚的是synchronized锁住的不是代码而是对象,因而在编写相关的代码块时要注意线程同步安全问题,下面就来以实例解析Java中的synchronized关键字与线程安全问题
首先来回顾一下synchronized的基本使用:
synchronized代码块,被修饰的代码成为同步语句块,其作用的范围是调用这个代码块的对象,我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。
synchronized方法,被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象。
synchronized静态方法,修饰一个static静态方法,其作用范围是整个静态方法,作用对象是这个类的所有对象。
synchronized类,其作用范围是Synchronized后面括号括起来的部分synchronized(className.class),作用的对象是这个类的所有对象。
synchronized()& ()中是锁住的对象, synchronized(this)锁住的只是对象本身,同一个类的不同对象调用的synchronized方法并不会被锁住,而synchronized(className.class)实现了全局锁的功能,所有这个类的对象调用这个方法都受到锁的影响,此外()中还可以添加一个具体的对象,实现给具体对象加锁。
synchronized (object) {
//在同步代码块中对对象进行操作
synchronized关键字与线程安全
以为用了synchronized关键字包住了代码就可以线程同步安全了。测试了下。发现是完全的错了。synchronized必须正确的使用才是真正的线程安全。。。虽然知道这种写法,一直以为却由于懒而用了错误的方法。
看来基础还没有打好。仍需复习加强!工作中犯这种错误是不可原谅的,要知道使用synchronized关键字的地方都是数据敏感的!汗一把。。。
先贴代码:
public class ThreadTest {
public static void main(String[] args) {
MyThread m1 = new MyThread(1);
MyThread m2 = new MyThread(2);
m1.start();
m2.start();
final class MyThread extends Thread {
public MyThread(int v) {
//这种做法其实是非线程安全的
public synchronized void print1(int v) {
for (int i = 0; i & 100; i++) {
System.out.print(v);
public void print2(int v) {
//线程安全
synchronized (MyThread.class) {
for (int i = 0; i & 100; i++) {
System.out.print(v);
public void run() {
print1(val);
// print2(val);
还是为了偷懒,汗一把。。。程序员总是懒的吧。能少写就少写。我把MyThread写成了一个匿名的最终的内部类,方便调用。它用了最直接的继承Thread来实现一个线程类,定义需要运行的run()方法。
首先注释了print2()方法,看看print1()的结果如何。print1()是一个使用了synchronized关键字定义的方法,我一直以为这样也可以实现线程安全。殊不知,我错了。
我们来直接运行main()方法。控制台打印结果如下:
2121212。。。
是一连串1和2交叉打印的结果。而我main方法中是先运行m1再运行m2的,显示没有做到线程同步!
MyThread m1 = new MyThread(1);
MyThread m2 = new MyThread(2);
m1.start();
m2.start();
接下来我们注释掉run方法中的print1(),运行print2();
控制台打印如下:
线程果然是安全了,一直以为也知道这种写法,但由于这种写法代码稍微多点也就没怎么考虑,今天才意识到这种错误。看来有时候不懒还是有好处的。打好基础很重要。纠正的长期以来的一个错误。
下面我们来看看具体原因。
synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA()
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA()
synchronized (this) // (1)
(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱!
2.同步块,示例代码如下:
public void method3(SomeObject so)
synchronized(so)
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable
private byte[] lock = new byte[0]; // 特殊的instance变量
Public void methodA()
synchronized(lock) { //… }
注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
public synchronized static void methodAAA() // 同步的static 函数
public void methodBBB()
synchronized(Foo.class) // class literal(类名称字面常量)
代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
还有一些技巧可以让我们对共享资源的同步访问更加安全:
1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。
2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。
总结一些synchronized注意事项:
当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。两个线程间是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。(两个线程使用的是同一个对象)
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具部分转载:
线程安全:
Synchronized:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说,一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的。(这句还未考证,但对全局变量和静态变量操作在多线程模型中会引发线程不安全)
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
3. 线程同步
3.1 Synchronized(同步)
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter outputter = new Outputter();
new Thread() {
public void run() {
outputter.output("zhangsan");
}.start();
new Thread() {
public void run() {
outputter.output("lisi");
}.start();
class Outputter {
public void output(String name) {
for(int i = 0; i & name.length(); i++) {
System.out.print(name.charAt(i));
运行结果:zhlainsigsan
显然输出的字符串被打乱了,我们期望的输出结果是zhangsanlisi,这就是线程同步问题,我们希望output方法被一个线程完整的执行完之后再切换到下一个线程,Java中使用synchronized保证一段代码在多线程执行时是互斥的,有两种用法:
方法 1: 使用synchronized将需要互斥的代码包含起来,并上一把锁。
synchronized (this) {
for(int i = 0; i & name.length(); i++) {
System.out.print(name.charAt(i));
这把锁必须是需要互斥的多个线程间的共享对象,像下面的代码是没有意义的。
Object lock = new Object();
synchronized (lock) {
for(int i = 0; i & name.length(); i++) {
System.out.print(name.charAt(i));
方法2:将synchronized加在需要互斥的方法上。
public synchronized void output(String name) {
for(int i = 0; i & name.length(); i++) {
System.out.print(name.charAt(i));
这种方式就相当于用this锁住整个方法内的代码块,如果用synchronized加在静态方法上,就相当于用××××.class锁住整个方法内的代码块。使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码块可以看成是一个 原子操作。
每个锁对象(JLS(java语言规范)中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互斥代码过程如下:
1. 获得同步锁;
2. 清空工作内存;
3. 从主内存拷贝对象副本到工作内存;
4. 执行代码(计算或者输出等);
5. 刷新主内存数据;
6. 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
#### 3.2 Volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。(那应该如何使用呢??)
public class Counter {
public static int count = 0;
public static void inc() {
Thread.sleep(1);
} catch (InterruptedException e) {
public static void main(String[] args) {
for (int i = 0; i & 1000; i++) {
new Thread(new Runnable() {
public void run() {
Counter.inc();
}).start();
System.out.println("运行结果:Counter.count=" + Counter.count);
运行结果:Counter.count=995
实际运算结果每次可能都不一样,本机的结果为:运行结果:Counter.count=995,可以看出,在多线程的环境下,Counter.count并没有期望结果是1000
很多人以为,这个是多线程并发问题,只需要在变量count之前加上volatile就可以避免这个问题,那我们在修改代码看看,看看结果是不是符合我们的期望。
public class Counter {
public volatile static int count = 0;
public static void inc() {
Thread.sleep(1);
} catch (InterruptedException e) {
public static void main(String[] args) {
for (int i = 0; i & 1000; i++) {
new Thread(new Runnable() {
public void run() {
Counter.inc();
}).start();
System.out.println("运行结果:Counter.count=" + Counter.count);
运行结果:Counter.count=992
运行结果还是没有我们期望的1000,下面我们分析一下原因
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是JVM栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这些交互:
read and load 从主存复制变量到当前工作内存
use and assign
执行代码,改变共享变量值
store and write 用工作内存数据刷新主存相关内容
其中use and assign 可以多次出现
但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是当前最新的
例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值
在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6
线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6
导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。(所以volatile用来干啥?:( )
线程同步(5种同步方式)
为何要使用同步?
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该...
线程同步的四种方式
转载地址: http://blog.csdn.net/ebowtang/article/details/一,什么是线程同步和互斥同步就是协同步调,按预定的先后次序进行运行。如:你说完,...
实现线程同步的几种方式
为什么要使用同步
Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前...
线程安全与线程同步
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
线程不安全就是...
Linux——线程同步和线程安全
Linux——线程同步和线程安全
同步:多进程或者多线程访问临界资源时,必须进行同步控制。多进程或者多线程的
执行并不完全是绝对的并行运行,有可能主线程需要等待函数线程的某些条...
多线程安全问题,是由于多个线程在访问共享的数据(共享的资源),并且操作共享数据的语句不止一条。那么这样在多条操作共享数据的之间线程就可能发生切换,从而引发线程安全问题。例如如下情况:publi...
上一篇博客学习了如何简单的使用多线程。其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单。所以很多时候不正确的使用多线程反倒会影响程序的性能。下面先看一个例子 :
class P...
一、线程并发同步概念线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。线程同步,就是当线程发...
高并发下的线程安全实现——互斥同步好久没来csdn上写博客了,去年(16年)来到杭州后,忙得沉淀的时间都没有了,这段时间空闲下来,慢慢补上!
线程允许多个活动同时进行,并发下有很多东西可能出错,比如...
NoHttp核心架构之多线程通信、&em&线程安全&/em&、&em&线程同步&/em&;synchronized锁,Lock锁;具体讲解请移步博客:http://blog.csdn.net/yanzhenjie1003/article/details/
没有更多推荐了,鍗氬?鍒嗙被锛

我要回帖

更多关于 线程不安全是什么意思 的文章

 

随机推荐