-
多线程分类的最新文章
-
最新评论
-
wx5951c76fc3280:已经重新上传了 之前传完我还看了呢 没问题 这就又挂了
-
咖啡:加油~
-
wx5951c76fc3280:好的,谢谢支持
-
最新文章
-
目录
-
在并发场景中,保证同一时刻只有一个线程对有并发隐患的代码进行操作
需求:两个线程对 count 变量进行200000次循环增加,预期结果是400000次
public class SynchronizedDemo implements Runnable {
private static int count = 0;
static SynchronizedDemo synchronizedInstance = new SynchronizedDemo();
public static void main(String[] args) {
Thread t1 = new Thread(synchronizedInstance);
Thread t2 = new Thread(synchronizedInstance);
t1.start();
t2.start();
try {
t1.join();
t2.join();
System.out.println("count 最终的值为: " + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
// synchronized (this) {
for (int i = 0; i < 200000; i++) {
count++;
}
//}
}
}
结果 :显然不等于400000次所以出现了运算错误
原因:
count++;
该语句包含三个操作:
注意:他们是将自己工作内存中的值进行改变刷回主内存,假设当前count的值为8,t1、t2将count的值复制到自己的工作内存中进行修改,如果此时t1将count变成9、t2此时也将count的值变成9,当t1、t2两个线程都将值刷回主内存的时候count值为9,并不是10,这个时候就会造成最后的结果和预期的不一致。
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 200000; i++) {
count++;
}
}
}
@Override
public synchronized void run() {
for (int i = 0; i < 200000; i++) {
count++;
}
}
@Override
public void run() {
for (int i = 0; i < 200000; i++) {
synchronized (SynchronizedDemo.class) {
count++;
}
}
}
输出结果:
后文详细讲解四种加 synchronized 的方式
2.1.1 方法锁
修饰普通方法默认锁对象为this当前实例对象
public synchronized void method() ;在普通方法上面加synchronized
public class SynchronizedDemo3 implements Runnable {
static SynchronizedDemo3 synchronizedDemo3 = new SynchronizedDemo3();
public synchronized void method() {
System.out.println("线程名称" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread t1 = new Thread(synchronizedDemo3);
t1.setName("我是线程 t1");
Thread t2 = new Thread(synchronizedDemo3);
t2.setName("我是线程 t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果: 线程 t1 和线程 t2 执行过程是顺序执行的
2.1.2 同步代码块
输出结果:线程 t1 和线程 t2 交叉执行形成了乱序
输出结果:线程 t1 和线程 t2 执行过程是顺序执行的
输出结果:线程 t1 和线程 t2 执行形成了顺序,这种情况下和this没有什么区别,但是如果是多个同步代码块的话就需要进行自定义对象锁了
输出结果:输出顺序线程t1 和线程t2 代码进行了交叉执行,出现了乱序
输出结果:线程 t1 和线程 t2 执行形成了顺序
特点:类锁只能在同一时间被一个对象拥有(无论有多少个实例想访问也是一个对象持有它)
2.2.1 synchronized修饰静态的方法
代码示例: synchronized 加在普通方法上面
public class SynchronizedDemo4 implements Runnable {
private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4();
private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4();
public synchronized void method() {
System.out.println("线程名称" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread t1 = new Thread(synchronizedInstance1);
t1.setName("我是线程 t1");
Thread t2 = new Thread(synchronizedInstance2);
t2.setName("我是线程 t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:输出顺序线程t1 和线程t2 代码进行了交叉执行,出现了乱序
public static synchronized void method();使用方式
public class SynchronizedDemo4 implements Runnable {
private static SynchronizedDemo4 synchronizedInstance1 = new SynchronizedDemo4();
private static SynchronizedDemo4 synchronizedInstance2 = new SynchronizedDemo4();
public static synchronized void method() {
System.out.println("线程名称" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread t1 = new Thread(synchronizedInstance1);
t1.setName("我是线程 t1");
Thread t2 = new Thread(synchronizedInstance2);
t2.setName("我是线程 t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:线程 t1 和线程 t2 执行形成了顺序
2.2.2 指定锁对象为Class对象
*synchronized (**SynchronizedDemo5.class**)*
public class SynchronizedDemo5 implements Runnable {
private static SynchronizedDemo5 synchronizedInstance1 = new SynchronizedDemo5();
private static SynchronizedDemo5 synchronizedInstance2 = new SynchronizedDemo5();
void method() {
synchronized (SynchronizedDemo5.class) { //类锁只有一把
System.out.println("线程名称" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名称" + Thread.currentThread().getName() + "运行完成");
}
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread t1 = new Thread(synchronizedInstance1);
t1.setName("我是线程 t1");
Thread t2 = new Thread(synchronizedInstance2);
t2.setName("我是线程 t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果: 线程 t1 和线程 t2 执行形成了顺序
就是说你已经获取了一把锁,等想要再次请求的时候不需要释放这把锁和其他线程一起竞争该锁,可以直接使用该锁
好处:避免死锁
粒度:线程而非调用
代码实例:
package synchronizedPage;
public class SynchronizedDemo6 {
int count = 0;
public static void main(String[] args) {
SynchronizedDemo6 synchronizedDemo6 = new SynchronizedDemo6();
synchronizedDemo6.method();
}
private synchronized void method() {
System.out.println(count);
if (count == 0) {
count++;
method();
}
}
}
输出结果:
代码实例:
package synchronizedPage;
public class SynchronizedDemo7 {
private synchronized void method1() {
System.out.println("method1");
method2();
}
private synchronized void method2() {
System.out.println("method2");
}
public static void main(String[] args) {
SynchronizedDemo7 synchronizedDemo7 = new SynchronizedDemo7();
synchronizedDemo7.method1();
}
}
输出结果:
代码实例:
package synchronizedPage;
public class SynchronizedDemo8 {
public synchronized void doSomething() {
System.out.println("我是父类方法");
}
}
class childrenClass extends SynchronizedDemo8{
public synchronized void doSomething() {
System.out.println("我是子类方法");
super.doSomething();
}
public static void main(String[] args) {
childrenClass childrenClass = new childrenClass();
childrenClass.doSomething();
}
}
输出结果:
当A线程持有这把锁时,B线程如果也想要A线程持有的锁时只能等待,A永远不释放的话,那么B线程永远的等待下去。
public void test() {
synchronized(this){
count++;
}
}
利用 javap -verbose 类的名字查看编译后的文件
monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权
monitorexit指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁
public synchronized void test() {
count++;
}
利用 javap -verbose 类的名字查看编译后的文件
方法的同步并没有通过指令monitorenter和monitorexit来完成,不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象,其实底层还是monitor对象锁。
从JDK6开始,就对synchronized的实现机制进行了较大调整,包括使用JDK5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁这些优化策略。所以synchronized关键字的优化使得性能极大提高,同时语义清晰、操作简单、无需手动关闭,所以推荐在允许的情况下尽量使用此关键字,同时在性能上此关键字还有优化的空间。
无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
锁的膨胀过程:
无锁状态 -> 偏向锁 -> 轻量级锁 -> 重量级锁
只能从低到高升级,不会出现锁的降级
所谓自旋锁,就是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。(减少线程切换)
使用场景: 自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。
缺点:虽然它可以避免线程切换带来的开销,但是它占用了CPU处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,所以增加了适应性自选锁
所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
线程如果自旋成功了,那么下次自旋的次数会更加多,因为上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,很少能够成功,那么以后自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。
为了保证数据的完整性,在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。作为写程序的人应该会知道哪里存在数据竞争,不可能随便的加锁。
将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。虽然我们平时倡导把加锁的片段尽量小为了增加并发效率和性能。但是如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗,所以引入锁粗化。
在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下(即线程A尚未执行完同步代码块,线程B发起了申请锁的申请),则一定会转化为轻量级锁或者重量级锁。
引入偏向锁主要目的是:为了在没有多线程竞争的情况下尽量减少不必要的轻量级锁执行路径。因为轻量级锁的加锁解锁操作是需要依赖多次CAS原子指令的,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要花费CAS操作来争夺锁资源,只需要检查是否为偏向锁、锁标识为以及ThreadID即可,处理流程如下:
偏向锁的获取和撤销流程:
引入轻量级锁的主要目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤如下:
轻量级锁的释放也是通过CAS操作来进行的,主要步骤如下:
问题:
因为在申请对象锁时需要以该值作为CAS的比较条件,同时在升级到重量级锁时,能通过这个比较判定是否在持有锁的过程中此锁被其他线程申请过,如果被其他线程申请了,则在释放锁的时候要唤醒被挂起的线程。
Synchronized是通过对象内部的一个叫做监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,性能消耗特别严重。 因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。
赞赏
0人进行了赞赏支持
1
收藏
Ctrl+Enter 发布
发布
取消