自定义线程类中的实例变量针对其他线程有共享和不共享之分,这在多个线程之间进行交互时是一个很重要的技术点。
不共享数据的情况
下面通过一个示例来看下数据不共享的情况。
package com.vhqimk.thread;
/*
* 测试数据不共享的情况
*/
public class Test {
public static void main(String[] args) {
// 创建三个MyThread对象,并分别给线程命名
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
// 启动三个线程
a.start();
b.start();
c.start();
}
}
class MyThread extends Thread {
private int count = 5;
public MyThread(String name) {
super();
this.setName(name);// 设置线程名称
}
public void run() {
super.run();
while (count > 0) {
count--;
System.out.println("由" + Thread.currentThread().getName() + " 计算,count=" + count);
}
}
}
不共享数据运行结果如图所示。
图 1-1 不共享数据的运行结果
由图 1-1可以看到,一共创建了三个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例不存在多个线程访问同一个实例变量的情况。
2. 共享数据的情况
共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理一个人的票数。
下面通过一个示例来看下数据共享的情况。
package com.vhqimk.thread;
/*
* 测试数据共享的情况
*/
public class Test {
public static void main(String[] args) {
// 创建一个MyThread对象,并将该对象分别加载到五个线程中并分别给线程命名
MyThread myThread = new MyThread();
Thread a = new Thread(myThread, "A");
Thread b = new Thread(myThread, "B");
Thread c = new Thread(myThread, "C");
Thread d = new Thread(myThread, "D");
Thread e = new Thread(myThread, "E");
// 启动五个线程
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread {
private int count = 5;
public void run() {
super.run();
count--;
System.out.println("由" + Thread.currentThread().getName() + " 计算,count=" + count);
}
}
运行结果如图 1-2所示。
图 1-2 共享数据运行结果
从图 1-2中可以看到线程A和线程B打印出的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全”问题,而我们想要的结果却不是重复的,而是依次递减的。
非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况。若要解决,只需在需要线程同步执行的方法前加synchronized。下面是在方法前加上synchronized关键字解决非线程安全的问题示例。
package com.vhqimk.thread;
/*
* 测试数据共享的情况
*/
public class Test {
public static void main(String[] args) {
// 创建一个MyThread对象,并将该对象分别加载到五个线程中并分别给线程命名
MyThread myThread = new MyThread();
Thread a = new Thread(myThread, "A");
Thread b = new Thread(myThread, "B");
Thread c = new Thread(myThread, "C");
Thread d = new Thread(myThread, "D");
Thread e = new Thread(myThread, "E");
// 启动五个线程
a.start();
b.start();
c.start();
d.start();
e.start();
}
}
class MyThread extends Thread {
private int count = 5;
//在方法前加上synchronized,使得方法里的线程被同步执行
synchronized public void run() {
super.run();
count--;
System.out.println("由" + Thread.currentThread().getName() + " 计算,count=" + count);
}
}
线程同步运行结果如图 1-3所示。
图 1-3 方法调用被同步
总结:线程有共享数据和不共享数据之分,共享数据时容易出现“非线程安全”问题,可以用关键字synchronized 解决。synchronized 可以在任意对象和方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。当一个线程想要执行同步方法里的代码时,县城首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronized 里面的代码。如果不能拿到这把锁,那么这个线程就会不断尝试拿这把锁,直到能够拿到为止,而且是有多个线程同时去争抢这把锁。