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。

相关推荐
Swift社区1 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht1 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht1 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20241 小时前
Swift 数组
开发语言
吾日三省吾码2 小时前
JVM 性能调优
java
stm 学习ing2 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc3 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐3 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi774 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器