Java多线程核心技术一-多线程基础其他内容

接上篇:

Java多线程核心技术一-基础篇synchronzied同步方法

Java多线程核心技术一-基础篇synchronzied同步语句块

1 String常量池特性与同步问题

JVM具有String常量池的功能,如下示例:

java 复制代码
public class Test01 {
    public static void main(String[] args) {
        String a = "a";
        String b = "a";
        System.out.println(a == b);
    }
}

在把synchronzied(string)同步块与String联合使用时,要注意常量池会带来一些意外。

java 复制代码
public class Service {
    public static void print(String param){
        try {
            synchronized (param){
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(500);
                    if(Thread.currentThread().getName().equals("B")){
                        break;
                    }
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
java 复制代码
public class Run1 {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
java 复制代码
public class ThreadA extends Thread{
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.print("A");
    }
}
java 复制代码
public class ThreadB extends Thread{
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public  void run(){
        service.print("A");
    }
}

出现这种情况就是因为String的两个值都是"AA",两个线程是持有相同的锁,造成线程B不能执行。这就是String常量池所带来的问题,所以大多数情况下,同步synchronzied都不使用String作为锁对象,而改用其他的。例如 new Object()实例化一个新的object对象时,它并不放入缓存池中,或者执行new String()创建不同的字符串对象,形成不同的锁。下面把synchronzied代码块的锁的对象改成object。

java 复制代码
public class Service1 {
    public static void print(Object obj){
        try{
            synchronized (obj){
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(500);

                }
            }

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


    }
}
java 复制代码
public class ThreadA1 extends Thread{
    private Service1 service1;

    public ThreadA1(Service1 service1) {
        this.service1 = service1;
    }
    @Override
    public void run(){
        service1.print(new Object());
    }
}
java 复制代码
public class ThreadB1 extends Thread{
    private Service1 service1;

    public ThreadB1(Service1 service1) {
        this.service1 = service1;
    }

    @Override
    public void run(){
        service1.print(new Object());
    }
}
java 复制代码
public class Run2 {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        ThreadA1 a = new ThreadA1(service1);
        a.setName("A");
        a.start();
        ThreadB1 b = new ThreadB1(service1);
        b.setName("B");
        b.start();
    }
}

A 和B 交替输出的原因是持有的锁不是同一个。

2 多线程死锁

Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,导致所有的任务都无法继续完成。在多线程技术中,死锁是必须要避免的,因为这会造成线程的"假死"。示例:

java 复制代码
public class DealThread implements Runnable{
    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();
    public void setFlag(String username){
        this.username = username;
    }

    @Override
    public void run() {
        if(username.equals("a")){
            synchronized (lock1){
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("按lock1 -> lock2代码顺序执行了");
                }
            }
        }
        if(username.equals("b")){
            synchronized (lock2){
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println("按lock2 -> lock1代码顺序执行");
                }
            }
        }
    }
}
java 复制代码
public class Run1 {
    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            Thread a = new Thread(t1);
            t1.setFlag("a");
            a.start();
            Thread.sleep(100);
            t1.setFlag("b");
            Thread b = new Thread(t1);
            b.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

可以使用JDK自带的工具来检测是否有死锁现象,进入jdk/bin目录下,执行jps命令:

看到运行线程Run1的id值是12880,在执行jstack命令,查看结果:

如上图,检测出有死锁 。

死锁是程序设计的bug,在设计程序时就要避免双方互相持有对方的锁,只要互相等待对方释放锁,就有可能出现死锁。

3 内置类与静态内置类

synchronzied关键字的知识点还涉及内置类的使用,先来看一个简单的内置类的测试。

java 复制代码
public class PublicClass {
    private String username;
    private String password;

    class PrivateClass{
        private String age;
        private String address;

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }
        public void printPublicProperty(){
            System.out.println(username + ";"+password);
        }
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
java 复制代码
public class Run1 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("usernameValue");
        publicClass.setPassword("passwordValue");
        System.out.println(publicClass.getUsername() + ";" + publicClass.getPassword());
        PublicClass.PrivateClass privateClass = publicClass.new PrivateClass();
        privateClass.setAge("ageValue");
        privateClass.setAddress("addressValue");
        System.out.println(privateClass.getAge() + ";" + privateClass.getAddress());
    }
}

如果PublicClass.java类和Run.java类不在同一个包中,则需要将PrivateClass内置类声明为public。想要实例化内置类,必须使用如下代码:

java 复制代码
PublicClass.PrivateClass privateClass = publicClass.new PrivateClass();

还有一种内置类叫静态内置类。

java 复制代码
public class PublicClass1 {
    static private String username;
    static private String password;

    static class PrivateClass{
        private String age;
        private String address;

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public void printPublicProperty(){
            System.out.println(username + ";"+password);
        }
    }

    public static String getUsername() {
        return username;
    }

    public static void setUsername(String username) {
        PublicClass1.username = username;
    }

    public static String getPassword() {
        return password;
    }

    public static void setPassword(String password) {
        PublicClass1.password = password;
    }
}
java 复制代码
public class Run2 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("usernameValue");
        publicClass.setPassword("passwordValue");
        System.out.println(publicClass.getUsername() + ";" + publicClass.getPassword());

        PublicClass1.PrivateClass privateClass = new PublicClass1.PrivateClass();
        privateClass.setAge("ageValue");
        privateClass.setAddress("addressValue");
        System.out.println(privateClass.getAge() + ";" + privateClass.getAddress());
    }
}

4 内置类的同步

本节测试的案例是内置类中有两个同步方法,但使用不同的锁,输出的结果也是异步的。

java 复制代码
public class Inner1 {
    public void method1(){
        synchronized ("其他的锁"){
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "; i = " +(i + 1));
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    synchronized public void method2(){
        for (int i = 11; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "; i = " + (i + 1));
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
java 复制代码
public class Run1 {
    public static void main(String[] args) {
        final Inner1 inner1 = new Inner1();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner1.method1();
            }
        },"A");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner1.method2();
            }
        }, "B");
        t1.start();
        t2.start();
    }
}

由于持有不同的锁,所有输出结果是乱序的。

5 锁对象改变导致异步执行

在把任何数据类型作为同步锁时,需要注意是否有多个线程同时争抢锁对象,如果同时争抢相同的锁对象,则这些线程之间就是同步的;如果分别获得自己的锁,那么这些线程之间就是异步的。通常情况下,一旦持有锁后就不再对锁对象进行更改,因为一旦更改就有可能出现一些错误。

java 复制代码
public class MyService {
    private String lock = "123";
    public void testMethod(){
        try {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "开始时间是:" + System.currentTimeMillis());
                lock = "456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束时间时: " + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
java 复制代码
public class ThreadA extends Thread{

    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run(){
        myService.testMethod();
    }
}
java 复制代码
public class ThreadB extends Thread{
    private MyService myService;

    public ThreadB(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run(){
        myService.testMethod();
    }
}
java 复制代码
public class Run1 {
    public static void main(String[] args) throws InterruptedException {

        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        ThreadB b = new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        Thread.sleep(50);
        b.start();
    }
}

因为50毫秒后,B现成取得锁时456.

继续实现,修改Run1.java,去掉代码中的Thread.sleep(50)

java 复制代码
public class Run2 {
    public static void main(String[] args) throws InterruptedException {

        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        ThreadB b = new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        b.start();
    }
}

需要注意的是,String类型是不可变的,都是创建新的内存空间来解决存储新的字符。

控制台输出的信息说明A线程和B线程检测到锁对象的值为123,且存储到内存空间X的位置,虽然将锁改成了456,并存储内存空间Y的位置,但结果还是同步的,因为A线程和B线程共同争抢的锁是X空间的123,不是Y空间的456。但是,还是会有很小的概率出现一起输出两个begin的情况,因为A线程将锁的值改成456后,B现成才启动区执行run方法,不存在A和B争抢同一把锁的情况,导致B线程获取的是更改后的锁的值(456),并连续输出两个begin。

相关推荐
这孩子叫逆10 分钟前
Spring Boot项目的创建与使用
java·spring boot·后端
星星法术嗲人13 分钟前
【Java】—— 集合框架:Collections工具类的使用
java·开发语言
黑不溜秋的27 分钟前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光32 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
天上掉下来个程小白34 分钟前
Stream流的中间方法
java·开发语言·windows
xujinwei_gingko1 小时前
JAVA基础面试题汇总(持续更新)
java·开发语言
liuyang-neu1 小时前
力扣 简单 110.平衡二叉树
java·算法·leetcode·深度优先
sp_wxf1 小时前
Lambda表达式
开发语言·python
一丝晨光1 小时前
Java、PHP、ASP、JSP、Kotlin、.NET、Go
java·kotlin·go·php·.net·jsp·asp
罗曼蒂克在消亡1 小时前
2.3MyBatis——插件机制
java·mybatis·源码学习