Java 多线程中篇

栏目: Java · 发布时间: 5年前

内容简介:我们创建两个线程来调用同一业务对象的相同功能时, 可以看到下面输出.两个线程在一起执行将

异步

public class PrintObject {
    public void printString(){
        System.out.println("begin");
        if(Thread.currentThread().getName().equals("a")){
            PrintStream printStream = System.out;
            printStream.println("线程 a 运行");
        }
        if(Thread.currentThread().getName().equals("b")){
            System.out.println("线程 b 运行");
        }
        System.out.println("end");
    }
}
public static void main(String[] a) {
    PrintObject pb = new PrintObject();

    Thread thread1 = new Thread(pb::printString);
    thread1.setName("a");
    thread1.start();

    Thread thread2 = new Thread(pb::printString);
    thread2.setName("b");
    thread2.start();
}

我们创建两个线程来调用同一业务对象的相同功能时, 可以看到下面输出.

begin
begin
线程 a 运行
end
线程 b 运行
end

两个线程在一起执行 printString 方法, 并且交叉打印. 也就是说当我们启动一个线程执行某个方法的时候就是异步执行 , 至于为啥要这样演示, 是因为下面的同步.

同步

synchronized public void printString() 方法上加入 synchronized 关键字, 来使方法同步.

执行结果:

begin
线程 a 运行
end
begin
线程 b 运行
end

那么为什么加入 synchronized 关键字后就会同步呢? 这是因为关键字 synchronized 会取得一把对象锁, 而不是把一段代码或方法当做锁; 哪个线程先执行带 synchronized 关键字的方法, 哪个线程就持有该方法所属的对象的锁 Look, 那么其他线程只能呈等待状态.

这里有个前提是多个线程访问同一个对象, 下面演示的是多个线程访问不同的对象.

public class PrintObject {
    synchronized public void printString(){
        System.out.println("begin");
        if(Thread.currentThread().getName().equals("a")){
            PrintStream printStream = System.out;
            printStream.println("线程 a 运行");
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                
            }
        }
        if(Thread.currentThread().getName().equals("b")){
            System.out.println("线程 b 运行");
        }
        System.out.println("end");
    }
}
public static void main(String[] a) {
        PrintObject pb = new PrintObject();
        PrintObject pb1 = new PrintObject();

        Thread thread1 = new Thread(pb::printString);
        thread1.setName("a");
        thread1.start();

        Thread thread2 = new Thread(pb1::printString);
        thread2.setName("b");
        thread2.start();
    }

执行结果

begin
线程 a 运行
begin
线程 b 运行
end

让 a 线程睡眠 100000 毫秒, 可以看到 a 线程并没有执行完, b 线程就运行了. 这也能够证明 synchronized 关键字取得是对象锁.

另外还需要注意一点, 我们使用两个线程执行同一对象的不同同步方法时, 如果线程 a 在睡眠, 那么线程 b 也会一直等待, 线程 a 执行完毕后再去执行.

注: 同步方法一定是线程安全的.

synchronized 锁重入

如果一个获取锁的线程调用其它的synchronized修饰的方法, 会发生什么?

在一个线程使用synchronized方法时调用该对象另一个synchronized方法, 即一个线程得到一个对象锁后再次请求该对象锁, 是永远可以拿到锁的.

Java 内部, 同一个线程调用自己类中其他synchronized方法/块时不会阻碍该线程的执行, 同一个线程对同一个对象锁是可重入的, 同一个线程可以获取同一把锁多次, 也就是可以多次重入. 原因是Java中线程获得对象锁的操作是以线程为单位的, 而不是以调用为单位的.

这种情况也可以发生在继承中, 也就是说子类的同步方法调用父类的同步方式时, 时可以锁重入的.

synchronized 同步代码块

public class PrintObject {
     public synchronized void printString(){
        try {
            System.out.println(Thread.currentThread().getName() + " 执行");
            System.out.println(Thread.currentThread().getName() + " 插入数据到数据库");
            // 让线程休眠, 模拟出网络延时
            Thread.sleep(5000);

            System.out.println(Thread.currentThread().getName() + " 共享数据减1");
            Thread.sleep(5000);

            System.out.println(Thread.currentThread().getName() + " 插入数据到数据库");
            Thread.sleep(5000);

            if (Thread.currentThread().getName().equals("b")) {
                SimpleDateFormat df = new SimpleDateFormat("mm:ss");
                System.out.println(df.format(new Date()));
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
PrintObject pb = new PrintObject();

Thread thread1 = new Thread(pb::printString);
thread1.setName("a");

Thread thread2 = new Thread(pb::printString);
thread2.setName("b");

SimpleDateFormat df = new SimpleDateFormat("mm:ss");
System.out.println(df.format(new Date()));

thread1.start();
thread2.start();

执行结果

47:34
a 执行
a 插入数据到数据库
a 共享数据减1
a 插入数据到数据库
b 执行
b 插入数据到数据库
b 共享数据减1
b 插入数据到数据库
48:04

我们上面这段程序两个线程全部执行完所用的时间为 30 秒, 这里可以看出同步方法存在一个很大的弊端.

就是说我们的某个线程开始执行方法时, 无论我们操作的是不是共享数据, 别的线程都会等待此线程释放锁. 然后继续执行.

可是我们在插入数据到数据库的时候, 并不是在操作共享数据, 那么我们有没有什么办法, 只同步操作共享数据的那部分代码呢?

我们就可以使用 synchronized 同步代码块, 将程序修改成下面样子.

public class PrintObject {
     public void printString(){
        try {
            System.out.println(Thread.currentThread().getName() + " 执行");
            System.out.println(Thread.currentThread().getName() + " 插入数据到数据库");
            // 让线程休眠, 模拟出网络延时
            Thread.sleep(5000);

            synchronized(this) {
                System.out.println(Thread.currentThread().getName() + " 共享数据减1");
                Thread.sleep(5000);
            }


            System.out.println(Thread.currentThread().getName() + " 插入数据到数据库");
            Thread.sleep(5000);

            if (Thread.currentThread().getName().equals("b")) {
                SimpleDateFormat df = new SimpleDateFormat("mm:ss");
                System.out.println(df.format(new Date()));
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果

54:12
b 执行
a 执行
b 插入数据到数据库
a 插入数据到数据库
a 共享数据减1
a 插入数据到数据库
b 共享数据减1
b 插入数据到数据库
54:32

减少了10秒的执行时间, 提高了执行效率.

同步方法和同步代码块的锁都是同一把锁. 同步方法获取的是该方法的对象锁, 而同步代码块获取中的参数是 this, 表示当前对象. 所以获取的是同一把锁.

静态同步 synchronized 方法与 synchronized(class) 代码块

synchronized 关键字可以应用在 static 静态方法上, 表示当前的 *.java 文件对应的 Class 类进行持锁.

虽然运行结果与 synchronized 关键字加到非 static 静态方法上的结果类似, 但是是对 Class 类进行加锁, 而 Class 锁可以对类的所有对象起作用.

synchronized (DemoApplication.class) {
            
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Designing for Emotion

Designing for Emotion

Aarron Walter / Happy Cog / 2011-10-18 / USD 18.00

Make your users fall in love with your site via the precepts packed into this brief, charming book by MailChimp user experience design lead Aarron Walter. From classic psychology to case studies, high......一起来看看 《Designing for Emotion》 这本书的介绍吧!

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具