廖雪峰Java11多线程编程-2线程同步-1同步代码块
1.线程安全问题
多个线程同时运行,线程调度由操作系统决定,程序本身无法决定。
如果多个线程同时读写共享变量,就可能出现问题。
假设有AddThread和DecThread,它们分别对同一个共享变量做加和减运算LOOP次,最终结果应该是0。但某些时候比如LOOP为10000时,结果是错误的。
class AddThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
Main.count += 1;
}
}
}
class DecThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
Main.count -= 1;
}
}
}
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
//等待这两个线程执行结束
t1.join();
t2.join();
System.out.println(count);
}
}
## 2.原子操作
* 因此对共享变量进行写入时,必须保证是原子操作
* 原子操作是指不能被中断的一个或一系列操作
当执行 n = n +1时,编译器会把它编译为3条字节码指令,分别是ILOAD, IADD, ISTORE。所以对于这个简单的赋值语句,它并不是一个原子操作,这就可能导致两个线程在执行这条语句的时候,会出现问题。
假设1:n=100,Thread1执行语句n为101,Thread2再执行n为102。
假设2:Thread1刚执行完ILOAD指令,就被操作系统暂停了,然后Thread2调度执行,结果n变成了101,此后Thread1再度被操作系统调度执行,结果也是101。即n+1的指令被2个线程调用了2次,最终只加了1.
所以我们要保证当Thread1执行时,Thread2不能执行,直到Thread1执行完毕,Thread2才能开始执行。这样运行的结果就是正确的。
要实现这个效果,就要对ILOAD之前和ISTORE之后进行加锁和解锁。
3.同步代码块
Java使用synchronized对一个对象进行加锁:
- 为了保证一系列操作作为原子操作,必须保证一系列操作过程中不被其他线程执行
synchronized (lock){
n=n+1;
}
当一个线程想要执行synchronized语句块时,必须首先获得指定对象的锁,这个对象就是synchronized括号里的对象,然后线程再执行synchronized语句块,执行结束以后释放锁。
在执行synchronized语句块时,如果Thread1执行到任何语句时,被操作系统中断。其他线程如Thread2因为无法获取lock对象的锁,从而导致Thread2无法进入synchronized语句块,Thread2就必须等待,直到Thread1再次被调用,并执行完synchronized语句块释放了锁,Thread2才能获得lock对象锁,进入synchronized语句块。
synchronized保证了代码块和任意时刻最多只有一个线程能执行。
- 因为一个对象的锁只能被一个线程获得,其他线程必须等待。
synchronized的问题:
- 性能下降。因为synchronized代码块无法并发执行,所以性能会下降。此外加锁和解锁都会消耗一定的时间,所以synchronized会降低程序的执行效率。
如何使用synchronized:
- 1.找出修改共享变量的线程代码块
- 2.选择一个实例作为锁
- 3.使用synchronized(lock Object){...}
注意:
- 对于同一个变量的修改,必须要获取同一个锁,如果2个线程获取的是不同的锁,它们是没有办法进行同步的。
- 不用担心异常。无论有无异常,在synchronized结束时都会释放锁。
class AddThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
synchronized (Main.LOCK) {
Main.count += 1;
}
}
}
}
class DecThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
synchronized (Main.LOCK) {//对于同一个变量的修改,要使用同一个锁
Main.count -= 1;
}//无论有无异常,都会在此释放锁
}
}
}
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
4.JVM的原子操作
JVM定义了几种原子操作:
- 基本类型(long和double除外)赋值
- 引用类型赋值
注意:
- 原子操作时不需要同步的。
- 可以把非原子操作变为原子操作
- 局部变量不需要同步
//原子操作不需要同步
public void set(int m){
synchronized (obj){
this.value = m;
}
}
//->
public void set(int m){
this.value = m;
}
//对2个int类型进行赋值,它不是一个原子操作。但可以先构造一个int数组,然后利用引用类型赋值,把它变成1个原子操作。
class Pair{
int first;
int last;
public void set(int first,int last){
synchronized (this){
this.first = first;
this.last = last;
}
}
}
//->
class Pair{
int[] pair;
public void set(int first,int last){
int[] ps = new int[]{first,last};
this.pair = ps;
}
}
//a,b,s1,s2,r都是局部变量,各个线程的局部变量是完全独立的,互不影响,所以这个方法不需要同步。
public int avg(int a, int b){
int s1 = a*a + b*b;
int s2 = a + b;
int r = s1/s2;
return r;
}
5.总结:
- 多线程同时修改变量,会造成逻辑错误
* 需要通过synchronized同步
* 同步的本质就是给指定对象加锁
* 注意加锁对象必须是同一个实例 - 对JVM定义的单个原子操作不需要同步
廖雪峰Java11多线程编程-2线程同步-1同步代码块的更多相关文章
- 廖雪峰Java11多线程编程-2线程同步-3死锁
1.线程锁可以嵌套 在多线程编程中,要执行synchronized块: 必须首先获得指定对象的锁 Java的线程锁是可重入的锁.对同一个对象,同一个线程,可以多次获取他的锁,即同一把锁可以嵌套.如以下 ...
- 廖雪峰Java11多线程编程-2线程同步-2synchronized方法
1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ cou ...
- 廖雪峰Java11多线程编程-2线程同步-4wait和notify
wait和notify synchronized解决了多线程竞争的问题 我们可以在synchronized块中安全的对一个变量进行修改,但是它没有解决多线程协调的问题. 例如设计一个TaskQueue ...
- 廖雪峰Java11多线程编程-1线程的概念-1多线程简介
多任务 现代操作系统(windows,MacOS,Linux)都可以执行多任务: 多任务就是同时运行多个任务,例如同时开启钉钉.百度网盘.火狐.谷歌.ps等 操作系统执行多任务就是让多个任务交替执行, ...
- 廖雪峰Java11多线程编程-4线程工具类-1ThreadLocal
多线程是Java实现多任务的基础: Thread ExecutorService ScheduledThreadPool Fork/Join Thread对象代表一个线程:调用Tread.curren ...
- 廖雪峰Java11多线程编程-1线程的概念-2创建新线程
Java语言内置多线程支持: 一个Java程序实际上是一个JVM进程 JVM用一个主线程来执行main()方法 在main()方法中又可以启动多个线程 1.创建新线程 1.1 方法一:使用Thread ...
- 廖雪峰Java11多线程编程-1线程的概念-5中断线程
1.中断线程: 如果线程需要执行一个长时间任务,就可能需要中断线程.场景:从网络上下载一个100M的文件,用户在下载过程中中断下载任务的执行. 中断线程就是其他线程给该线程发一个信号,该线程收到信号后 ...
- 廖雪峰Java11多线程编程-1线程的概念-3线程的状态
1线程的状态 线程终止的的原因: run()或call()方法执行完成,线程正常结束 线程抛出一个未捕获的Exception或Error 直接调用该线程的stop()方法来结束该线程--该方法容易导致 ...
- 廖雪峰Java11多线程编程-3高级concurrent包-2ReadWriteLock
ReentrantLock保证单一线程执行 ReentrantLock保证了只有一个线程可以执行临界区代码: 临界区代码:任何时候只有1个线程可以执行的代码块. 临界区指的是一个访问共用资源(例如:共 ...
随机推荐
- div中内容可左右上下滑动
在<table>外套一层<div>,并且声明overflow:scroll属性,如: <div style="width:1620px;height:680px ...
- CSS W3C统一验证工具
CssStats 是一个在线的 CSS 代码分析工具 网址是: http://www.cssstats.com/ 如果你想要更全面的,这个神奇,你值得拥有: W3C 统一验证工具: http://v ...
- 弹性网卡支持私网多IP
摘要: 弹性网卡支持多IP功能可以最多在一块弹性网卡配置20个私网IP地址,特别适用于于以下场景. 1.单个服务器上托管多个应用,提升实例利用率,每个应用对外暴露一个独立的服务IP地址. 2.当实例发 ...
- thinkphp 默认值输出
我们可以给变量输出提供默认值,例如: 大理石平台厂家 {$user.nickname|default="这家伙很懒,什么也没留下"} 对系统变量依然可以支持默认值输出,例如: {$ ...
- 8 包含min函数的栈
0 引言 题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数.在该栈中,调用min.push及pop的时间复杂度都是O(). 1 抽象问题具体化 2 具体问题抽象分析 需要解 ...
- .net Framework 中的四种计时器
在Framework中存在着4种定时器:其中分为两类, 多线程计时器 1:System.Threading.Timer 2:System.Timers.Timer 特殊目的的单线程计时器: 1:Sys ...
- Docker系列(六):Docker网络机制(下)
Linux Namespace详解 namespace:是一个空间,空间里可以放进程,文件系统,账号,网络等,某个资源被放到namespace之后 别人就看不到他了. 可以看到有两个namespace ...
- 纯CSS3实现图片展示特效
本文中要实现的一个纯CSS3的图片展示特效,以前只能用JavaScript实现,可想而知会受到多方面的限制,特别是性能.而今天我们将用简单的CSS3代码实现,你会发现它的动画效果在现代浏览器的帮助下无 ...
- <Python基础>python是如何进行内存管理的
.Python 是如何进行内存管理的?答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制⒈对象的引用计数机制Python 内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用 ...
- MySQL5.1的安装过程
开始配置文件