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

我们可以通过使用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. Git本地提交到远程指定分支

    git push origin master:ziranmeng2.(本地分支:远程分支)

  2. HTTP 2.0的那些事

    转自:http://www.admin10000.com/document/9310.html 在我们所处的互联网世界中,HTTP协议算得上是使用最广泛的网络协议.最近http2.0的诞生使得它再次互 ...

  3. golang在linux下的开发环境部署[未完]

    uname -a Linux symons_laptop 4.8.2-1-ARCH #1 SMP PREEMPT Mon Oct 17 08:11:46 CEST 2016 x86_64 GNU/Li ...

  4. ueditor

    1:添加插件包 2:添加文件上传的jar包 3:页面引入ueditor插件 <!-- ueditor --><link type="text/css" href= ...

  5. CSS继承

    不可继承的:display.margin.border.padding.background.height.min-height.max-height.width.min-width.max-widt ...

  6. AspNetPager分页控件

    AspNetPager分页控件解决了分页中的很多问题,直接采用该控件进行分页处理,会将繁琐的分页工作变得简单化,下面是我如何使用AspNetPager控件进行分页处理的详细代码:1.首先到www.we ...

  7. springmvc 中controller与jsp传值

    参考:springmvc 中controller与jsp传值 springMVC:将controller中数据传递到jsp页面 jsp中,死活拿不到controller中的变量. 花了半天,网上列出各 ...

  8. Memcached安装配置及访问

    1.Memcached键值对访问,对于网页来说,key需要使用uri. 2.Memcached的相关配置 memcached:缓存服务器,但本身无法决定缓存任何数据 一半依赖于客户端,一半依赖于服务端 ...

  9. 《CoffeeScript应用开发》学习:第三章-构建简单的应用程序

    字符串插值 CoffeeScript提供了一种更好的构建字符串的解决方案.在双引号字符串(单引号无效)中使用#{}包含一个动态的值. str = 'Hello, CoffeeScript.' cons ...

  10. RichEdit 追加 RTF

    下面实现追加RTF 到 RichEdit 的功能其本质是:EM_STREAMIN 消息,详细查看 MSDN//--------------------------------------------- ...