举例:
public class Task { int num = 1; public void taskA(){ num+=200; } public void taskB(){ num/=2; } public void taskC(){ num+=600; } } class T1 extends Thread{ Task t; public T1(Task t){ this.t = t; } public void run(){ t.taskA(); } } class T2 extends Thread{ Task t; public T2(Task t){ this.t = t; } public void run(){ t.taskB(); } } class T3 extends Thread{ Task t; public T3(Task t){ this.t = t; } public void run(){ t.taskC(); } } class Test{ public static void main(String[] args) { Task t = new Task(); T1 t1 = new T1(t); T2 t2 = new T2(t); T3 t3 = new T3(t); t1.start(); t2.start(); t3.start(); try { t1.join(); t2.join(); t3.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("num:"+t.num); } }
在此次实验中,创作出了三个线程分别进行不同的计算,但是通过运行结果可知,最后算出来的结果出现了700以外的数字。证明了线程运行的顺序是不确定的、
因此,我们做出如下改进,保证在t1执行完成后,t2才可以执行,而后t3在前两个线程执行结束的前提下才可以执行。
public class Task { int num = 1; boolean flag1 = false; boolean flag2 = false; boolean flag3 = false; public void taskA(){ num+=200; flag1 = true; } public void taskB(){ num/=2; flag2 = true; } public void taskC(){ num+=600; flag3 = true; } } class T1 extends Thread{ Task t; public T1(Task t){ this.t = t; } public void run(){ while(true){ if(!t.flag2&&!t.flag3){ t.taskA(); break; } } } } class T2 extends Thread{ Task t; public T2(Task t){ this.t = t; } public void run(){ while(true){ if(t.flag1){ t.taskB(); break; } } } } class T3 extends Thread{ Task t; public T3(Task t){ this.t = t; } public void run(){ while(true){ if(t.flag2){ t.taskC(); break; } } } } class Test{ public static void main(String[] args) { Task t = new Task(); T1 t1 = new T1(t); T2 t2 = new T2(t); T3 t3 = new T3(t); t1.start(); t2.start(); t3.start(); try { t1.join(); t2.join(); t3.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("num:"+t.num); } }
为每一个线程设置了flag之后,如果前面一个线程的flag是false(代表前面一个线程没有执行结束),此时后面的线程不允许进行,但是为了防止前一个线程的flag是false而导致后一个线程的run方法直接跳过,我们将每一个run方法都设置为死循环,让其不停循环,直至上一个线程的flag变为true。
举例二:
public class Task2 extends Thread{ static int num=0; public void run(){ for(int i=0;i<10000;i++){ num++; } } } class Test2{ public static void main(String[] args) { Task2 task1 = new Task2(); Task2 task2 = new Task2(); Task2 task3 = new Task2(); task1.start(); task2.start(); task3.start(); try { task1.join(); task2.join(); task3.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("num:"+Task2.num); } }
在本次实验中,Task2的线程任务是负责将num的数值在原来的基础上加一万,按常理来说经历三个线程对同一个对象的操作应该可以将num从0添加到30000。但是实际上每次运行的结果基本上都不到30000,这是由于线程的安全性没有得到保障,在三个线程同时进行的过程中有一个堆内存(即所有线程的公共区域),而num存在于公共区域中,每个线程都可以将num读取到自己的线程内存中进行num++运算,但如果同时有两个线程读取到了相同数值的num,并且在自己的进程中对num同时进行叠加操作,那么会导致本应该执行两次num++的num值,却只执行了一次,所以最后num的值无法叠加到30000。为了解决多线程操作同一个资源所出现的资源重复被操作的线程安全问题,可以给资源“上锁”,如下所示:
public class Task2 extends Thread{ static int num=0; static Object object = new Object(); public void run(){ synchronized (object){ for(int i=0;i<10000;i++){ num++; } } } } class Test2{ public static void main(String[] args) { Task2 task1 = new Task2(); Task2 task2 = new Task2(); Task2 task3 = new Task2(); task1.start(); task2.start(); task3.start(); try { task1.join(); task2.join(); task3.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("num:"+Task2.num); } }
总结:当多个线程同时操作同一个资源的时候,会出现资源被重复进行同样的操作的情景,为了保证线程的安全性,我们可以利用Synchronized(){}代码块来给资源上锁,()中只可以写入对象,当代码块开始执行的时候,实现上锁,此时别的线程无法抢夺资源的操控权,当代码块执行结束的时候,其他线程会继续争夺资源的操控权。