前言
什么是时候会出现线程不安全?
多个线程同时访问同一个对象中一个全局实例变量,会出现"非线程安全",不同环境不同结果,接下来带大家分析各种同步与异步的问题。
提示:以下是本篇文章正文内容,下面案例可供参考
一、synchronized的使用?
1.0.1 实际变量非线程安全及解决
代码如下(示例):
服务层
public class Service {
private int num=0;
public void addI(String username){
try {
if (username.equals("a")){
num=100;
System.out.println("a set over!");
Thread.sleep(2000);
}else{
num=200;
System.out.println("b set over!");
}
System.out.println(username+" num="+num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程
public class ThreadA extends Thread{
private Service numRef;
public ThreadA(Service numRef){
super();
this.numRef=numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
public class ThreadB extends Thread{
private Service numRef;
public ThreadB(Service numRef){
super();
this.numRef=numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
测试类
public class Run {
public static void main(String[] args) {
Service service =new Service();
ThreadA a=new ThreadA(service);
a.start();
ThreadB b=new ThreadB(service);
b.start();
}
}
运行结果如下:
出现了 “非线程安全” 覆盖值的情况
解决1:
改变服务类,上一把synchronized锁
public class Service {
private int num=0;
synchronized public void addI(String username){
try {
if (username.equals("a")){
num=100;
System.out.println("a set over!");
Thread.sleep(2000);
}else{
num=200;
System.out.println("b set over!");
}
System.out.println(username+" num="+num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
线程进入时用synchronized声明的方法时就上锁,方法执行完成后自动解锁,那么下一个线程才会进入synchronized声明的方法里,实现同步运行,线程安全。
解决2:
改变服务类,将全局实例变量变成私有的
public class Service {
synchronized public void addI(String username){
int num=0;
try {
if (username.equals("a")){
num=100;
System.out.println("a set over!");
Thread.sleep(2000);
}else{
num=200;
System.out.println("b set over!");
}
System.out.println(username+" num="+num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
两个线程成异步执行自己改变自己的变量,互不影响
解决3:
不改变服务类,多创建一个对象
public class Run {
public static void main(String[] args) {
Service service =new Service();
Service service1=new Service();
ThreadA a=new ThreadA(service);
a.start();
ThreadB b=new ThreadB(service1);
b.start();
}
}
运行结果如下:
多个线程访问多个对象的实例变量,线程与对象一对一的关系,实现线程安全。
总结:多个线程对共享资源有写操作时,必须同步,非共享资源时,可以进行异步(保证线程安全)。
同步的单词为synchronized ,异步为asynchronized
1.0.2 当类中存在同步方法与非同步方法时(synchronized锁的是对象,而非方法)
代码如下(示例):
服务层
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("A end endTime="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void methodB(){
try {
System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("B end endTime="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程
public class ThreadA extends Thread{
private MyObject object;
public ThreadA(MyObject object){
super();
this.object=object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread{
private MyObject object;
public ThreadB(MyObject object){
super();
this.object=object;
}
@Override
public void run() {
super.run();
object.methodB();
}
}
运行类
public class Run {
public static void main(String[] args) {
MyObject object=new MyObject();
ThreadA a=new ThreadA(object);
a.setName("A");
ThreadB b=new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
运行结果如下:
synchronized锁的是对象,而非方法,所以当A线程执行,对象上锁,B线程等待,当A线程执行完毕,释放锁,B线程才能执行methodB方法,所以是同步执行。
当methodA带synchronized,而methodB不带synchronized的情况。
代码如下(示例):
只改变服务层,其他不变
服务层
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("begin methodA threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("A end endTime="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB(){
try {
System.out.println("begin methodB threadName="+Thread.currentThread().getName()+"--begin time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("B end endTime="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
A线程持有了对象锁,但是B线程可以以异步的方式访问非synchronized类型的方法。
1.0.3 synchronized锁重入
概念:自己可以再次获取自己内部的锁,支持继承的环境中。
代码如下(示例):
服务层
public class Service {
synchronized public void service1(){
try {
System.out.println("service1");
Thread.sleep(10000);
service2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void service2(){
try {
System.out.println("service2");
Thread.sleep(10000);
service3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void service3(){
try {
System.out.println("service3");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void service4(){
System.out.println("service4");
}
}
两个线程类
public class MyThread1 extends Thread{
private Service service;
public MyThread1(Service service){
this.service=service;
}
@Override
public void run() {
super.run();
service.service4();
}
}
public class MyThread2 extends Thread{
private Service service;
public MyThread2(Service service){
this.service=service;
}
@Override
public void run() {
super.run();
service.service1();
}
}
测试类
public class test1 {
public static void main(String[] args) {
Service service=new Service();
MyThread2 myThread=new MyThread2(service);
myThread.start();
MyThread1 myThread1=new MyThread1(service);
myThread1.start();
}
}
运行结果如下:
在一个 synchronized方法/块的内部调用本类其他的synchronized方法/块时,是永远可以得到锁的。
1.0.4 用同步代码块解决同步方法的弊端(运行效率)
用synchronized声明的方法
代码如下(示例):
服务类
public class Taak {
private String getData1;
private String getData2;
synchronized public void doLongTimeTack(){
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();
getData2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
两个线程类
public class MyThread1 extends Thread{
private Taak taak;
public MyThread1(Taak taak){
this.taak=taak;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1=System.currentTimeMillis();
taak.doLongTimeTack();
CommonUtils.endTime1=System.currentTimeMillis();
}
}
public class MyThread2 extends Thread{
private Taak taak;
public MyThread2(Taak taak){
this.taak=taak;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2=System.currentTimeMillis();
taak.doLongTimeTack();
CommonUtils.endTime2=System.currentTimeMillis();
}
}
测试类
public class test {
public static void main(String[] args) {
Taak taak=new Taak();
MyThread1 myThread1=new MyThread1(taak);
myThread1.start();
MyThread2 myThread2=new MyThread2(taak);
myThread2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long beginTime=CommonUtils.beginTime1;
if (CommonUtils.beginTime2<CommonUtils.beginTime1){
beginTime=CommonUtils.beginTime2;
}
long endTime=CommonUtils.endTime1;
if (CommonUtils.endTime2>CommonUtils.endTime1){
endTime=CommonUtils.endTime2;
}
System.out.println("耗时:"+((endTime-beginTime)/1000));
}
}
运行结果如下:
用synchronized修饰的方法,是同步执行,保证了线程安全,但是效率低下,解决此问题,运用synchronized代码块
解决:
改变服务类,使用同步代码块
public class Taak {
private String getData1;
private String getData2;
public void doLongTimeTack(){
try {
System.out.println("begin task");
Thread.sleep(3000);
String privateGetDate1="长时间处理任务从远程返回值1 threadName="+Thread.currentThread().getName();
String privateGetDate2="长时间处理任务从远程返回值2 threadName="+Thread.currentThread().getName();
synchronized (this){
getData1=privateGetDate1;
getData2=privateGetDate2;
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
当一个线程访问object的一个 synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized同步代码块,在保证线程安全的同时,同步异步相结合,实现大幅度提高运行效率。
1.0.5 synchronized代码块的同步性
在使用同步synchronized(this)代码块时要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问被阻塞,说明synchronized(this)使用的是同一个“锁”。
代码如下:
服务类
public class ObjectService {
public void serviceMethodA(){
try {
synchronized (this){
System.out.println("A begin time="+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time="+System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void serviceMethodB(){
synchronized (this){
System.out.println("B begin time="+System.currentTimeMillis());
System.out.println("B end time="+System.currentTimeMillis());
}
}
}
两个线程类
public class ThreadA extends Thread{
private ObjectService objectService;
public ThreadA(ObjectService objectService){
super();
this.objectService=objectService;
}
@Override
public void run() {
super.run();
objectService.serviceMethodA();
}
}
public class ThreadB extends Thread{
private ObjectService objectService;
public ThreadB(ObjectService objectService){
super();
this.objectService=objectService;
}
@Override
public void run() {
super.run();
objectService.serviceMethodB();
}
}
测试类
public class run {
public static void main(String[] args) {
ObjectService objectService=new ObjectService();
ThreadA threadA=new ThreadA(objectService);
threadA.setName("a");
threadA.start();
ThreadB threadB=new ThreadB(objectService);
threadA.setName("b");
threadB.start();
}
}
运行结果如下:
用synchronized同步代码块同步运行
当然如果将一个 synchronized(this)同步代码块换成用synchronized关键字修饰的方法,结果会变吗,答案是否定的,因为 synchronized(this)同步代码块将当前对象作为锁,使用synchronized关键字修饰的serviceMethodB()同步方法是将当前方法所在类的对象作为锁,都是一把锁,所以是同步运行,改变服务类。
代码如下:
服务类
public class ObjectService {
public void serviceMethodA(){
try {
synchronized (this){
System.out.println("A begin time="+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end time="+System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void serviceMethodB(){
System.out.println("B begin time="+System.currentTimeMillis());
System.out.println("B end time="+System.currentTimeMillis());
}
}
运行结果如下:
1.0.6 synchronized(非this对象)的使用
synchronized(非this对象 x)格式的写法是将x对象本身作为“对象监视器”
当不使用 synchronized(非this对象 x)时
代码如下(示例):
服务类
public class MyOneList {
private List list=new ArrayList();
synchronized public void add(String data){
list.add(data);
};
synchronized public int getSize(){
return list.size();
}
}
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
线程类
public class MyThread1 extends Thread{
private MyOneList list;
public MyThread1(MyOneList list){
this.list=list;
}
@Override
public void run() {
super.run();
MyService myService=new MyService();
myService.addServiceMethod(list,"A");
}
}
public class MyThread2 extends Thread{
private MyOneList list;
public MyThread2(MyOneList list){
this.list=list;
}
@Override
public void run() {
super.run();
MyService myService=new MyService();
myService.addServiceMethod(list,"B");
}
}
测试类
public class test1 {
public static void main(String[] args) {
Service service=new Service();
MyThread2 myThread=new MyThread2(service);
myThread.start();
MyThread1 myThread1=new MyThread1(service);
myThread1.start();
}
}
测试结果为listsize=2,因为是异步运行,第一个线程进入时延时了两秒,此时还没有进行add操作,第二个线程在延时过程中进入了该方法,所以就进行了两次add操作。
当使用使用 synchronized(非this对象 x)时。
更改MyService类
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try {
synchronized (list) {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
测试结果为listsize=1,因为synchronized(非this对象 x)是将x对象本身作为“对象监视器”,第一个线程进入时获得了锁,直到执行完add操作才释放锁,此时另一个线程进入该方法,if判断为false,实现了同步运行。
1.0.7 synchronized使用的3个结论
1.当多个线程同时执行synchronized(非this对象x){}同步代码块时呈同步效果
2.当其他线程执行x对象中synchronized同步方法时呈同步效果,
还可以将synchronized关键字应用在static静态方法上,就是对当前的*.java文件对应的class类的对象进行持锁。
当然在x对象中synchronized关键字应用在static和非static是异步执行的
在x对象中synchronized关键字应用在static静态方法和synchronized(x.class)是同步运行的,因为他们都是*.java文件对应的class类的对象进行持锁。
3.当其他线程执行x对象方法里面synchronized(this)同步代码块时也呈现同步效果
*注意:如果其他线程调用x对象中不加synchronized关键字的方法还是异步调用
二、多线程的死锁?
2.0.1 死锁
java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程中,“死锁”是必须避免的,因为这会造成线程的“假死”
代码如下(示例):
服务类:
public class DealThread implements Runnable{
public String username;
public Object lock1=new Object();
public Object lock2=new Object();
public void setFlag(String username){
this.username=username;
}
@Override
public void run() {
if (username.equals("a")){
synchronized (lock1){
try {
System.out.println("username="+username);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("按lock1---lock2代码顺序执行了");
}
}
}
if (username.equals("b")){
synchronized (lock2){
try {
System.out.println("username="+username);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("按lock2---lock1代码顺序执行了");
}
}
}
}
}
测试类
public class run {
public static void main(String[] args) {
try {
DealThread dealThread=new DealThread();
dealThread.setFlag("a");
Thread thread1=new Thread(dealThread);
thread1.start();
Thread.sleep(100);
dealThread.setFlag("b");
Thread thread2=new Thread(dealThread);
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
当setFlag赋值为a是第一个线程进入时获得lock1锁,延时100毫秒,赋值为b,第二个线程进入获取了lock2锁,lock1等待lock2锁,lock2等待lock1锁,自己持有自己的锁,又在等在对方的锁,造成假死,死锁。
三、并发的三大性质
1.原子性:指一组操作在执行时不能被打断,要么全部执行成功,要么全部执行失败,不能进行分割和中断。
2.可见性:A线程更改的数据B线程立马能看到。
3.有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。
3.0.1 使用volatile解决多线程的死循环(volatile的可见性)
代码如下(示例):
服务类
public class RunThread extends Thread{
private boolean isRunning=true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
super.run();
System.out.println("进入run了");
while (isRunning == true){ }
System.out.println("线程被停止了!");
}
}
}
}
测试类
public class run {
public static void main(String[] args) {
try {
RunThread thread=new RunThread();
thread.start();
thread.sleep(1000);
thread.setRunning(false);
System.out.println("已经赋值为false");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
为什么会造成死循环呢?因为在启动线程时,变量private boolean isRunning=true;分别存在于公共内存及线程的私有内存中,线程运行后一直在线程的私有变量中获取 isRunning,而代码 thread.setRunning(false);虽然被执行,跟新的却是公共内存中的isRuning变量变为false,操作的是2块内存地址的数据,所以一直是死循环状态
这个问题其实就是私有内存中的值和公共内存中的值不同步造成的,解决这样的问题就要使用volatile关键字了,他主要作用就是当前线程访问isRunning这个变量时,强制性从公共内存中进行取值
更改服务类
代码如下(示例):
服务类
public class RunThread extends Thread{
volatile private boolean isRunning=true;
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
super.run();
System.out.println("进入run了");
while (isRunning == true){ }
System.out.println("线程被停止了!");
}
}
}
}
运行结果如下:
通过使用volatile关键字,强制从公共内存中读取变量的值,增加了实例变量多个线程之间的可见性。
3.0.2 volatile的非原子性
非原子性是因为i++操作不是原子性的,会拆成三个步骤。
关键字volatile不支持i++运算原子性
代码如下(示例):
线程类
public class MyThread extends Thread{
volatile public static int count;
private static void addCount(){
for (int i = 0; i <100 ; i++) {
count++;
}
System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());
}
@Override
public void run() {
super.run();
addCount();
}
}
测试类
public class run {
public static void main(String[] args) {
MyThread[] t1=new MyThread[100];
for (int i = 0; i < 100; i++) {
t1[i]=new MyThread();
}
for (int i = 0; i <100 ; i++) {
t1[i].start();
}
}
}
运行结果如下:
运算结果不是10000,说明多线程环境下volatile public static int count++运算操作是非原子性的。
但是synchronized具有原子性和可见性
改变线程类
线程类
public class MyThread extends Thread{
public static int count;
synchronized private static void addCount(){
for (int i = 0; i <100 ; i++) {
count++;
}
System.out.println("count=" +count+" ----线程名称"+Thread.currentThread().getName());
}
@Override
public void run() {
super.run();
addCount();
}
}
运行结果如下:
3.0.3 使用Atomic原子类进行i++操作实现原子性
线程类
public class AddCountThread extends Thread{
private AtomicInteger count=new AtomicInteger(0);
@Override
public void run() {
super.run();
for (int i = 0; i <10000 ; i++) {
//原子上增加一个当前值
System.out.println(count.incrementAndGet());
}
}
}
测试类
public class Run {
public static void main(String[] args) {
AddCountThread countThread=new AddCountThread();
Thread t1=new Thread(countThread);
t1.start();
Thread t2=new Thread(countThread);
t2.start();
Thread t3=new Thread(countThread);
t3.start();
Thread t4=new Thread(countThread);
t4.start();
Thread t5=new Thread(countThread);
t5.start();
}
}
运行结果如下:
原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作的可用的类型,他可以在没有锁的情况下做到线程安全
3.0.4 总结
可见性:synchronized和volatile具有可见性。
原子性:使用synchronized具有原子性,使用Atomic原子类具有原子性
synchronized和volayile都禁止代码重排序
volatile和synchronized的使用场景是
1.当想要实现1个变量的值被更改时,让其他线程能取到最新值时,就要对变量使用volatile
2.如果多个线程对同一个对象中的同一个实例变量进行操作时,为了避免出现非线程安全,就要使用synchronized