当多个线程同时访问同一资源(变量,文件,记录),如果只有读操作,则不会有线程安全问题,如果有读和写操作,则会产生线程安全问题,必须保证共享数据同一时刻只能有同一个线程操作。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() {
} } }

Java线程的安全问题的更多相关文章

  1. Java 线程不安全问题分析

    当多个线程并发访问同一个资源对象时,可能会出现线程不安全的问题 public class Method implements Runnable { private static int num=50; ...

  2. Java 线程安全问题

    线程安全问题产生原因: 1.多个线程操作共享的数据: 2.操作共享数据的线程代码有多条.   当一个线程正在执行操作共享数据的多条代码过程中,其它线程也参与了运算, 就会导致线程安全问题的发生. cl ...

  3. Java学习:线程的安全问题

    线程的安全问题 模拟卖票案例创建三个的线程,同时开启,对共享的票进行出售 public class RunnableImpl implementsc Runnable{ //定义一个多线程共享的票源 ...

  4. java多线程(三)线程的安全问题

    1.1. 什么是线程安全 如果有多个线程同时运行同一个实现了Runnable接口的类,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的:反之,则是线程不 ...

  5. Java 线程安全问题的本质

    原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: 线程安全问题的本质 理解CPU JVM虚拟机类比于操作系统 重排序 汇总 一些解释 ...

  6. 第22章 java线程(2)-线程同步

    java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...

  7. Java线程新特征——Java并发库

    一.线程池   Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...

  8. Java线程:概念与原理

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  9. java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  10. java线程详解(三)

    java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private R ...

随机推荐

  1. J2eefast页面调试状态下自动进入debugger断点

    将fastJS.min.js 替换为 fastJs.js 将所使用到的模块的min.js 替换为js文件

  2. hyperf的使用

    hyperf是swoole的封装框架,用起来效率还是不错的. 使用方式看手册 https://hyperf.wiki/2.2/#/zh-cn/quick-start/install 其实是靠compo ...

  3. 关于switch的优化:转移表(完结)

    用转移表来代替功能实现中的 switch 语句 1. switch 语句应用场景 在许多场景中会用到条件判断,此时简单的条件判断一般通过 if/else 语句实现:如果涉及三条以上的功能分支一般会通过 ...

  4. linux学习用到的命令

    创建快件方式 ln 创建目录的快件方式 sudo ln -s /root/myhack/ /root/Desktop以上指令是创建软链接到桌面. ln -s /mnt/hgfs/VMware_shar ...

  5. Web实时消息推送的解决方案

    什么是消息推送(push) 推送的场景比较多,比如有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用. 消息推送(push)通常是指网站的运营工作等人员,通过某种工具对用户当前网 ...

  6. 正也科技案例 | 药企使用S2P深入营销管理数据化建设

    为了获取更*的市场空间,医药健康行业正迎来一波前所未有的产业升级.尽管不少企业取得了许多成绩,但仍面临诸多挑战. 浙江某知名医药公司,在泌尿系统.心脑血管系统及眼科用药领域均拥有强势品牌.其产品更是荣 ...

  7. 对于web性能优化我有话说!

    web性能是什么呢? 简单来说就是咱们的项目打开的快不快,举以下几个例子 表单提交 列表切换 动画的流程性 打开速度 MDN上对web性能的定义则是Web性能是网站或应用程序的客观度量和可感知的用户体 ...

  8. 树莓派4B 微雪7寸触摸屏 双屏 触摸屏校正

    树莓派4B+微雪7寸触摸屏+PC显示器,以触摸屏位主显示,PC显示器扩展,这时会有触摸不准的情况. 通过观察可以发现触摸被放大到了整个屏幕,即触摸屏+PC显示器. 1. 通过查看2个屏幕分辨率和位置, ...

  9. COSBrowser文件链接导出——爆赞的本地化管理功能

    前言 ​ 用过COSBrowser的小伙伴们应该都知道,COSBrowser的文件分享功能非常好用.但是,文件分享功能又有所局限,就是它只能分享单个文件,而文件夹分享,更有其因为部分必要因素,如安全性 ...

  10. 助推乡村振兴,四川农担X中电金信大数据智能风控平台彰显“榜样的力量”

    ​在2024年3月召开的两会上,政府工作报告首次提到了要大力发展科技金融.绿色金融.普惠金融.养老金融.数字金融,为深入推进金融高质量发展,走中国特色金融发展之路指明了方向.其中,四川农担作为一家专注 ...