前言
记录学习过程
我们可以通过Runnable接口创建线程,也可以实现多线程,接下来就需要实现线程安全
线程安全:当一个类,不断被多个线程调用,仍能表现出正确的行为时,那它就是线程安全的
而以前很多安全的代码到了多线程就会出问题,先要明白为什么会线程不安全
多线程基础知识
线程不安全
多线程是为了提高程序的使用率,然而会出现很多线程安全问题
因为在多线程的环境下,线程是交替执行的,一般他们会使用多个线程执行相同的代码。如果在此相同的代码里边有着共享的变量,或者一些组合操作,我们想要的正确结果就很容易出现了问题
共享变量问题
package com.company.Thread;
public class ThreadOfPool {
private static int count=1;
static class CreatThread1 extends Thread {
public void run(){
int counts =count ;
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
counts=counts+1;
System.out.println("线程1:"+counts);
}
}
static class CreatThread2 extends Thread {
public void run() {
count++;
System.out.println("线程2:"+count);
}
}
public int getCount(){
return count;
}
public static void main(String[] args) throws InterruptedException {
CreatThread1 creatThread1=new CreatThread1();
CreatThread2 creatThread2=new CreatThread2();
creatThread1.start();
creatThread2.start();
}
}
就是一个 读取-修改-写入 存在的问题
这里在读取后sleep了一秒,模拟count++的操作
按理两次相加应该为3,而这两个线程都显示2,就是线程1读取了共享变量,还没修改线程2就读取了共享变量,导致结果的不正常
对象的发布与逸出
发布(publish) 使对象能够在当前作用域之外的代码中使用
逸出(escape) 当某个不应该发布的对象被发布了
常见逸出的有下面几种方式:
- 静态域逸出
- public修饰的get方法
- 方法参数传递
- 隐式的this
静态域逸出
public static Set<Father> fatherset;
静态变量随着类加载而加载,存储在方法区,public修饰的静态域相对于发布了Father对象,没有初始化就可以使用Father对象了
public修饰get方法
public int getCount(){
return count;
}
其他类也可以通过getCount方法获得count变量
count变量超出了作用域
方法参数传递
作为一个方法的参数传递出去,逸出
public void setCount(int count){
this.count=count;
}
count作为参数逸出
隐式的this
this引用逸出是指,在类的构造方法中发布该类的对象,导致尚未构造完全的对象被其他线程访问。这样一来,不同的线程得到的可能是不同的结果,有些线程访问的是构造完全的对象,而另一些线程则会访问到尚未构造完全的对象,最终导致不可预测的错误
效率问题
使用多线程是为了提高程序运行效率
没有设计好反倒会降低效率甚至死锁
例如 web中无限制创建线程
线程竞争开销,上下文切换开销
还需要深入学习
Java多线程引发的性能问题以及调优策略
线程安全
线程的不安全大致可以看做在对象创建时和对象创建后。不仅仅是在对象创建后的业务逻辑中要考虑线程的安全性,在对象创建的过程中,也要考虑线程安全。
多线程带来的线程安全问题在Java中,我们一般会有下面这么几种办法来实现线程安全问题:
- 无状态(没有共享变量)
- 使用final使该引用变量不可变(如果该对象引用也引用了其他的对象,那么无论是发布或者使用时都需要加锁)
- 加锁(内置锁,显示Lock锁)
- 使用JDK为我们提供的类来实现线程安全(此部分的类就很多了)
原子性(就比如上面的count++操作,可以使用AtomicLong来实现原子性,那么在增加的时候就不会出差错了!)
容器(ConcurrentHashMap等等…)