Java多线程之二(Synchronized)

经常使用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,如今咱们能够了解一下在多线程中占据着重要地位的锁了。api

为何会出现线程不安全

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

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

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的使用。ide

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在不一样场景下的区别

实体类:ui

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);
//        }
    }
}

复制代码

测试类:this

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(...)方法为静态方法:spa

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对象和字节码对象,而能够是任意的对象。而实际效果和使用当前类的对象一致。

相关文章
相关标签/搜索