多线程带来的数据安全问题:
- 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 一个进程可以由多个线程进行分配,综合一起完成某个进程的任务
- 线程就是进程中一个独立控制单元,线程控制者进程的执行,是进程的执行单元
- 为了更好的利用cpu资源,如果只有一个线程,则第二个任务必须要等到第一个任务结束后才能进行执行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待
- 进程之间不能共享数据,线程之间可以进行共享
- 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小(资源可以进行共享)
- Java语言内置了多线程功能的支持,简化了java多线程编程
线程的生命周期:
- 新建:从新建一个线程对象到程序start()这个线程之间的状态,都是新建状态
- 就绪:线程对象调用了start()方法后,就处于就绪状态,等待JVM虚拟机的线程调度器的调度
- 运行:在线程被线程调度器调用后,再次获得CPU资源后就可以执行run()方法,此时线程处于运行状态,运行状态的线程可以变为就绪,阻塞及死亡状态
- 等待/阻塞/睡眠:在一个线程执行了sleep,suspend(挂起)等方法就会失去cpu的占有紫云啊,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态(等待被调度)
- 终止(死亡)状态:run()方法完成后或发生其他终止条件时就会切换到终止状态
实现Runnable接口和Thread的对比优势:
- 使用Thread进行继承,比较简单,但是局限性,无法再次继承其他的类
- 使用Runnable接口:线程代码存放在接口的子类run方法中,避免了单继承的局限性,多个线程可以共享一个target对象,适合对多线程共同处理一份资源的过程;缺点在于每次进行线程的访问必须使用Threa.currentThread()方法,没有返回值
- 综上所述,建议使用接口的方式创建多线程
为什么要由线程状态的管理(sleep):
- 线程执行的太快,或者需要强制执行到下一个线程,降低抢占的概率
线程的同步与锁:
**为什么一定要线程同步?**
java允许多线程并发控制,当做个线程同时操作一个可共享资源变量的时候,由于线程之间的抢占性机制,产生相互冲突,导致数据不准确,所以假如同步锁用来避免该线程在没有完成操作的时候其他线程抢占调用,从而保证了该变量的唯一性和准确性。
java程序执行的过程中,线程可以理解为java程序执行的每行代码每个对象等执行控制流,进行执行,所以每个线程的抢占可以发生在任何的一行代码中,抢占是随时随地的。
1. 线程之间存在相互抢占,发生在代码的每一步,导致多线程的数据并发安全问题
2. 解决方式:加锁,主要有两种加锁的方式
- 同步代码块锁 syncronizeed(对象)---->相当于加上标记的作用
- 根据锁对象 共享进来的所有线程对象保证执行代码块中的执行内容执行权不会被抢占
1. 方法取资源被所有线程对象进行共享,静态,静态块,方法类等(耗内存)
2. 锁对象:一个对象,可以将哪些线程对象共享进来
3. 可以把当前参与的线程对象共享进来的对象(推荐写法)
4. this(当参与的所有线程对象共享一个Runnable实现类共享)
5. 使用该代码块,代码块的范围会获取到内置锁,从而实现同步,被该关键字修饰的代码块将会被自动枷锁
6. 确定对象的时候,不会释放锁对象,但是会释放cpu的执行权,让其他线程进行抢占
- 同步方法锁
- 根据锁对象 共享进来的所有线程对象保证执行方法中的执行内容执行权不会被抢占
1.如果是非静态方法,默认锁对象就是this当前对象
2.如果是静态方法,默认锁对象就是当前类.class(方法区资源,所有的calss文件都是先被加载进入方法区)
3. 每个java对象都有一个内置锁,当用synchronize的时候会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会一直处于阻塞状态。
问题:如果同步函数被静态修饰后,使用的锁是什么?静态方法中不能定义this?
- 静态内存是:内存中没有本类对象,但是一定有该类对应的字节码文件,此时的对象为类名.class 该对象是class对象,一定被加载进了内存
- 所以静态的中是这样的:
-
public static mySyn(String name){
synchronized (Xxx.class) {
Xxx.name = name;
}
}
同步代码块代码:
class Seller implements Runnable{
//总的票数
//int count=100;
//声明代表票的类的对象共享同一个对象
//有参构造---保证创建的共享一个对象
private Ticket t;
public Seller(Ticket t){
this.t=t;
}
@Override
public void run() {
//同步代码块
//锁对象:指定线程对象的共享范围,可以共享那些线程对象
//被共享进来的线程对象,在代码块中不会发生抢占
//开始卖票,最终的边界条件是count=0的时候,不能进行减少
while (true){
// synchronized(t){//t表示当前参与的线程对象共享进来
//指定锁对象可以把所有线程对象都共享资源---(方法区资源)
//synchronized (Math.class){
synchronized (this){
//当前对象的确定,只会释放cpu的执行权,但是不会释放当前的锁对象
//总的票数为0的时候,进行循环的结束
if(t.getCount()==0)
break;
//count=0的时候票已经被卖完了
//每次进行卖一张票
t.setCount(t.getCount()-1);
System.out.println(Thread.currentThread().getName()+"正在卖第一张票"+",此时还有"+t.getCount()+"张票");
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Ticket{
//总票数
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
同步和异步的区别:
同步:在某个进程中只能有一个线程对象拥有资源(没有抢占)
异步:一个资源可能会被多个线程抢占,造成数据不准确
同步一定是安全的,线程时安全的---每个线程对象都加了锁
异步不一定是不安全的,但是同步一定是安全的
安全的不一定是同步,同步一定是安全的
**死锁:**由于锁的嵌套导致死锁的问题,通过死锁检测来让其中一个线程先执行,减少死锁的发生
死锁的产生原因:
- 当有多个线程存在的时候,进程A中包含B进程的所需要的资源,B进程中包含A进程执行所需的资源,两个进程都是处于下一步需要的资源,但是资源却被对方获取,两个进程互相等待,形成永久等待的状态便形成了死锁的状态
代码:
public class DeadLockDemo1 {
//死锁的测试数据
public static void main(String[] args) {
//创建打印的扫描对象
Printer p=new Printer();
//创建扫描
Scann s=new Scann();
//员工一先打印在扫描
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//锁住了p打印机的线程
synchronized (p){
//打印
p.print();
//休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//锁住扫描
synchronized (s