前言:前一篇文章主要描述了多线程中访成员变量与局部变量问题,我们知道访成员变量有线程安全问题,在多线程程序中

我们可以通过使用synchronized关键字完成线程的同步,能够解决部分线程安全问题

在java中synchronized同步关键字可以使用在静态方法和实例方法中使用,两者的区别在于:

对象锁与类锁
对象锁
当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。

如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放

类锁
由上述同步静态方法引申出一个概念,那就是类锁。其实系统中并不存在什么类锁。当一个同步静态方法被调用时,系统获取的其实就是代表该类的类对象的对象锁
在程序中获取类锁
可以尝试用以下方式获取类锁
synchronized (xxx.class) {...}
synchronized (Class.forName("xxx")) {...}
同时获取2类锁
同时获取类锁和对象锁是允许的,并不会产生任何问题,但使用类锁时一定要注意,一旦产生类锁的嵌套获取的话,就会产生死锁,因为每个class在内存中都只能生成一个Class实例对象。

同步静态方法/静态变量互斥体
由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只由一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。一旦一个静态变量被作为synchronized block的mutex。进入此同步区域时,都要先获得此静态变量的对象锁

代码

/**
* 同步代码块与同步实例方法的互斥
*
* @author cary
*/
public class TestSynchronized {
/**
* 同步代码块
*/
public void testBlock() {
synchronized (this) {
int i = ;
while (i-- > ) {
System.out
.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
}
} /**
* 非同步普通方法
*/
public void testNormal() {
int i = ;
while (i-- > ) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 同步实例方法
*/
public synchronized void testMethod() {
int i = ;
while (i-- > ) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 主方法分别调用三个方法
*
* @param args
*/
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread test1 = new Thread(new Runnable() {
public void run() {
test.testBlock();
}
}, "testBlock");
Thread test2 = new Thread(new Runnable() {
public void run() {
test.testMethod();
}
}, "testMethod");
test1.start();
;
test2.start();
test.testNormal();
}
}

执行结果

testBlock : 4
main : 4
testBlock : 3
main : 3
testBlock : 2
main : 2
testBlock : 1
main : 1
testBlock : 0
main : 0
testMethod : 4
testMethod : 3
testMethod : 2
testMethod : 1
testMethod : 0

上述的代码,第一个方法时用了同步代码块的方式进行同步,传入的对象实例是this,表明是当前对象,

当然,如果需要同步其他对象实例,也不可传入其他对象的实例;第二个方法是修饰方法的方式进行同步。

因为第一个同步代码块传入的this,所以两个同步代码所需要获得的对象锁都是同一个对象锁,

下面main方法时分别开启两个线程,分别调用testBlock()和testMethod()方法,那么两个线程都需要获得该对象锁,

另一个线程必须等待。上面也给出了运行的结果可以看到:直到testBlock()线程执行完毕,释放掉锁testMethod线程才开始执行

(两个线程没有穿插执行,证明是互斥的)

对于普通方法

结果输出是交替着进行输出的,这是因为,某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。

进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,

其他线程还是可以访问那些没有同步的方法(普通方法)

结论:synchronized只是一个内置锁的加锁机制,当某个方法加上synchronized关键字后,就表明要获得该内置锁才能执行,

并不能阻止其他线程访问不需要获得该内置锁的方法

类锁的修饰(静态)方法和代码块:

/**
*
* 类锁与静态方法锁
*
* @author cary
*/
public class TestSynchronized2 {
/**
* 类锁
*/
public void testClassLock() {
synchronized (TestSynchronized2.class) {
int i = ;
while (i-- > ) {
System.out
.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
}
} public static synchronized void testStaticLock() {
int i = ;
while (i-- > ) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 普通方法
*/
public void testNormal() {
int i = ;
while (i-- > ) {
System.out.println("normal-" + Thread.currentThread().getName()
+ " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 静态方法
*/
public void testStaticNormal() {
int i = ;
while (i-- > ) {
System.out.println("static-" + Thread.currentThread().getName()
+ " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 测试synchronized锁的互斥效果
*
* @param args
*/
public static void main(String[] args) {
final TestSynchronized2 test = new TestSynchronized2();
Thread testClass = new Thread(new Runnable() {
public void run() {
test.testClassLock();
}
}, "testClassLock");
Thread testStatic = new Thread(new Runnable() {
public void run() {
TestSynchronized2.testStaticLock();
}
}, "testStaticLock");
/**
* 线程1
*/
testClass.start();
/**
* 线程2
*/
testStatic.start();
/**
* 成员方法
*/
test.testNormal();
/**
* 静态方法
*/
TestSynchronized2.testStaticLock(); }
}

执行结果

testClassLock :
normal-main :
normal-main :
testClassLock :
normal-main :
testClassLock :
testClassLock :
normal-main :
testClassLock :
normal-main :
testStaticLock :
testStaticLock :
testStaticLock :
testStaticLock :
testStaticLock :
main :
main :
main :
main :
main :

类锁和静态方法锁线程是分先后执行的,没有相互交叉,类锁和静态方法锁是互斥的

其实,类锁修饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象出来的概念,

只是为了区别静态方法的特点,因为静态方法是所有对象实例共用的,

所以对应着synchronized修饰的静态方法的锁也是唯一的,所以抽象出来个类锁。

结论:类锁和静态方法锁是互斥的

 锁静态方法和普通方法

/**
* 锁普通方法和静态方法。
*
* @author cary
*/
public class TestSynchronized3 {
/**
* 锁普通方法
*/
public synchronized void testNormal() {
int i = ;
while (i-- > ) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 锁静态方法
*/
public static synchronized void testStatic() {
int i = ;
while (i-- > ) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep();
} catch (InterruptedException ie) {
}
}
} /**
* 普通方法和静态方法
*
* @param args
*/
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread test1 = new Thread(new Runnable() {
public void run() {
test.testNormal();
}
}, "testNormal");
Thread test2 = new Thread(new Runnable() {
public void run() {
TestSynchronized3.testStatic();
}
}, "testStatic");
/**
* 启动普通方法线程
*/
test1.start();
/**
* 启动静态方法线程
*/
test2.start(); }
}

执行结果

testNormal :
testStatic :
testNormal :
testStatic :
testNormal :
testStatic :
testStatic :
testNormal :
testNormal :
testStatic :

上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,

这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。

同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。

到这里,对synchronized的用法已经有了一定的了解。这时有一个疑问,既然有了synchronized修饰方法的同步方式,

为什么还需要synchronized修饰同步代码块的方式呢?而这个问题也是synchronized的缺陷所在

synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,

必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,

那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

java多线程详解(3)-线程的互斥与同步的更多相关文章

  1. JAVA多线程提高二:传统线程的互斥与同步&传统线程通信机制

    本文主要是回顾线程之间互斥和同步,以及线程之间通信,在最开始没有juc并发包情况下,如何实现的,也就是我们传统的方式如何来实现的,回顾知识是为了后面的提高作准备. 一.线程的互斥 为什么会有线程的互斥 ...

  2. java多线程详解(6)-线程间的通信wait及notify方法

    Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...

  3. java多线程详解(7)-线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, 这样频繁创建线程就会大大降低系 ...

  4. Java 多线程详解(四)------生产者和消费者

    Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...

  5. Java 多线程详解(五)------线程的声明周期

    Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...

  6. Java多线程详解(二)

    评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡 ...

  7. Java多线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  8. 原创Java多线程详解(一)

    只看书不实践是不行的.来实践一下~~~~~~(引用请指明来源) 先看看百科对多线程的介绍 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的 ...

  9. java多线程详解(1)-多线程入门

    一.多线程的概念 线程概念 线程就是程序中单独顺序的流控制. 线程本身不能运行,它只能用于程序中. 说明:线程是程序内的顺序控制流,只能使用分配给程序的资源和环境. 进程:操作系统中执行的程序 程序是 ...

随机推荐

  1. javascript 中slice,substr,substring方法的对比

    实际开发过程中,字符串是一种最常用的一种数据类型,而在程序中对字符串的操作也是十分繁琐,javascript中就提供了很多字符串相关的方法,然而由于方法过多,很多方法功能有交叉,参数有雷同,容易使用混 ...

  2. [转]:C#的ToString如何格式化字符串

    C 货币 2.5.ToString("C") ¥2.50 D 十进制数 25.ToString("D5") 00025 E 科学型 25000.ToString ...

  3. Android Activity 四种启动模式

    task和back stack(任务和回退栈) 任务启动,task被加入到回退栈的栈顶,返回的时候回退栈的栈顶任务会被弹出,并被销毁,栈中的前一任务恢复运行,当activity销毁是,系统不会保留ac ...

  4. 小知识:Python函数传递变长

    先来实践一把: def f1(*args): print(args) f1(1,2,3,4) 得出:(1, 2, 3, 4) ----- 是一个元祖 Part 1: *args可以传递任意多的数,ar ...

  5. webpack配置备份

    package.json: { "name": "webpackTest", "version": "1.0.0", & ...

  6. HBase预分区

    seq 0 7 | awk '{printf("\\x%02x\\x%02x\n", $1/256, $1%256);}' | sort -R |head -3 create 'm ...

  7. 如何理解JS回调函数

    1.回调函数英文解释: A callback is a function that is passed as an argument to another function and is execut ...

  8. 第一个PHP程序-HelloWorld

    <?php //echo输出字符串 echo "Hello php!你好 php" ; 以上程序输出结果为:Hello php!你好 php

  9. Android 工程师如何快速学会web前段

    Android 工程师如何快速学会web前段 今天主要聊一下本人最近在学习web前段的感受,最近html5是越来越火了,前段时间公司做了一个项目然后让我们“android”的程序猿过去帮忙把客户 端框 ...

  10. angularJs基础

    AngularJs是为了克服Html在构建应用上的不足而设计的.Html是一门很好的为静态文件展示设计的声明式语言,但是要构建web应用的话就显得乏力了.所以我做了一些工作来让浏览器做我瞎向要的事. ...