实例变量
多线程中实例变量的两种情况:
- 不共享数据:每个线程访问各自的实例变量。
- 共享数据:多个线程访问同一个变量。
不共享数据
package com.myThread;
/**
* 线程继承类
* @author admin
*/
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);
}
}
}
package run;
import com.myThread.MyThread;
/**
* 运行代码
* @author admin
*/
public class Run {
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.start();
b.start();
c.start();
}
}
运行结果:
共创建3个线程,每个线程都有各自的 count 变量,各自处理自己的count变量值(根据Java实例的特性,每new一个MyThread实例,其实例地址都不相同,对应的a、b、c三个的线程处理的都是自己的实例里的数据 )。
处理流程如下图所示:
数据共享设计
package com.myThread;
/**
* 线程继承类
* @author admin
*/
public class MyThread2 extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
count--;
//此处不用for循环,因为使用同步后其他线程就得不到运行的机会,会一直由一个线程进行减法运算
System.out.println("由 "+ this.currentThread().getName() + " 计算,count=" +count);
}
}
package run;
import com.myThread.MyThread2;
/**
* 运行代码
* @author admin
*/
public class Run2 {
public static void main(String[] args) {
MyThread2 myThread = new MyThread2();
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();
}
}
运行结果:
多个线程处理一个实例变量,只new了一个MyThread2线程实例,在创建5个Thread线程来调用MyThread2 这个线程实例(根据Java实例的特性,new的一个MyThread2实例,不同的Thread实例调用的是同一个MyThread2实例,对应的a、b、c、d、e五个的线程处理的都是MyThread2实例里的数据) 。
线程安全
由上面数据共享的示例中引发了一个问题:“由 A 计算,count=3” 和 “由 B 计算,count=3” 两个输出结果发现,线程A和线程B输出的count值都是3,说明此时产生了“非线程安全”问题。
首先来分析下,在JVM中,i–的操作
- 取得原有i值。
- 计算i-1。
- 对i进行赋值。
由此可见,多线程同时访问,必然会出现“非线程安全”问题。
什么是非线程安全
非线程安全是指多个线程对一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
如何才能做到线程安全
可以通过加锁(synchronized)实现;在当前一个线程(抢到锁的线程)执行时,其他的线程不可执行在外进行排队,等到当前线程执行完后,再由下一个分配(多个线程争抢执行机会,即抢锁)到的线程进行执行。
synchronized可以在任意对象及方法上加锁,加锁的代码部分称 “互斥区” 或 “临界区”。
package com.myThread;
/**
* 线程继承类
* @author admin
*/
public class MyThread2 extends Thread{
private int count = 5;
@Override
synchronized public void run() {
super.run();
count--;
//此处不用for循环,因为使用同步后其他线程就得不到运行的机会,会一直由一个线程进行减法运算
System.out.println("由 "+ this.currentThread().getName() + " 计算,count=" +count);
}
}
package run;
import com.myThread.MyThread2;
/**
* 运行代码
* @author admin
*/
public class Run2 {
public static void main(String[] args) {
MyThread2 myThread = new MyThread2();
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();
}
}
通过在run 方法前加入 synchronized 关键字,使用多线程执行run 方法时,以排队的方式进行处理。
运行结果:
i --和System.out.println()引发的线程安全问题
package com.myThread;
/**
* 线程继承类
* @author admin
*/
public class MyThread2 extends Thread{
private int count = 5;
@Override
public void run() {
//注意:i--改到println() 方法中直接打印。
System.out.println("i=" + (i--) + " threadName=" + Thread.currentThread().getName());
}
}
package run;
import com.myThread.MyThread2;
/**
* 运行代码
* @author admin
*/
public class Run2 {
public static void main(String[] args) {
// 创建一个MyThread对象,并将该对象分别加载到五个线程中并分别给线程命名
MyThread2 myThread = new MyThread2();
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
Thread t4 = new Thread(myThread);
Thread t5 = new Thread(myThread);
// 启动五个线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
有几率出现非线程安全的问题
通过查看println() 方法的源码,可以看出是加锁同步的,但是 i --的操做是发生在进入println() 之前,所以会有产生非线程安全的几率。
为了防止发生非线程安全问题还应使用同步方法,即在run()方法前最前面加上synchronized。