实例变量的不共享
自定义线程类中的实例对于其他线程来说可以有共享与不共享之分,这是多线程的核心知识点之一。
不共享情景如下所示
创建代码 test5.java
下面通过一个示例看下数据的不共享情况,创建 test5.java 类,并输入如下代码:
class MyThread extends Thread{
private int count = 5;
public MyThread(String name){
super();
this.setName(name);
}
@Override
public void run(){
while(count > 0){
count --;
System.out.println(Thread.currentThread().getName() + ":.." + count);
}
}
}
public class test5{
public static void main(String[] args){
Thread myThread_A = new MyThread("A");
Thread myThread_B = new MyThread("B");
Thread myThread_C = new MyThread("C");
Thread myThread_D = new MyThread("D");
Thread myThread_E = new MyThread("E");
myThread_A.start();
myThread_B.start();
myThread_C.start();
myThread_D.start();
myThread_E.start();
}
}
class MyThread extends Thread{ private int count = 5; public MyThread(String name){ super(); this.setName(name); } @Override public void run(){ while(count > 0){ count —; System.out.println(Thread.currentThread().getName() + “:..” + count); } } } public class test5{ public static void main(String[] args){ Thread myThread_A = new MyThread(“A”); Thread myThread_B = new MyThread(“B”); Thread myThread_C = new MyThread(“C”); Thread myThread_D = new MyThread(“D”); Thread myThread_E = new MyThread(“E”); myThread_A.start(); myThread_B.start(); myThread_C.start(); myThread_D.start(); myThread_E.start(); } }
test5 运行结果
运行 test5 实例数据不共享情况的测试之后,输出结果如下所示:
可以看到,一共创建了 5 个线程,每个线程都有各自的 count 变量,自己减少自己的 count 值,这样的情况便是实例变量不共享,此示例不存在多个线程访问同一个变量的情况。
实例变量的共享
共享情景如下所示
创建代码 test6.java
下面通过一个示例看下数据的共享情况,创建 test6.java 类,并输入如下代码:
class MyThread extends Thread{
private int count = 5;
@Override
public void run(){
count --;
System.out.println(Thread.currentThread().getName() + ":.." + count);
}
}
public class test6{
public static void main(String[] args){
MyThread myThread = new MyThread();
Thread thread_A = new Thread(myThread,"A");
Thread thread_B = new Thread(myThread,"B");
Thread thread_C = new Thread(myThread,"C");
Thread thread_D = new Thread(myThread,"D");
Thread thread_E = new Thread(myThread,"E");
thread_A.start();
thread_B.start();
thread_C.start();
thread_D.start();
thread_E.start();
}
}
class MyThread extends Thread{ private int count = 5; @Override public void run(){ count —; System.out.println(Thread.currentThread().getName() + “:..” + count); } } public class test6{ public static void main(String[] args){ MyThread myThread = new MyThread(); Thread thread_A = new Thread(myThread,”A”); Thread thread_B = new Thread(myThread,”B”); Thread thread_C = new Thread(myThread,”C”); Thread thread_D = new Thread(myThread,”D”); Thread thread_E = new Thread(myThread,”E”); thread_A.start(); thread_B.start(); thread_C.start(); thread_D.start(); thread_E.start(); } }
test6 运行结果
第一次运行 test6 测试结果如下所示
如图所示我们所创建的 5 个线程,分别对 count 数值进行了 — 操作,此时这 5 个线程即为共享实例变量。
第二次运行 test6 测试结果如下所示
因为多线程之间是不断争夺 CPU 资源的,所以会导致上面这种不安全的情况产生。每一次多线程的测试结果都不会相同,但是此时由于我们新建的线程数量太少,效果还不太明显,具体可看下面 test7 的例子。
感受实例变量共享的安全问题
从 test6 的测试结果我们可以初步看出,多线程在并发执行的时候,可能会重复删除数据、或未删除数据,等等情况产生,本小节将对多线程实例变量共享的安全问题进行放大,让读者更清晰的感受。
创建代码 test7.java
test7 中将 count 改为 50,并且用循环的方式连续创建了 30 个线程。
class MyThread extends Thread{
private int count = 50;
@Override
public void run(){
count --;
System.out.println(Thread.currentThread().getName() + " :.." + count);
}
}
public class test7{
public static void main(String[] args){
MyThread myThread = new MyThread();
for(int i = 0; i < 30; i++){
Thread thread = new Thread(myThread, "i+" + i);
thread.start();
}
}
}
class MyThread extends Thread{ private int count = 50; @Override public void run(){ count —; System.out.println(Thread.currentThread().getName() + “ :..” + count); } } public class test7{ public static void main(String[] args){ MyThread myThread = new MyThread(); for(int i = 0; i < 30; i++){ Thread thread = new Thread(myThread, “i+” + i); thread.start(); } } }
test7 运行结果
第一次运行 test7 测试结果如下所示
第二次运行 test7 测试结果如下所示
test7 第二次运行的时候最后结果是 21.
36 35 34 33 34 32 31 这几个数值出现的时候,线程产生了混乱,已经没办法正确 -1 了。
下一步
初次解决实例变量共享的安全问题
我们将利用 synchronized 关键字来初次解决实例变量共享的安全问题。
初步认识 synchronized 关键字
在 test8 的测试之中,通过在 run 方法前加入 synchronized 关键字,使多个线程在执行 run 方法时,以排队的方式进行处理。
当一个线程想要执行同步方法里面的代码之前,会先判断 run 方法有没有被上锁,如果被上锁,说明有其他线程正在调用 run 方法,必须等其它线程对 run 方法调用结束后才可以执行 run 方法。这样也就实现了排队调用 run 方法的目的,也就达到了解决实例变量共享的安全问题的目的。
synchronized 可以在任意对象及方法上加锁,而加锁的这段代码被称之为“互斥区”或“临界区”。
创建代码 test8.java
class MyThread extends Thread{
private int count = 50;
@Override
synchronized public void run(){
count --;
System.out.println(Thread.currentThread().getName() + " :.." + count);
}
}
public class test8{
public static void main(String[] args){
MyThread myThread = new MyThread();
for(int i = 0; i < 30; i++){
Thread thread = new Thread(myThread, "i+" + i);
thread.start();
}
}
}
class MyThread extends Thread{ private int count = 50; @Override synchronized public void run(){ count —; System.out.println(Thread.currentThread().getName() + “ :..” + count); } } public class test8{ public static void main(String[] args){ MyThread myThread = new MyThread(); for(int i = 0; i < 30; i++){ Thread thread = new Thread(myThread, “i+” + i); thread.start(); } } }