常用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. su与su -的区别

    su命令从普通用户切换到root用户下虽然可以切换,但是切换过后它所属的环境变量没有切换回原本属于root本身该有的环境变量,使用su - root 就可以切换会本来用户所属自身的变量

  2. Meanshift均值漂移算法

      通俗理解Meanshift均值漂移算法  Meanshift车手?? 漂移?? 秋名山???   不,不,他是一组算法,  今天我就带大家来了解一下机器学习中的Meanshift均值漂移. Mea ...

  3. web安全系列1:入侵的途径

    大家好,接下来的很长一段时间我都会介绍和web安全有关的知识,欢迎大家关注和转发. 话不多说,我们首先来看看今天的主题----入侵的途径.当然,今天介绍的都是针对web网站的常用手法和技巧. 不可否认 ...

  4. jquery学习总结12-24

    一.jquery操作类的相关方法 1.addClass()方法可以为DOM元素添加类,若添加多个类中间可以用空格连接 2.removeClass()方法可以为DOM元素删除类,若删除多个类中间可以用空 ...

  5. appium 1.6.x版本去除安装Unlock、Setting

    (appium目录不知道可以查看appium运行日志) 修改目录/usr/local/lib/node_modules/appium/node_modules/appium-android-drive ...

  6. C语言中结构体(struct)的几种初始化方法

    转自https://www.jb51.net/article/91456.htm     本文给大家总结的struct数据有3种初始化方法 1.顺序 2.C风格的乱序 3.C++风格的乱序 下面通过示 ...

  7. python循环解压rar文件

    python循环解压rar文件 C:. │ main.py │ ├─1_STL_算法简介 │ STL_算法简介.rar │ └─2_STL_算法_填充新值 STL_算法_填充新值.rar 事情是这样的 ...

  8. How to setup Visual Studio without pain

    Visual Studio (VS) can be very hard to install. If you are lucky, one whole day may be enough to ins ...

  9. OC字典的使用

    在OC中,字符串.数组.字典是最常见的对象类型,但是在这三个当中,字典的用法相对较少,因为字典的属性和方法比较少,但是一个字典的用法比较复杂,因为在一个字典当中,既可以包含字符串,也可以包含数组,数组 ...

  10. 不停止nginx服务,使配置文件生效

    ps -ef | grep "nginx: master process" | grep -v "grep" | awk -F ' ' '{print $2}' ...