JAVA多线程基础学习二:synchronized
本篇主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题,没错就是使用synchronized.
一、如何解决线程安全问题?
一般来说,是如何解决线程安全问题的呢?
基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。
通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。
在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。本文主要讲述synchronized的使用方法,Lock的使用方法后面再说。
二、synchronized关键字
1.简介
synchronized,我们谓之锁,主要用来给方法、代码块加锁。当某个方法或者代码块使用synchronized时,那么在同一时刻至多仅有有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但是,其余线程是可以访问该对象中的非加锁代码块的。
synchronized主要包括两种方法:synchronized 方法、synchronized 块。
2.synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void getResult();
synchronized方法控制对类成员变量的访问。它是如何来避免类成员变量的访问控制呢?我们知道方法使用了synchronized关键字表明该方法已加锁,在任一线程在访问改方法时都必须要判断该方法是否有其他线程在“独占”。每个类实例对应一个把锁,每个synchronized方法都必须调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,被阻塞的线程方能获得该锁。
其实synchronized方法是存在缺陷的,如果我们将一个很大的方法声明为synchronized将会大大影响效率的。如果多个线程在访问一个synchronized方法,那么同一时刻只有一个线程在执行该方法,而其他线程都必须等待,但是如果该方法没有使用synchronized,则所有线程可以在同一时刻执行它,减少了执行的总时间。所以如果我们知道一个方法不会被多个线程执行到或者说不存在资源共享的问题,则不需要使用synchronized关键字。但是如果一定要使用synchronized关键字,那么我们可以synchronized代码块来替换synchronized方法。
3.synchronized 块
synchronized代码块所起到的作用和synchronized方法一样,只不过它使临界区变的尽可能短了,换句话说:它只把需要的共享数据保护起来,其余的长代码块留出此操作。语法如下:
synchronized(object) {
//允许访问控制的代码
}
如果我们需要以这种方式来使用synchronized关键字,那么必须要通过一个对象引用来作为参数,通常这个参数我们常使用为this.、
synchronized (this) {
//允许访问控制的代码
}
4.synchronized的使用
synchronized代码块,被修饰的代码成为同步语句块,其作用的范围是调用这个代码块的对象,我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。
synchronized方法,被修饰的方法成为同步方法,其作用范围是整个方法,作用对象是调用这个方法的对象。
synchronized静态方法,修饰一个static静态方法,其作用范围是整个静态方法,作用对象是这个类的所有对象。
synchronized类,其作用范围是Synchronized后面括号括起来的部分synchronized(className.class),作用的对象是这个类的所有对象。
synchronized(),()中是锁住的对象, synchronized(this)锁住的只是对象本身,同一个类的不同对象调用的synchronized方法并不会被锁住,而synchronized(className.class)实现了全局锁的功能,所有这个类的对象调用这个方法都受到锁的影响,此外()中还可以添加一个具体的对象,实现给具体对象加锁。
5.synchronized注意事项
当两个并发线程访问同一个对象中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。两个线程间是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。(两个线程使用的是同一个对象)
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。
下面通过代码来实现:
1)当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
输出结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
2)然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}
输出结果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
3)尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
} }
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
4)第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
//修改Thread2.m4t2()方法如下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
5)每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。
并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。
代码如下:
public class Test {
public static void main(String[] args) {
final InsertData insertData = new InsertData();
new Thread(){
@Override
public void run() {
insertData.insert();
}
}.start();
new Thread(){
@Override
public void run() {
insertData.insert1();
}
}.start();
}
}
class InsertData {
public synchronized void insert(){
System.out.println("执行insert");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行insert完毕");
}
public synchronized static void insert1() {
System.out.println("执行insert1");
System.out.println("执行insert1完毕");
}
}
输出结果:
执行insert
执行insert1
执行insert1完毕
执行insert完毕
第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。
三、面试题
当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
synchronized关键字的用法?
答:synchronized关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。
简述synchronized 和java.util.concurrent.locks.Lock的异同?
答:Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)
以上就是synchronized的概念和基本使用用法;但如想要更加深入分析synchronized的实现原理,则需要继续攻关,本文仅仅是入门级,希望对你有帮助。
JAVA多线程基础学习二:synchronized的更多相关文章
- Java多线程基础学习(二)
9. 线程安全/共享变量——同步 当多个线程用到同一个变量时,在修改值时存在同时修改的可能性,而此时该变量只能被赋值一次.这就会导致出现“线程安全”问题,这个被多个线程共用的变量称之为“共享变量”. ...
- JAVA多线程基础学习三:volatile关键字
Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...
- JAVA多线程基础学习一:基础知识
我们知道多线程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 一.线程基本概念 1 线程:进程中负责程序执行的执行单元(执行 ...
- Java多线程基础(二)
1.多线程数据安全 线程同步:多个线程需要访问同一资源时,需要以某种顺序来确定该资源某一时刻只能被一个线程使用.从而,解决并发操作可能带来的异常. 2.同步代码块实现同步(部分代码的访问,我们希望它同 ...
- Java多线程基础学习(一)
1. 创建线程 1.1 通过构造函数:public Thread(Runnable target, String name){} 或:public Thread(Runnable target ...
- java多线程基础(二)--sleep(),wait,()yield()和join()方法
1.sleep()方法 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”.不推荐使用. sleep()使当前线程进入阻塞状态,在指定时间内不会执行. 2.wait()方法 在其他线程调用 ...
- java多线程基础(二)--java线程各状态关系
注意只有可运行(就绪态)和运行中(运行态)可以相互转换
- Java多线程技术学习笔记(二)
目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...
- Java 多线程基础(十二)生产者与消费者
Java 多线程基础(十二)生产者与消费者 一.生产者与消费者模型 生产者与消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”.“消费者”.“仓库”和“产品”.他们之间的关系如下: ①.生 ...
随机推荐
- 在html中怎么格式化输出json字符串
#今天的项目用到,看俊哥找到,特此记录下来 步骤: 1.在html页面中输入下面的标签,必须是在pre标签内输出格式才会生效: <pre id="songReqJson"&g ...
- HTTPD解析介绍
配置文件全局介绍 (1)主配置文件:/etc/httpd/conf/httpd.conf 全局配置:Section 1: Global Environment 第33行 中心主机配置: Section ...
- (十二)Jmeter之Bean Shell的使用(一)
一.什么是Bean Shell BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些语法和方法; BeanShell是一种松散类型的脚本语言(这点和JS类似); BeanS ...
- XHTML5 与 HTML 4.01的差异
在 HTML 4.01 中,td 元素的 "bgcolor"."height"."width" 以及 "nowrap" ...
- Impala:新一代开源大数据分析引擎--转载
原文地址:http://www.parallellabs.com/2013/08/25/impala-big-data-analytics/ 文 / 耿益锋 陈冠诚 大数据处理是云计算中非常重要的问题 ...
- 802.1p 优先级与内部优先级的映射关系
缺省情况下,所有华为 S 系列交换机的 802.1P 优先级 与内部优先级的映射关系是 一 样的,如表 10-3 所示.从中可以看出,这些交换机中 802.1p 优先级与内部优先级的缺省映射关系是按等 ...
- 洛谷 Roy&October之取石子
题目背景 Roy和October两人在玩一个取石子的游戏. 题目描述 游戏规则是这样的:共有n个石子,两人每次都只能取pk 个(p为质数,k为自然数,且pk小于等于当前剩余石子数),谁取走最后一个石子 ...
- Unity3D手游开发日记(7) - 适合移动平台的天气效果
腾讯的天涯明月刀的天气很棒,以前我也在CE3的基础上做了一个效果差不多的,但是在手机上,还是斜下固定视角的游戏,是否还需要一个天气系统? 而且没有G-Buffer的支持,很多牛逼效果实现不了,比如角色 ...
- C++11——引入的新关键字
1.auto auto是旧关键字,在C++11之前,auto用来声明自动变量,表明变量存储在栈,很少使用.在C++11中被赋予了新的含义和作用,用于类型推断. auto关键字主要有两种用途:一是在变量 ...
- Mobile Service
link 试题分析 我们发现$dp(t,s1,s2,s3)$表示在$t$时刻$3$个人的位置.发现时间复杂度为$O(n \times L^3)$.不仅会$T$还会$MLE$,所以需要优化$dp$.我们 ...