Java线程的安全问题
当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作。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
死锁原因的分析:这个例子是个比较明显的死锁,线程t1,t2几乎同时启动,在一秒钟的等待时间里,t1获得了锁lock1,t2获得了锁lock2,一秒钟后t1又想去获得lock2,但是现在lock2被t2持有,需要一直等直到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
这是一个不是非常明显的死锁的例子,线程thread1和thread2几乎同时开始执行,thread1执行a.fun(b)时,由于A类的fun方法是个同步方法,故锁是当前调用者this对象,即a,调用fun方法,thread1便持有了锁a,与此同时,thread2同理的持有了锁b,这些都在一秒钟前完成了,1秒钟后,thread1执行b的last同步方法,同理需要先获得锁b,但是锁b目前被thread2持有,同时thread2也开始执行a的last方法,需要先持有锁a,但是锁a被thread1持有,双方都在等待对方先释放自己需要的锁,否则就一直阻塞无法继续运行,造成死锁。
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() {
        }
    }
}
Java线程的安全问题的更多相关文章
- Java 线程不安全问题分析
		当多个线程并发访问同一个资源对象时,可能会出现线程不安全的问题 public class Method implements Runnable { private static int num=50; ... 
- Java 线程安全问题
		线程安全问题产生原因: 1.多个线程操作共享的数据: 2.操作共享数据的线程代码有多条. 当一个线程正在执行操作共享数据的多条代码过程中,其它线程也参与了运算, 就会导致线程安全问题的发生. cl ... 
- Java学习:线程的安全问题
		线程的安全问题 模拟卖票案例创建三个的线程,同时开启,对共享的票进行出售 public class RunnableImpl implementsc Runnable{ //定义一个多线程共享的票源 ... 
- java多线程(三)线程的安全问题
		1.1. 什么是线程安全 如果有多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的:反之,则是线程不 ... 
- Java 线程安全问题的本质
		原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: 线程安全问题的本质 理解CPU JVM虚拟机类比于操作系统 重排序 汇总 一些解释 ... 
- 第22章  java线程(2)-线程同步
		java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ... 
- Java线程新特征——Java并发库
		一.线程池 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ... 
- Java线程:概念与原理
		Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ... 
- java线程详解
		Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ... 
- java线程详解(三)
		java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private R ... 
随机推荐
- frida 连接夜神模拟器
			adb connect 127.0.0.1:62001 adb devices adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27 ... 
- 想要硬件设计不用愁?首先要搞懂这三类GPIO!
			合宙低功耗4G模组经典型号Air780E,支持两种软件开发方式: 一种是传统的AT指令:一种是基于模组做Open开发. 传统AT指令的开发方式,合宙模组与行业内其它模组品牌在软件上区别不大,在硬件功耗 ... 
- OSG开发笔记(三十二):深入理解相机视口、制作支持与主视图同步变换旋转的相机HUD
			前言 深入理解相机视口,摸索相机视口旋转功能,背景透明或者不透明. 本篇,实现了一个左下角旋转HUD且背景透明的相机视口. Demo HUD相机的坐标 ... 
- P11233 CSP-S 2024 染色
			P11233 CSP-S 2024 染色 考试最后码方程忘记 \(a[i-1]\) 了,调不出来,只好 \(50pts\) 收尾. 思路 \(dp\) 的难点在于确定一段的颜色后,无法快速找到上一段相 ... 
- 1、oracle实例、软件、库简单讲解
			oracle的基本结构 oracle软件(RDBMS) oracle软件:关系型数据库管理系统 在linux系统上,oracle软件安装在:/u01/app/oracle这个目录下 oracle数据库 ... 
- MySQL之使用pt-online-schema-change在线修改大表结构
			原因: 最近公司上一个功能, 需要为其中某个表中新增字段,但是考虑到线上数据已经达到300w+的级别,同时使用的mysql的版本是5.7而非8.0,这会导致新增字段的时候,对全表进行加锁,直到添加完毕 ... 
- vue3-setup中使用响应式
			基本类型的响应式数据 在 Vue 3 中,ref是一个函数,用于创建响应式的数据.它主要用于处理基本类型(如数字.字符串.布尔值等)的数据响应式 当我们调用 ref 函数时,会返回一个包含一个 .va ... 
- 解析JDBC使用查询MySQL【非流式、流式、游标】
			解析JDBC使用游标查询MySQL 使用jdbc查询MySQL数据库,如果使用游标或者流式查询的话,则可以有效解决OOM的问题,否则MySQL驱动就会把数据集全部查询出来加载到内存里面,这样在大数据的 ... 
- #oscp#渗透测试 kioptix level 3靶机getshell及提权教程
			声明! 文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关,切勿触碰法律底线,否则后果自负!!!! 一.靶机搭建 点击扫描虚拟机 选择靶机使在文件夹即可 二.信息收集 前言 信 ... 
- 中电金信:产教联合共育人才 AFAC2024金融智能创新大赛启动
			当前,人工智能技术正在蓬勃发展,引领着各行各业迈向智能化的新纪元,特别是在金融科技领域,伴随人工智能技术的不断迭代与突破,金融服务的边界也在不断拓展,传统的金融业态正经历着深刻的变革与重塑. 与此同时 ... 
