线程间数据共享
并发运行:
线程中并发指一个时间段中多个线程都处于已启动但没有运行结束的状态。
多个线程之间默认并发运行,这种运行方式往往会出现交叉的情况。
如下例子:
串行运行:
使原本并发运行的多个线程实现串行运行,即多线程间同步执行,需要通过对象锁机制来实现,synchronized就是一个利用锁实现线程同步的关键字。
默认情况下,线程之间是交叉进行的(并发运行),下边用synchronized关键字实现线程之间数据共享,依次执行。
1,
public class T {
public static void main(String[] args) {
Object lock=new Object();//数据共享
new CountThread1("线性1",lock).start();
new CountThread1("线性2",lock).start();//记得就绪,调用start方法。默认情况下,线程之间交叉进行。
}
}
class CountThread1 extends Thread{
Object lock;
CountThread1(String name,Object lock){
super(name);
this.lock=lock;
}
@Override
public void run() {
System.out.println(getName()+"~~~~~~~~~~");
synchronized (lock) {//synchronized为对象锁,添加了此行代码之后,将锁定包围在里边的代码,只有锁定该代码的线程可以执行里边代码。之外的代码,执行可以交叉
for (int i = 0; i < 5; i++) {
System.out.println(getName() + "..." + i);//getName()是定义在父类中的方法。
}
}
}
}
输出如下
对lock的说明:
synchronized对象锁:该对象锁是Java中创建的一个对象,该对象可由任意类创建,只要求所创建的对象在多个线程之间共享即可。如果对象锁为全局成员,为了保证该对象在多个线程间共享,该成员往往被private static final修饰。
为什么通过synchronized就能实现多线程间串行运行呢?
被synchronized括着的部分就是线程执行临界区,每次仅能有一个线程执行该临界区中的代码:当多个线程中的某个线程先拿到对象锁, 则该线程执行临界区内的代码,其他线程只能在临界区外部等待,当此线程执行完临界区中的代码后,在临界区外部等待的其他线程开始再次竞争以获取对象锁,进而执行临界区中的代码,但只能有一条线程“胜利”。
临界区中的代码具有互斥性、唯一性和排它性:一个线程只有执行完临界区中的代码另一个线程才能执行。
2,
public class Text {
public static void main(String[] args) {
new CountThread1("线程1").start();
new CountThread1("线程2").start();//记得就绪,调用start方法。默认情况下,线程之间交叉进行。
}
}
class CountThread1 extends Thread{
CountThread1(String n){
super(n);
}
String name;//静态的,在对象之间共享,同一个数据。
@Override
public void run() {
name=getName();//某一个线程对象改变名字之后,另一个线程直接拿来用。
System.out.println(name+"~~~~~~~~~~");
synchronized (String.class) {
for (int i = 0; i < 5; i++) {
System.out.println(name+ "..." + i);
}
}
}
}
可能结果如下:
下边为该例子的变体
public class Text {
public static void main(String[] args) {
new CountThread1("线程1").start();
new CountThread1("线程2").start();//记得就绪,调用start方法。默认情况下,线程之间交叉进行。
}
}
class CountThread1 extends Thread{
CountThread1(String n){
super(n);
}
@Override
public void run() { //非静态的,线程对象调用。
test();
}
void test() {
synchronized (String.class) {//String.class是私有的构造方法,单例。数据共享,不交叉。
for (int i = 0; i < 5; i++) {
System.out.println(getName()+"..." + i);
}
}
}
}
synchronized关键字
synchronized同步关键字有两种使用方式:
1,声明同步方法:
public synchronized void methodName( ){
//同步操作方法体
}
例子:
public class Playground {
public static void main(String[] args) {
Bike bike = new Bike();
PersonThread xiaoLin =new PersonThread("小林",bike);
xiaoLin.start();
PersonThread xiaoWang =new PersonThread("小王",bike);
xiaoWang.start();
}
}
class PersonThread extends Thread{
Bike bike;
String name;
PersonThread(String name, Bike bike){
super(name);
this.name = name;
this.bike = bike;
}
@Override
public void run() {//run方法执行表示正在骑车
bike.move(name);
}
}
class Bike{
public synchronized void move(String personName){ //若为非静态同步方法,则多线程间共享调用该方法的对象;若为静态同步方法,则多线程之间共享调用该方法的类;
String threadName = Thread.currentThread().getName();
System.out.println("当前线程:"+threadName);//判断当前哪条线程在执行该方法
for (int i = 1; i <= 3; i++) {
System.out.println(personName+":已经运行"+i+"秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2,同步代码块
synchronized (需要同步操作的对象) {
//同步对象操作的语句
}
如下例子:
public class Playground {
public static void main(String[] args) {
PersonThread xiaoLin =new PersonThread("小林");
xiaoLin.start();
PersonThread xiaoWang =new PersonThread("小王");
xiaoWang.start();
}
}
class PersonThread extends Thread{
String name;
PersonThread(String name){
this.name = name;
}
@Override
public void run() {//run方法执行表示正在骑车
Bike.move(name);
}
}
class Bike{
public static void move(String personName){
String threadName = Thread.currentThread().getName();
synchronized(Bike.class){
System.out.println("当前线程:"+threadName);//判断当前哪条线程在执行该方法
for (int i = 1; i <= 3; i++) {
System.out.println(personName+":已经运行"+i+"秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程间协作
用以下例子说明线程间的协作
import java.util.Date;
public class Watch {
String time;
class DisplayThread extends Thread{//内部类:显示时间
TimeThread timeThread;
public DisplayThread(TimeThread timeThread) {
this.timeThread=timeThread;
}
@Override
public void run() {
System.out.println(time);//直接显示时间,极大可能为null;
}
}
class TimeThread extends Thread{//计算时间
@Override
public void run() {
time=new Date().toString();//创建时间对象
}
}
public static void main(String[] args) {
Watch watch=new Watch();
TimeThread timeThread=watch.new TimeThread();//内部类创建对象方法。理想状态是先计算时间,再显示时间,线程之间的协作。
timeThread.start();
DisplayThread displayThread=watch.new DisplayThread(timeThread);
displayThread.start();
}
}
上述代码执行过程中,可能是显示器线程抢先于时间线程,所以会在没有得到时间的时候就输出,结果为null。
下面我们考虑实现总是可以显示时间
方法一:sleep()方法的使用
import java.util.Date;
public class Watch {
String time;
class DisplayThread extends Thread{//内部类
TimeThread timeThread;
public DisplayThread(TimeThread timeThread) {
this.timeThread=timeThread;
}
@Override
public void run() {
if(time==null) { //先来判断显示器线程此时要显示的时间是否为空
try {
sleep(100);//此时设置显示器线程阻塞一定的时间,理论上在阻塞期间应该是时间线程在执行,设置合理的阻塞时间,一定也会有时间显示。此法性能不高。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);//直接显示时间,极大可能为null;
}
}
class TimeThread extends Thread{
@Override
public void run() {
time=new Date().toString();//创建时间对象
}
}
public static void main(String[] args) {
Watch watch=new Watch();
TimeThread timeThread=watch.new TimeThread();//内部类创建对象方法。理想状态是先计算时间,再显示时间,线程之间的协作。
timeThread.start();
DisplayThread displayThread=watch.new DisplayThread(timeThread);
displayThread.start();
}
}
一定会显示时间。
方法二:join()方法的使用
import java.util.Date;
public class Watch {
String time;
class DisplayThread extends Thread{//内部类
TimeThread timeThread;
public DisplayThread(TimeThread timeThread) {
this.timeThread=timeThread;
}
@Override
public void run() {
if(time==null) { //先来判断显示器线程此时要显示的时间是否为空
try {//方法2:join
timeThread.join();//执行join方法的显示器线程阻塞,一直到调用join方法的时间线程执行完毕。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);
}
}
class TimeThread extends Thread{
@Override
public void run() {
time=new Date().toString();//创建时间对象
}
}
public static void main(String[] args) {
Watch watch=new Watch();
TimeThread timeThread=watch.new TimeThread();//内部类创建对象方法。理想状态是先计算时间,再显示时间,线程之间的协作。
timeThread.start();
DisplayThread displayThread=watch.new DisplayThread(timeThread);
displayThread.start();
}
}
一定有时间显示。
方法三:线程之间的协作——wait()方法与notify()方法的使用
import java.util.Date;
public class Watch {
String time;
String lock="";
class DisplayThread extends Thread{//内部类
TimeThread timeThread;
public DisplayThread(TimeThread timeThread) {
this.timeThread=timeThread;
}
@Override
public void run() {
synchronized (lock) {
try {//方法3:线程协作
lock.wait();//wait方法源自于object方法,执行该方法的线程会阻塞,1,共享对象调用wait方法,2,该方法必须用synchronized语句块,不然出异常,且锁定的对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);
}
}
class TimeThread extends Thread{
@Override
public void run() {
time=new Date().toString();//创建时间对象
synchronized (lock) {//四个lock是同一个对象。
lock.notify();
}
}
}
public static void main(String[] args) {
Watch watch=new Watch();
TimeThread timeThread=watch.new TimeThread();//内部类创建对象方法。理想状态是先计算时间,再显示时间,线程之间的协作。
timeThread.start();
DisplayThread displayThread=watch.new DisplayThread(timeThread);
displayThread.start();
}
}
说明:
synchronized关键字只是起到了多个线程“串行”执行临界区中代码的作用,但是哪个线程先执行,哪个线程后执行依无法确定,Object类中的wait()、notify()和notifyAll()三个方法解决了线程间的协作问题,通过这三个方法的“合理”使用可以确定多线程中线程的先后执行顺序:
wait():对象锁调用了wait()方法会使当前持有该对象锁的线程【也就是说该线程执行“对象锁.wait()”代码!】处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。
notify():对象锁调用notify()方法就会唤醒在此对象锁上等待的单个线程。
notifyAll():对象锁调用notifyAll()方法就会唤醒在此对象锁上等待的所有线程;调用notifyAll()方法并不会立即激活某个等待线程,它只能撤销等待线程的中断状态,这样它们就能够在当前线程退出同步方法或同步代码块法后与其它线程展开竞争,以争取获得资源对象来执行。
总之:谁调用了wait方法,谁就必须调用notify或notifyAll方法,并且“谁”是对象锁。
注意:
使用Object类中的wait()、notify()和notifyAll()三个方法需要注意以下几点:
1,wait()方法需要和notify()或notifyAll()方法中的一个配对使用,且wait方法与notify()或notifyAll()方法配对使用时不能在同一个线程中。
如下例子
public class Watch {
public static void main(String[] args) {
Object lockObj = new Object();
new CounterThread(lockObj).start();
}
}
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {
try {
System.out.println("wait方法被执行!");
lockObj.wait();//对此程序而言,下面代码永远不会被执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lockObj) {
System.out.println("notify方法被执行!");
lockObj.notify();
}
}
}
在上述代码中,notify方法永远不会被执行。
2,wait()方法、notify()方法和notifyAll()方法必须在同步方法或者同步代码块中使用,否则出现IllegalMonitorStateException 异常。
import java.util.Date;
public class Watch {
String time;
String lock=""; //此处对象可以是
class DisplayThread extends Thread{//内部类
TimeThread timeThread;
public DisplayThread(TimeThread timeThread) {
this.timeThread=timeThread;
}
@Override
public void run() {
try {//方法3:线程协作
lock.wait();//wait方法源自于object方法,执行该方法的线程会阻塞,1,共享对象调用wait方法,2,该方法必须用synchronized语句块,不然出异常,且锁定的对象
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(time);
}
}
class TimeThread extends Thread{
@Override
public void run() {
time=new Date().toString();//创建时间对象
lock.notify();
}
}
public static void main(String[] args) {
Watch watch=new Watch();
TimeThread timeThread=watch.new TimeThread();//内部类创建对象方法。理想状态是先计算时间,再显示时间,线程之间的协作。
timeThread.start();
DisplayThread displayThread=watch.new DisplayThread(timeThread);
displayThread.start();
}
}
3,调用wait()方法、notify()方法和notifyAll()方法的对象必须和同步锁对象是一个对象。,
import java.text.SimpleDateFormat;
import java.util.Date;
public class ElectronicWatch {
String currentTime;
DisplayThread displayThread = new DisplayThread();
public static void main(String[] args) {
new ElectronicWatch().displayThread.start();
}
/**
* 该线程负责显示时间
*/
class DisplayThread extends Thread{
@Override
public synchronized void run() {
new TimeThread().start();
try {
this.wait();//this指代当前对象,为DisplayThread对象。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentTime);
}
}
/**
* 该线程负责获取时间
*/
class TimeThread extends Thread{
@Override
public synchronized void run() {
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
this.notify();////this指代当前对象,为TimeThread对象。
}
}
}
this不是同一个对象,上述代码不执行
sleep()方法和wait()方法区别
1,sleep()方法被调用后当前线程进入阻塞状态,但是当前线程仍然持有对象锁,在当前线程sleep期间,其它线程无法执行sleep方法所在临界区中的代码。
public class ElectronicWatch {
public static void main(String[] args) {
Object lockObj = new Object();
new PrintThread("1号打印机",lockObj).start();
new PrintThread("2号打印机",lockObj).start();
}
}
class PrintThread extends Thread {
private Object lockObj;
public PrintThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {//当某台打印机执行临界区中的代码,输出线程名后由于调用了sleep方法,从而使得该打印机线程阻塞30秒,在这30秒期间因该打印机线程仍然持有对象锁,从而导致另一台打印机线程只能在30秒后才能执行临界区中的代码。
System.out.println(getName());
try {
sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出:控制台先输出一号打印机,隔三十秒之后再输出二号打印机。
2,对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,在当前线程处于线程等待期间,其它线程可以执行wait方法所在临界区中的代码。
public class ElectronicWatch {
public static void main(String[] args) {
Object lockObj = new Object();
new PrintThread("1号打印机",lockObj).start();
new PrintThread("2号打印机",lockObj).start();
}
}
class PrintThread extends Thread {
private Object lockObj;
public PrintThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {
System.out.println(getName());
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
代码分析:
当某台打印机执行临界区中的代码,输出线程名后由于对象锁调用了wait方法,从而使得该打印机线程进入阻塞状态,该打印机线程同时释放对对象锁的拥有权,从而导致另一台打印机线程可以在前一台打印机线程阻塞期间即能执行临界区中的代码,当对象锁再次调用wait方法后另一台打印机线程也会进入阻塞状态。
wait方法
例1:
public class ElectronicWatch {
public static void main(String[] args) {
Object lockObj = new Object();
new CounterThread("1号计数器",lockObj).start();
new CounterThread("2号计数器",lockObj).start();
}
}
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
int i = 1;
while (true) {
synchronized (lockObj) {
System.out.println(getName() + ":" + i);
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
}
代码分析:
尽管此处是死循环,但由于对象锁lockObj调用了wait()方法,使得分别持有该lockObj对象锁的“1号计数器”线程和“2号计数器”线程处于线程等待状态,所以循环并没有继续下去。
例2:
import java.text.*;
import java.util.Date;
public class ElectronicWatch {
public static void main(String[] args) {
TimeThread1 timeThread = new TimeThread1 ();
timeThread.start();
synchronized (timeThread) {
try {
timeThread.wait();//为什么时间线程没有进入阻塞状态呢?——timeThread变量所代表的《对象【充当】对象锁》,由于该代码在main方法中,也就是说将来由主线程执行临界区中的代码,也就是说《主线程是持有对象锁的线程》,主线程执行完“timeThread.wait()”代码后进入阻塞状态,是主线程进入阻塞状态而非时间线程,这也是main方法中“System.out.println("main方法");”代码不执行的原因。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main方法");//为什么这行代码不执行?——由于主线程进入了阻塞状态,所以该行代码不执行
}
}
class TimeThread1 extends Thread{
@Override
public void run() {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
while (true) {
String currentTime = dateFormat.format(new Date());
System.out.println(currentTime);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
notifyAll方法
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
int i = 1;
while (true) {
synchronized (lockObj) {
System.out.println(getName() + ":" + i);
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
}
class NotifyAllThread extends Thread {
private Object lockObj;
public NotifyAllThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {
System.out.println("notifyAll方法执行完毕");
lockObj.notifyAll();
}
}
}
public class Test {
public static void main(String[] args) {
Object lockObj = new Object();
new CounterThread("1号计数器", lockObj).start();
new CounterThread("2号计数器", lockObj).start();
try {
Thread.sleep(5000);//主线程阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
new NotifyAllThread(lockObj).start();
}
}
代码分析:
尽管此处是死循环, “1号计数器”线程和“2号计数器”线程输出1后对象锁lockObj调用了wait()方法,使得两个线程进入线程等待状态;但主线程在sleep 5000毫秒后启动了NotifyAllThread线程类创建的线程对象,该线程对象在执行run方法时对象锁调用了notifyAll方法,致使两个线程全部被唤醒,所以两个线程又循环了一遍。