什么叫线程间的数据共享?如何实现线程间的数据共享?

一、为什么要实现线程间的数据共享

1.线程运行方式默认为并行运发

线程并发是指同一个时间段多个线程都处于已经启动但是没有结束运行的状态
多个线程之间默认的运行方式是并发运行,而且存在交叉的情况

public class Test {

	public static void main(String[] args) {
		new CounterThread("线程1").start();
		new CounterThread("线程2").start();
	}
}

class CounterThread extends Thread{

	public CounterThread(String threadName){
		super(threadName);
	}
	
	@Override
	public void run() {
		for(int i=0;i<3;i++){
			System.out.println(getName()+" : "+i);
		}
	}
}

运行结果
在这里插入图片描述
原因
线程之间运行是抢占式的,谁抢到了CPU的使用权谁就执行
所以多个线程都处于就绪状态时
如果线程一先抢到了CPU使用权先输出
线程一还没运行结束线程二就从它手里把CPU的使用权抢过来运行输出
这么抢占来抢占去就导致了多个线程可能同时处于运行状态,但是都没有运行结束

2.什么是串行运行?

使原本并发运行的多个线程实现串行运行,即多线程间同步执行,需要通过对象锁机制来实现,synchronized就是一个利用锁实现线程同步的关键字

public class Test {
	
	private static Object shareData = new Object();//多线程间共享的数据,定义一个任意类型的普通变量即可

	public static void main(String[] args) {
		new CounterThread("线程1",shareData).start();
		new CounterThread("线程2",shareData).start();
	}
}

class CounterThread extends Thread{

	private Object shareData;
	
	public CounterThread(String threadName,Object shareData){
		super(threadName);//给线程命名
		this.shareData = shareData;//把全局变量shareData赋值为多线程间 的共享数据
	}
	
	@Override
	public void run() {
		synchronized (shareData) {//谁获得了锁谁就必须执行完大括号内的代码才能释放锁,等待下一个线程获得锁执行程序
			for (int i = 0; i < 3; i++) {
				System.out.println(getName() + " : " + i);
			}
		}
	}
}

执行结果
在这里插入图片描述
synchronized对象锁:该对象锁是Java中创建的一个对象,该对象可由任意类创建,只要求所创建的对象在多个线程之间共享即可。如果对象锁为全局成员,为了保证该对象在多个线程间共享,该成员往往被private static final修饰

通过synchronized关键字实现了线程串行运行:一个线程执行完synchronized 代码块后另一个线程才执行,但是哪个线程先执行synchronized 代码块中的代码无法确定

二、如何实现线程间的数据共享(多线程同步原理)

1.为什么通过synchronized就能实现多线程间串行运行呢?

①只能有一个线程位于临界区

被synchronized括着的部分就是线程执行临界区,每次仅能有一个线程执行该临界区中的代码:当多个线程中的某个线程先拿到对象锁, 则该线程执行临界区内的代码(相当于临界区内的代码就被锁起来了,其他线程进不去执行,里面的线程执行不完也出不来),其他线程只能在临界区外部等待,当此线程执行完临界区中的代码后,在临界区外部等待的其他线程开始再次竞争以获取对象锁,进而执行临界区中的代码

②临界区中的代码具有互斥性、唯一性和排它性:

一个线程只有执行完临界区中的代码另一个线程才能执行(但是具体哪一个线程能够先执行临界区的代码不能确定)

import java.text.*;
import java.util.Date;

public class Test {

	public static void main(String[] args) {//主线程
		Object lockObj = new Object();//多线程间共享数据
		new DisplayThread(lockObj).start();//显示器线程处于就绪状态并把共享数据传入
		//显示器线程进入运行状态
	}
}

class DisplayThread extends Thread {

	Object lockObj;

	public DisplayThread(Object lockObj) {
		this.lockObj = lockObj;
	}

	@Override
	public void run() {
		synchronized (lockObj) {//显示器线程获得了共享数据,开始执行代码
			new TimeThread(lockObj).start();//显示器线程处于就绪状态
			try {
				sleep(60000);//显示器线程进入阻塞状态,这里时间线程从显示器线程那里将CPU资源抢过来,从就绪状态进入运行状态,获得共享数据执行run方法
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class TimeThread extends Thread {

	Object lockObj;

	public TimeThread(Object lockObj) {
		this.lockObj = lockObj;
	}

	@Override
	public void run() {
		System.out.println("时间线程开始执行......");//时间线程执行完该行代码后,由于显示器线程还没有执行完synchronized代码块里的方法,时间线程不会继续执行下去
		synchronized (lockObj) {//60s后等显示器线程从阻塞状态进入就绪状态,显示器线程释放了锁,时间线程获得了共享对象,执行synchronized代码块里的方法
			DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
			String time = dateFormat.format(new Date());
			System.out.println(time);
		}
	}
}

执行结果
在这里插入图片描述
①显示器线程和时间线程共享lockObj(对象锁)对象
②显示器线程优先进入启动状态,随后执行相应的run方法
③当执行同步代码块时lockObj变量所代表的对象锁归显示器线程所有,
④进而创建时间线程并使之处于启动状态,此时有一下两种状态:
1、时间线程马上进入执行状态,
马上执行该时间线程run方法,可是由于此时lockObj变量所代表的对象锁被显示器线程持有,
这时时间线程进入阻塞状态,显示器线程再次执行,然后执行sleep方法,显示器线程在继续持有对象锁的前提下
也进入阻塞状态,60秒后显示器线程进入执行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行;
2、如果时间线程并没有马上进入执行状态,显示器线程执行sleep方法,显示器线程在继续持有对象锁的前提下
也进入阻塞状态,此时时间线程进入执行状态,执行该时间线程run方法,执行该方法中第一行输出代码,
可是由于此时lockObj变量所代表的对象锁被显示器线程持有,
所以时间线程并没有执行时间线程run方法内临界区中的代码,这时时间线程也进入阻塞状态,此时显示器和时间两条线程均进去阻塞状态,
等待少于60秒的时间后,显示器线程进入运行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行

2.误区:“共享数据”是一个表达式相同,内容不同的非共享数据

package multithreading;

public class Test {
	
	public static void main(String[] args) {
		new CounterThread("线程1").start();//start方法就绪后调用run方法
		new CounterThread("线程2").start();
		//这两个对象都拥有一个独立的对象锁,二者不相同,因此无法实现数据共享
	}
}

class CounterThread extends Thread{

	public CounterThread(String threadName){
		super(threadName);
	}
	
	@Override
	public void run() {
		synchronized (this) {//此时临界区中的代码无法实现串行执行,因为此时对象锁在线程1和线程2之间不共享,不是同一个对象
			System.out.println(this);//哪个线程调用这个方法,this就指的是哪个线程对象
			for (int i = 0; i < 3; i++) {
				System.out.println(getName() + " : " + i);
			}
		}
	}
}

执行结果
在这里插入图片描述
方法改进

public class Test {

	public static void main(String[] args) {
		Runnable counterThread = new CounterThread();//创建了一个计数器线程对象
		new Thread(counterThread,"线程1").start();
		new Thread(counterThread,"线程2").start();
	}
}

class CounterThread implements Runnable {

	@Override
	public void run() {//this为main方法中创建的计数器线程,故为同一个对象锁
		synchronized (this) {// 此时临界区中的代码可以实现串行执行,因为此时接口实现类对象充当了对象锁的功能,该对象锁在两个线程之间共享
			Thread thread = Thread.currentThread();
			for (int i = 0; i < 3; i++) {
				System.out.println(thread.getName() + ":" + i);
			}
		}
	}
}

执行结果
在这里插入图片描述

三、synchronized关键字

1.声明同步方法

public synchronized void methodName( ){
	//同步操作方法体
}

例子

public class Playground {

	public static void main(String[] args) {//main线程
		Bike bike = new Bike();//定义了一个bike对象
		PersonThread xiaoLin =new PersonThread("小林",bike);//创建了一个线程名字叫xiaoLin
		xiaoLin.start();//xiaoLin线程就绪
		PersonThread xiaoWang =new PersonThread("小王",bike);//创建了一个线程名字叫xiaoWang
		xiaoWang.start();//xiaoWang线程就绪
		//小Lin线程和xiaoWang线程传入的bike为同一个bike对象
	}
}

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);//bike对象调用run方法
	}
}

class Bike{//获得锁的线程只有执行完锁内的方法,才能释放锁,同一个锁同一时间只有一个线程能够获得
	public synchronized void move(String personName){//为非静态同步方法,故xiaoLin线程和xiaoWang线程共享bike对象
		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
		xiaoLin.start();//xiaoLin线程就绪
		PersonThread xiaoWang =new PersonThread("小王");//创建了一个线程名字叫xiaoWang
		xiaoWang.start();//xiaoWang线程就绪
	}
}

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();
		System.out.println("当前线程线程:"+threadName);//判断当前那条线程在执行该方法,两个线程先抢占式输出,先执行synchronized代码块的线程获得对象锁
		synchronized(Bike.class){//此时多个线程间共享该Class类对应的对象
			System.out.println(Bike.class);//打印输出,怎么两个线程对应的Class类对应的对象是同一个对象,两个线程共享对象锁
			for (int i = 1; i <= 3; i++) {
				System.out.println(personName+":已经运行"+i+"秒");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

执行结果
在这里插入图片描述

3.死锁

如果有两个或两个以上的线程都访问了多个资源,而这些线程占用了一些资源的同时又在等待其它线程占用的资源,也就是说多个线程之间都持有了对方所需的资源,而又相互等待对方释放的资源,在这种情况下就会出现死锁。

多个线程互相等待对方释放对象锁,此时就会出现死锁

public class DeadLockThread {
	// 创建两个线程之间竞争使用的对象
	private static Object lock1 = new Object();//对象锁1
	private static Object lock2 = new Object();//对象锁2

	public static void main(String[] args) {
		new ShareThread1().start();//线程1进入就绪状态
		new ShareThread2().start();//线程2进入就绪状态
	}

	private static class ShareThread1 extends Thread {
		public void run() {
			synchronized (lock1) {//线程1多的了对象锁1,执行synchronized内代码
				try {
					Thread.sleep(50);//线程1阻塞50s
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lock2) {
					System.out.println("ShareThread1");
				}
			}
		}
	}

	private static class ShareThread2 extends Thread {
		public void run() {
			synchronized (lock2) {//线程2获得了对象锁2,执行synchronized代码块内代码
				try {
					Thread.sleep(50);//线程2阻塞50s
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lock1) {
					System.out.println("ShareThread2");
				}
			}
		}
	}
}

①由于线程是抢占式输出,线程1和线程2中,假设线程1先抢占到CPU的资源获得对应对象锁1进入阻塞状态
②随后线程2抢占到CPU的资源获得对应的对象锁2进入阻塞状态
③线程1等待50s后,由于对象锁2被线程2获取,线程1等待获取对象锁2
④线程2等待50s后,由于对象锁1被线程1获取,线程2等待获取对象锁1
⑤线程1和线程2都处于等待状态,且等待不到需要的对象锁,故陷入死锁

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值