惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Cloudbric
Cloudbric
The Register - Security
The Register - Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives
G
Google Developers Blog
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园_首页
Last Week in AI
Last Week in AI
A
Arctic Wolf
B
Blog RSS Feed
J
Java Code Geeks
C
Cybersecurity and Infrastructure Security Agency CISA
I
InfoQ
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
有赞技术团队
有赞技术团队
S
Schneier on Security
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Vercel News
Vercel News
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans

Liu Zijian's Blog | 一个技术博客

使用Certbot自动续签HTTPS证书 使用Filebeat采集Nginx日志到ES Python的协程 Python中的异常 Python中的类和对象 Python的函数 Python的数据结构,推导式、迭代器和生成器 Spring AI集成多模态模型 LangChain4j多模态 LangChain Tools工具使用 Python中的模块和包 Python全局环境和虚拟环境(venv) LangChain Prompt提示词工程 LangChain4j Tools工具使用 基于Dify搭建AI智能体应用 LangChain4j RAG检索增强生成 Spring AI实现MCP Server Spring AI集成MCP Client LangChain4j Prompt提示词工程 Spring AI使用知识库增强对话功能 Spring AI实现一个智能客服 Spring AI实现一个简单的对话机器人 实现MinIO数据的每日备份 自己实现一个DNS服务 简单理解AI智能体 大模型和大模型应用 LangChain开篇 LangChain4j开篇 一个解析Excel2007的POI工具类 DataPermissionInterceptor源码解读 TenantLineInnerInterceptor源码解读 BaseMultiTableInnerInterceptor源码解读 Spring AI开篇 SQL解析工具JSQLParser 芋道源码解读之多租户 芋道源码解读之数据权限 芋道源码解读开篇 Java实现将数据导出为Word文档 OA系统的天数该怎样计算 安装MySQL8 安装MySQL5.7 RockyLinux9环境下编译MySQL8 MySQL字符集及底层原理 Java实现LDAP登录 Docker Compose IPv4和IPv6 使用虚拟机安装一个K8s集群 使用GraalVM原生编译打包SpringBoot工程 Nginx防止目录穿越 Java线程的状态 Nginx防盗链设置 使用python将excel表格转换为SQL INSERT Redis的公共操作命令 Redis数据结构之Bitfleid Redis数据结构之Bitmap Redis数据结构之GEO Redis数据结构之Hash Redis数据结构之HyperLogLog Redis数据结构之List Redis数据结构之Set Redis数据结构之Stream Redis数据结构之String Redis数据结构之ZSet 使用python压缩图片 利用Python实现Hexo站点的持续集成 Nginx设置HTTPS监听 firewalld防火墙工具的使用 Linux信号(signal)机制 MySQL5.7x 主从复制 用IP自签发一个HTTPS证书 基于Hexo实现一个静态的个人博客 RockyLinux9环境下编译MySQL5.7 Docker离线安装 MySQL数据定义语言 Docker与联合文件系统 Docker的网络 Docker的镜像操作 MySQL存储过程 MyBatis-Plus开篇 MySQL变量 MySQL视图 MySQL事务 MySQL插入修改和删除 MySQL查询 MySQL系统命令 Docker的容器操作 Docker的安装和配置 Docker容器数据卷 浅谈OAuth2.0授权原理 JVM开篇 浅谈Linux(Unix)的I/O模型 一个通用的CloseableHttpClient工厂类 JUC可重入锁ReentrantLock JUC读写锁ReadWriteLock Java的单例 Java泛型 Java8的新特性 最近最少使用算法(LRU) MySQL函数 SpringBoot配置和启动
Java线程安全和同步机制
Liu Zijian · 2022-05-01 · via Liu Zijian's Blog | 一个技术博客

当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作。Java采取的办法是synchronized同步代码块或同步方法。同步代码块或同步方法解决了线程安全问题,但是操作共享数据时,线程时串行执行的,意味着效率较低。

1.多线程安全问题

经典卖票案例:

两个线程一块卖票,没有加同步代码块,程序运行结果不正确,存在超卖重卖


public class Ticket {

    public static void main(String[] args) {

        TicketTask t1 = new TicketTask();
        TicketTask t2 = new TicketTask();

        t1.start();
        t2.start();

    }

    static class TicketTask extends Thread {

        static int ticket = 200;

        @Override
        public void run() {
            while (true) {

                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() +" " + ticket);
                    ticket--;
                }
                else {
                    break;
                }
            }
        }
    }
}

2 同步代码块和同步方法解决线程安全问题

2.1 同步代码块

需要被同步的代码,即为操作共享数据的代码。共享数据,即为多个线程都需要操作的数据。同步监视器可以由任何类的对象担任,但是多个线程必须共用同一个同步监视器。

同步代码块的语法

synchronized (同步监视器/) {
  //需要被同步的代码
}

加入同步代码块,程序运行结果正确,当有线程操作共享数据,其他线程需要等待。lock作为同步监视器,锁住代码块中的操作,谁获得同步监视器,谁运行同步代码块中的代码。


public class Ticket {

    public static void main(String[] args) {

        TicketTask t1 = new TicketTask();
        TicketTask t2 = new TicketTask();

        t1.start();
        t2.start();

    }

    static class TicketTask extends Thread {

        static int ticket = 200;
        static final Object lock = new Object();

        @Override
        public void run() {
            while (true) {

                synchronized (lock) {

                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName() +" " + ticket);
                        ticket--;
                    }
                    else {
                        break;
                    }

                }

            }
        }
    }
}

2.2 同步方法

如果需要同步执行的代码恰好在一个方法中,可以使用同步方法保证线程安全,在方法声明上使用 synchronized 关键字,此时锁为对象实例(this)。同一时刻,只有一个线程能够执行该实例的方法。

public synchronized void test() {

}

3 同步代码块和同步方法的使用

3.1 Runnable创建线程时使用同步代码块

使用synchronized使得实例方法加锁变为同步方法,因为Runnable对象只有一个,所以锁可以直接使用当前调用者this


public class TestRunnable {
    public static void main(String[] args) {
        Target target = new Target();
        for (int i = 0; i < 10; i++) {
            new Thread(target, "T"+i).start();
        }

    }

}


class Target implements Runnable {

    private Integer i = 1000;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) {
                if (i > 0) {
                    i--;
                    System.out.println(Thread.currentThread().getName() + "->" + i);
                } else {
                    break;
                }
            }

        }

    }

}

3.2 Thread类创建线程时,使用同步代码块

Thread类创建线程时,使用同步代码块加锁,因为Thread对象是多个,所以需要静态的监视器对象object,如果还用this就出现了多个锁

public class TestThread {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new MyThread("T"+i).start();
        }
    }

}



class MyThread extends Thread {

    private static Integer i = 1000;
    //同步监视器,锁
    private static Object object = new Object();

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while(true) {
 
            synchronized(object) {
                if (i > 0) {
                    i--;
                    System.out.println(Thread.currentThread().getName() +"->" + i);
                } else {
                    break;
                }
            }
        }
    }
}

3.3 Runnable创建线程时,使用同步方法加锁

public class TestRunnable {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {

            private int num = 1000;

            @Override
            public void run() {
                while (true) {
                    this.show();
                }
            }
 
            public synchronized void show() {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (num > 0) {
                    num--;
                    System.out.println(Thread.currentThread().getName() + "->" + num);
                }
            }

        };

        for (int i = 0; i < 10; i++) {
            new Thread(runnable, "T" + i).start();
        }

    }
}

3.4 Thread创建线程时,使用同步方法加锁

public class TestThread {

    public static void main(String[] args) {

        TicketTest ticketTest = new TicketTest();

        for (int i = 0; i < 10; i++) {
            new Thread("T"+i) {

                @Override
                public void run() {
                    while (true) {
                        ticketTest.test();
                    }
                }

            }.start();
        }
    }

    static class TicketTest  {

        private int num = 1000;

        private synchronized void test() {

            if (num > 0) {
                num--;
                System.out.println(Thread.currentThread().getName() + "->" + num);
            }
        }

    }

}

3.5 静态方法加锁和使用.class对象做锁

在静态方法上使用 synchronized,锁住的是类的.class对象,每个类的class对象只有一个,所以同时只能有一个线程进入方法。

public static synchronized void staticMethod() {
    // 方法体
}

同步块上使用.class对象做锁,因为每个类.class对象只有一个,故也能用于保证线程安全

synchronized (A.class) {

}

4 死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

写程序时,要避免出现死锁。

示例代码1

死锁原因的分析:这个例子是个比较明显的死锁,线程t1t2几乎同时启动,在一秒钟的等待时间里,t1获得了锁lock1t2获得了锁lock2,一秒钟后t1又想去获得lock2,但是现在lock2t2持有,需要一直等直到t2释放lock2,与此同时,t2也想去获得lock1,但是lock1现在被t1持有,需要一直等待,直到t1释放lock1,两个线程都在争抢在对方持有的锁,且都在等待对方先释放各自持有的锁,不然就一直等待,线程都一直处在阻塞状态无法继续运行,造成死锁。


public class TestDeadLock {
    public static void main(String[] args) {
        Object lock1 = new Object();
        Object lock2 = new Object();

        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (lock1) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }

                    synchronized (lock2) {
                        System.out.println(lock1);
                        System.out.println(lock2);
                    }
                }

            }
        };


        Thread t2 = new Thread(){
            @Override
            public void run() {
                synchronized (lock2) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }

                    synchronized (lock1) {
                        System.out.println(lock1);
                        System.out.println(lock2);
                    }
                }
            }
        };


        t1.start();
        t2.start();


    }
}

示例代码2

这是一个不是非常明显的死锁的例子,线程thread1thread2几乎同时开始执行,thread1执行a.fun(b)时,由于A类的fun方法是个同步方法,故锁是当前调用者this对象,即a,调用fun方法,thread1便持有了锁a,与此同时,thread2同理的持有了锁b,这些都在一秒钟前完成了,1秒钟后,thread1执行blast同步方法,同理需要先获得锁b,但是锁b目前被thread2持有,同时thread2也开始执行alast方法,需要先持有锁a,但是锁athread1持有,双方都在等待对方先释放自己需要的锁,否则就一直阻塞无法继续运行,造成死锁。

public class TestDeadLock2  {

    public static void main(String[] args) {

        A a = new A();
        B b = new B();

        Thread thread1 = new Thread() {
            @Override
            public void run() {
                a.fun(b);
            }
        };


        Thread thread2 = new Thread() {
            @Override
            public void run() {
                b.fun(a);
            }
        };

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

    }



    public static class A extends Thread {

        public synchronized void fun(B b) {


            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            b.last();
        }

        public synchronized void last() {

        }

    }

    public static class B extends Thread {

        public synchronized void fun(A a) {

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            a.last();
        }

        public synchronized void last() {
        }

    }

}