自定义线程类中的实例变量针对其他线程可以有共享和不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。
1. 不共享数据的情况:
public class MyThread extends Thread {
private int count = 5;
public MyThread (String name) {
super();
this.setName(name); //设置线程名称
}
@Override
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由 " + this.currentThread().getName() + " 计算,count = " + count);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread a = new MyThread("AAA");
MyThread b = new MyThread("BBB");
MyThread c = new MyThread("CCC");
a.start();
b.start();
c.start();
/*
运行结果:三个线程各自操作自己的count变量
由 CCC 计算,count = 4
由 CCC 计算,count = 3
由 CCC 计算,count = 2
由 CCC 计算,count = 1
由 CCC 计算,count = 0
由 BBB 计算,count = 4
由 BBB 计算,count = 3
由 BBB 计算,count = 2
由 BBB 计算,count = 1
由 BBB 计算,count = 0
由 AAA 计算,count = 4
由 AAA 计算,count = 3
由 AAA 计算,count = 2
由 AAA 计算,count = 1
由 AAA 计算,count = 0
*/
}
}
2. 共享数据的情况:
public class MyThread2 extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
//不使用 while 循环,因为使用后其他线程得不到运行的机会,一直由一个线程进行
count--;
System.out.println("由 " + this.currentThread().getName() + " 计算,count = " + count);
}
}
那么如何避免上述的非线程安全问题?public class Test2 { public static void main(String[] args) { MyThread2 mt = new MyThread2(); Thread a = new Thread(mt,"AAA"); Thread b = new Thread(mt,"BBB"); Thread c = new Thread(mt,"CCC"); Thread d = new Thread(mt,"DDD"); Thread e = new Thread(mt,"EEE"); a.start(); b.start(); c.start(); d.start(); e.start(); /* 运行结果:三个线程同时对 count 进行处理,产生非线程安全问题 由 AAA 计算,count = 2 由 DDD 计算,count = 1 由 BBB 计算,count = 2 由 EEE 计算,count = 2 由 CCC 计算,count = 0 */ } }
非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
使用 synchronize 关键字
在 run 方法前加上 synchronized 关键字,当一个线程调用 run 前,先判断 run 方法有没有被上锁,如果上锁说明有其他线程正在调用 run 方法,必须等其他线程对 run 方法调用结束后才可以执行 run 方法。这个线程会不断地尝试拿这把锁,知道能够拿到为止,那么就会出现多个线程同时去争这把锁。synchronized 可以再任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。public class ThreadSafe extends Thread { private int count = 5; @Override public synchronized void run() { super.run(); //不使用 while 循环,因为使用后其他线程得不到运行的机会,一直由一个线程进行 count--; System.out.println("由 " + this.currentThread().getName() + " 计算,count = " + count); } }
3. i-- 与 System.out.printlin() 的异常
上面说到解决非线程安全问题使用的是 synchronized 关键字,那么我们看下面这个例子:
public class PrintlnThread extends Thread { private int i = 5; @Override public void run() { System.out.println("i = " + (i--) + " threadName = " + Thread.currentThread().getName()); } }
虽然 println() 方法在内部是同步的,但是 i-- 的操作是在进入 printlin() 之前发生的,所以有非线程安全问题的概率。为避免这个问题,还是需要使用同步方法。public class TestPrintlnThread { public static void main(String[] args) { PrintlnThread pt = new PrintlnThread(); Thread t1 = new Thread(pt); Thread t2 = new Thread(pt); Thread t3 = new Thread(pt); Thread t4 = new Thread(pt); Thread t5 = new Thread(pt); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); /* 运行结果: i = 5 threadName = Thread-2 i = 1 threadName = Thread-1 i = 2 threadName = Thread-4 i = 4 threadName = Thread-5 i = 3 threadName = Thread-3 */ } }