作者:宁艺汉先生 | 来源:互联网 | 2024-11-09 16:46
《JAVA啃骨头》之 Java SE 章节java的并发机制及并发机制下数据安全问题的解决方案 说在前面:线程和进程 在提及多线程的时候,必须引入线程这个概念,而线程往往是和进程联系在一起的。线程和进
《JAVA啃骨头》之 Java SE 章节
java的并发机制及并发机制下数据安全问题的解决方案
-
说在前面:线程和进程
在提及多线程的时候,必须引入线程这个概念,而线程往往是和进程联系在一起的。线程和进程的关系,举一个例子,一家公司是一个进程,网景和京东是不同的两家公司,他们就是两个不同的进程,本质上来说,进程实际上指的是一个应用程序。而线程可以将其看做是刘强东和奶茶妹妹,他们是两个线程,在京东这个进程里面有两个线程。
进程(网景)和进程(京东)之间的资源是不会共享的,而同一个进程下的线程之间是会共享一部分资源的,当然并不是全部的资源都会共享。(刘强东和奶茶妹妹是有一部分的秘密可以共享的,但是他们也有各自的小秘密。)
-
并发机制及存在价值
如果把一个火车站看做是一个进程的话,那么多条检票窗口就是不同的线程,检票窗口多,检票效率就高。多线程的存在也同样是为了提高效率。
一个线程一个栈,多个栈并行,工作效率就高。
-
对于单核的CPU来说,多线程的本质其实是单线程
一个CPU就像一个大脑,一个大脑只能同时执行一个任务,对于多核的CPU来说,确实可以做到真正的并行,但对于单核CPU来说,一个时间只能同时进行一个任务,所以,对于单核的CPU来说,多线程其实本质上是单线程。
那么问题来了,既然单核的CPU的多线程其实本质是单线程,我们为何还称之为多线程呢?再举个栗子,小明准备写作业,可他有两个作业要写,但是他不想一个写完了再写另外一个,所以他想了一个特别的方法,一个作业写一个字,之后再写另一个作业,也只写一个字,这样轮流写,最终,我们可以看做他的两个作业是同时写完的。我们可以把小明看做是一个CPU,同时只能写一个作业(一个线程),但是,由于他在两个作业之间来回切换(抢夺时间片,获得执行权),总的来看,其实就是实现了同时完成两个作业(多线程并发)。
-
多线程的实现方式
java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
注意:第二种方式实现了接口,比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。(java支持单继承,支持多实现)因此,在使用多线程的时候,常常采用第二种方式。
-
多并发下数据安全问题
如果两个线程同时对一个数据进行修改,在一个线程获取数据后,还未将修改的数据返回,而另一个线程在此时获取的数据,那么,就会出现数据安全问题。
多线程并发的数据安全问题的存在有三个条件:存在对数据的修改、多线程、共享数据。
-
多并发环境下追求数据安全
首先需要明确,虽然我们追求高效率,但是,数据的安全永远是需要放在第一位的,因此,在两个需要作出选择的时候,我们要首选数据安全,也正因此,我们在具备上述三个条件的时候,就不能采取多线程机制,只能采取单线程机制(线程同步机制)。
-
多并发环境下数据安全的解决方案
java的数据存在于三种变量里:
属性 | 位置 | 位置个数 | 是否满足上述三个条件 | 是否存在线程安全问题 |
---|
实例变量 | 堆中 | 1 个 | 有可能 | 是 |
静态变量 | 方法区 | 1个 | 有可能 | 是 |
局部变量 | 栈 | 多个 | 不满足 | 是 |
以后,存储数据常常采用数据结构的方式进行存储数据。如果使用局部变量的话:建议使用:StringBuilder。因为局部变量不存在线程安全问题,选择StringBuilder。StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。
synchronized是一种锁机制,synchronized有三种写法:
第一种:同步代码块:灵活
synchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
我们以后开发中应该怎么解决线程安全问题?是一上来就选择线程同步synchronize吗?不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。
- 多并发的使用场景
多并发也叫做高并发,高并发的应用场景有很多,比如:12306的秒杀、淘宝双十一的抢购。对于不同的并发量,可以有不同的解决方案…