常用API

method 注释
run() run()方法是我们创建线程时必须要实现的方法,但是实际上该方法只是一个普通方法,直接调用并没有开启线程的作用。
start() start()方法作用为使该线程开始执行;Java虚拟机调用该线程的 run 方法。 但是该方法只能调用一次,如果线程已经启动将会抛出IllegalThreadStateException异常。
yield() yield()方法让出CPU并且不会释放锁,让当前线程变为可运行状态,所以CPU下一次选择的线程仍可能是当前线程。
wait() wait()方法使得当前线程挂起,放弃CPU的同时也放弃同步资源(释放锁),让其他等待这些资源的线程能继续执行,只有当使用notify()/notifyAll()方法是才会使得等待的线程被唤醒,使用此方法的前提是已经获得锁。
notify()/notifyAll() notify()/notifyAll()方法将唤醒当前锁上的一个(全部)线程,需要注意的事一般都是使用的notifyAll()方法,因为notify()方法的唤醒是随机的,我们没有办法控制。

同步

上面已经介绍了比较常用的api,现在我们可以了解一下在多线程中占据着重要地位的锁了。

为什么会出现线程不安全

在上一篇文章中有提到在现在操作系统中进程是作为资源分配的基本单位,而线程是作为调度的基本单位,一般而言,线程自己不拥有系统资源,但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源,如已打开的文件、I/O设备等,可以供该进程中的所有线程所共享,一旦有多个线程在操作同样的资源就可能造成线程安全的问题。

在我们熟悉的Java中存在着局部变量和类变量,其中局部变量是存放在栈帧中的,随着方法调用而产生,方法结束就被释放掉,而栈帧是独属于当前线程的,所以不会有线程安全的问题。而类变量是被存放在堆内存中,可以被所有线程共享,所以也会存在线程安全的问题。

synchronized

在Java中我们见得最多的同步的方法应该就是使用synchronized关键字了。实际上synchronized就是一个互斥锁,当一个线程运行到使用了synchronized的代码段时,首先检查当前资源是否已经被其他线程所占用,如果已经被占用,那么该线程则阻塞在这里,直到拥有资源的线程释放锁,其他线程才可以继续申请资源。

实现简单理解

public static void test(){
synchronized (SyncDemo.class){
}
} //编译后的代码
public static void test();
Code:
0: ldc #3 //将一个常量加载到栈中这里既是class com/learn/set/mutilthread/sync/SyncDemo
2: dup //复制栈顶元素(SyncDemo.class)
3: astore_0 //将栈顶元素存储到局部变量表
4: monitorenter //以字节码对象(SyncDemo.class)为锁开始同步操作
5: aload_0 //将局部变量表slot_0入栈(SyncDemo.class)
6: monitorexit //退出同步
7: goto 15 //到这里程序跳转到return语句正常结束,下面代码是异常路径
10: astore_1
11: aload_0
12: monitorexit
13: aload_1
14: athrow
15: return

到这里就差不多了,详细的原理后面再谈,这里主要是谈谈synchronized的使用。

synchronized的使用

在Java语言中,synchronized关键字可以用来修饰方法以及代码块:

修饰方法
    //修饰普通方法
public synchronized void say(){ }
//修饰静态方法
public synchronized static void fun(){ }
修饰代码块
    public void fun1(){
//使用当前对象为锁
synchronized (this){
//statement
}
} public void fun2(){
//使用当前类字节码对象为锁
synchronized (SyncDemo.class){
//statement
}
}

synchronized在不同场景下的区别

实体类:

public class User {
private static int age = 20; public synchronized void say(String user) throws InterruptedException {
// synchronized (User.class){
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//当前线程休眠,判断别的线程是否还能调用
Thread.sleep(1000);
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
// }
} public synchronized void say1(String user) throws InterruptedException {
// synchronized (User.class){
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
Thread.sleep(1000);
age = 15;
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
// }
}
}

测试类:

public class SyncTest{
private static User user1 = new User();
private static User user2 = new User(); private static class Sync1 extends Thread{
@Override
public void run() {
try {
user1.say("user1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} private static class Sync2 extends Thread{
@Override
public void run() {
try {
user2.say("user2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
Sync1 sync1 = new Sync1();
Sync2 sync2 = new Sync2();
sync1.start();
sync2.start();
}
}
运行结果:
第一次运行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
第二次运行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-1:user2
20:Thread-0:user1

运行结果表示在普通方法上加synchronized关键字实际上是锁的当前对象,所以不同线程操作不同对象结果可能出现不一致。修改实体类User的say(...)方法为静态方法:

public synchronized void say(String user) throws InterruptedException {
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
Thread.sleep(1000);
System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
}

运行结果始终按照顺序来:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

说明在静态(类)方法上加synchronized关键字实际上是锁的当前类的字节码对象,因为在JVM中任何类的字节码对象都只有一个,所以只要对该字节码对象加锁那么任何对该类的操作也都是同步的。

在最初类的基础上修改类Sync2,使得两个线程操作统一对象:

private static class Sync2 extends Thread{
@Override
public void run() {
try {
user1.say("user2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

运行结果始终按照顺序来:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

同理可测试在使用synchronized修饰代码块的作用,可得结果使用this对象实际是锁当前对象,与synchronized修饰普通方法类似,使用User.class字节码对象实际是锁User类的字节码对象,与synchronized修饰静态方法类似。需要说明的事锁代码块实际上并不是必须使用当前类的this对象和字节码对象,而可以是任意的对象。而实际效果和使用当前类的对象一致。

Java多线程之二(Synchronized)的更多相关文章

  1. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  2. Java多线程6:Synchronized锁代码块(this和任意对象)

    一.Synchronized(this)锁代码块 用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待.这种情况下就 ...

  3. Java多线程5:Synchronized锁机制

    一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...

  4. 简述Java多线程(二)

    Java多线程(二) 线程优先级 Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行. 优先级高的不一定先执行,大多数情况是这样的. 优 ...

  5. Java多线程(二)关于多线程的CPU密集型和IO密集型这件事

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  6. Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

    一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...

  7. java多线程系列(二)

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  8. java多线程系列(二)---对象变量并发访问

    对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

  9. Java多线程简析——Synchronized(同步锁)、Lock以及线程池

    Java多线程 Java中,可运行的程序都是有一个或多个进程组成.进程则是由多个线程组成的.最简单的一个进程,会包括mian线程以及GC线程. 线程的状态 线程状态由以下一张网上图片来说明: 在图中, ...

随机推荐

  1. MySQL 连接出现 Authentication plugin 'caching_sha2_password' cannot be loaded

    参考帖子: https://www.cnblogs.com/zhurong/p/9898675.html cmd 需要使用管理员权限打开

  2. [c#.net]遍历一个对象中所有的属性和值

    利用反射 SpDictItem sp = GetCFHObject.GetSpItem("); PropertyInfo[] propertys = sp.GetType().GetProp ...

  3. vue中 关于$emit的用法

    1.父组件可以使用 props 把数据传给子组件.2.子组件可以使用 $emit 触发父组件的自定义事件. vm.$emit( event, arg ) //触发当前实例上的事件 vm.$on( ev ...

  4. (转)jira7.2安装、中文及破解

    转自:http://www.cnblogs.com/ilanni/p/6200875.html 本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanni ...

  5. python insert所用 插入到自定的位置

    a = list(range(50)) b = list(range(50)) c = [] for x in a: c.insert(x, [a[x], b[x]]) print(c)

  6. document,element,node方法

    document方法: getElementById(id)                             返回指定结点的引用 getElementsByTagName_r(name)    ...

  7. kali入门

    第一章:入门kalilinux By:鬼尘 第一章基本上就是涵盖以下的主题: ·kali的发展简史 ·kali的一般用途 ·kali的下载与安装 ·kali的配置与更新 在本章结尾部分,我们还会介绍k ...

  8. lightoj 1074

    这题怎么说呢,负环上的点都不行 网上也有很多解法 我用dfs的spfa解的 我发现网上别人的代码都是用bfs的spfa写的,我就用的dfs的,快了好多 代码还看的别人的,只有中间的spfa是自己写的 ...

  9. 25.HashTable

    在java中有两个类都提供了一个多种用途的hashTable机制,他们都可以将key和value结合起来构成键值对通过put(key,value)方法保存起来,然后通过get(key)方法获取相对应的 ...

  10. DOSBOX的安装和使用(window10 64位)

    1.安装DOSBOX DOXBOX和MASM的下载和安装 2.使用DOSBOX 1.打开只有一个窗口的dosbox 2.修改dosbox的分辨率 1.打开DOSBox 0.74 Options.bat ...