//为了均衡CPU和内存的速度差异,增加了缓存 导致了可见性的问题; //操作系统增加了进程 线程 分时复用CPU,均衡CPU和io设备的速速差异 导致了原子性问题; //jvm指令重排序(优化指令排序) 导致了有序性的问题
可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据
原子性问题是指 多线个线程增加数据 有几个线程挂了, 数据就会减少;
有序性问题是指 对象创建需要三步,堆中分配内存-初始化-变量指向内存地址;如果重排序会出现1 3 2;导致没有初始化的数据创建;
//如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统 //如果每个线程执行的代码不同: 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
可见性问题实例:
线程共享:只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享
可见性问题 导致数据不一致需要加锁 定义一个共享的锁对象
总结:要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。
class TicketInfo implements Runnable{
private int ticketNumber=20;
@Override
public void run() {
while(true){
if(ticketNumber>0){
try {
//使用sleep不然执行每个线程都会占用完毕
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"....sale : "+ ticketNumber--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
int num=10;
TicketInfo ticket = new TicketInfo();
//如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统
//只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
t4.start();
//可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据
}
线程传参的方式
//线程传参的方式2类4种 //第一类:主动给线程传参 //通过构造函数给Thread进行传递 //通过变量和方法给Runnable传递数据 //第二类 线程主动获取参数 //Thread线程主动获取参数通过回调函数获取 //线程Exchanger工具类实现线程间的数据交换
主动给线程传参;
class TicketInfo implements Runnable{
private int ticketNumber=0;
//通过构造函数进行传参
TicketInfo(int ticketNumber){
this.ticketNumber=ticketNumber;
}
/**
* 第二种通过变量传参
*/
private String parameter ;
public void setParameter(String parameter) {
this.parameter = parameter ;
}
@Override
public void run() {
try{
while(true){
if(ticketNumber>0){
try {
//使用sleep不然执行每个线程都会占用完毕
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+".."+parameter+"..sale : "+ ticketNumber--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
TicketInfo ticket = new TicketInfo(20);
//如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统
//只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
ticket.setParameter("ticket");
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
t1.start();
t2.start();
t3.start();
t4.start();
//可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据
}
Java如何实现多个线程之间共享数据
实现多个线程之间共享数据
一、 如果每个线程执行的代码相同
可以使用同一个Runnable对象,这个Runnable对象中有能共享数据,例如 买票系统
class TicketInfo implements Runnable{
private int ticketNumber=0;
//通过构造函数进行传参
TicketInfo(int ticketNumber){
this.ticketNumber=ticketNumber;
}
/**
* 第二种通过变量传参
*/
private String parameter ;
public void setParameter(String parameter) {
this.parameter = parameter ;
}
Object lock = new Object(); // 定义一个共享的锁对象
@Override
public void run() {
try{
while(true){
synchronized (lock){
if(ticketNumber>0){
try {
//使用sleep不然执行每个线程都会占用完毕
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+".."+parameter+"..sale : "+ ticketNumber--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
2.如果每个线程执行的代码不同
将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
思想: 一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,在主函数中直接创建线程对象,即可完成操作(可以实现两个内部类,不用构造方法传值,使用final定义data局部变量)
例如: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1
//取款线程 需要传入一个共享数据
class MyRunnable2 implements Runnable{
private ShareDate data;
private int num;
MyRunnable2(ShareDate data,int num){
this.data = data;
this.num = num;
}
@Override
public void run() {
if(num>0){
for(int i=0;i<num;i++){
data.decrement();
}
}
}
}
//存款的线程 需要传入一个数据数据
class MyRunnabe1 implements Runnable{
private ShareDate data;
private int num;
MyRunnabe1(ShareDate data,int num){
this.num=num;
this.data=data;
}
@Override
public void run() {
if(num>0){
for(int i=0;i<num;i++){
data.increment();
}
}
}
}
//封装共享数据和操作共享数据方法的类
class ShareDate{
private int j=10;
public synchronized void increment(){
j++;
System.out.println(Thread.currentThread().getName()+" add :"+j);
}
public synchronized void decrement(){
j--;
System.out.println(Thread.currentThread().getName()+" del :"+j);
}
}
public static void main(String[] args) {
//将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。
// 每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
//思想: 一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,
// 在主函数中直接创建线程对象,即可完成操作
// (可以实现两个内部类,不用构造方法传值,使用final定义data局部变量)
//例如: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1
//将数据封装到一个对象,内存中只有一个data成员变量,是共享数据
ShareDate data=new ShareDate();
for (int i = 0; i <2;i++ ) {
System.out.println("内存中只有一个data成员变量");
new Thread(new MyRunnabe1(data,1)).start();//12
new Thread(new MyRunnable2(data,2)).start();//12-4=8
}
//要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。
}
多线程之间共享数据的方式探讨
总结:要同步互斥的几段代码最好放在几个独立的方法中,这些方法再放入一个类中,这样比较容易实现它们之间的同步互斥和通信。
方式一:代码一致
如果每个线程执行的代码相同,可以用一个Runnable对象,这个Runnable对象中存放能共享数据(卖票系统)
方式二:代码不一致
如果每个线程执行的代码不同时,就需要不同的 Runnable 对象:
a.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据操作的方法也分配到这个对象中,这样容易实现针对改数据进行的各个操作的互斥通信
实例代码为: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1
b.将 Runnable 对象作为某一个类的内部类,共享数据作为这个外部类的成员变量,每个线程对共享数据的操作方法也分配到外部类中,实现共享数据的互斥和通信操作,作为内部类的各个 Runnable 对象调用外部类的这些方法。
public class MultiThreadShareData {
private int shareData=0;
public static void main(String[] args) {
MultiThreadShareData m=new MultiThreadShareData();
//初始化Runnable对象
MyRunnable1 myRunnable1 = m.new MyRunnable1();
MyRunnable2 myRunnable2=m.new MyRunnable2();
//开启线程
new Thread(myRunnable1).start();
new Thread(myRunnable2).start();
}
private synchronized void increment(){
this.shareData++;
System.out.println(Thread.currentThread().getName()+":shareData增加了1后shareData="+shareData);
}
private synchronized void decrement() {
this.shareData--;
System.out.println(Thread.currentThread().getName()+":shareData减少了1后shareData="+shareData);
}
/**
* 作为内部类的Runnable对象
*/
class MyRunnable1 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
increment();
}
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
decrement();
}
}
}
}
c.. 以上两种方法的组合:将共享数据封装到一个对象中,每个线程对共享数据的操作方法也分配到对象中,对象作为外部类的成员变量或方法中的局部变量,每个线程的 Runnable 作为成员内部类或局部内部类。
public class MultiThreadShareData {
public static void main(String[] args) {
ShareData data = new ShareData();
new Thread(()->{
for(int i=0;i<100;i++){
data.increment();
}
}).start();
new Thread(()->{
for (int j=0;j<100;j++) {
data.decrement();
}
}).start();
}
}
/**
封装共享数据的对象
*/
class ShareData{
//共享数据
private int j=0;
/**
对共享数据进行操作的方法
*/
public synchronized void increment(){
this.j++;
System.out.println(Thread.currentThread().getName()+":j增加了1后j="+j);
}
public synchronized void decrement() {
this.j--;
System.out.println(Thread.currentThread().getName()+":j减少了1后j="+j);
}
public int getJ() {
return j;
}
}
总之,要同步互斥的几段代码最好放在几个独立的方法中,这些方法再放入一个类中,这样比较容易实现它们之间的同步互斥和通信。
线程通信(同步)
wait/notify 实例
public class ThreadExecutionOrder {
private static int sharedCount = 0;
public static void main(String[] args) {
Object lock = new Object(); // 定义一个共享的锁对象
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) { // 通过共享的锁对象来实现同步
while(sharedCount != 0) { // 如果前一个线程还未执行完,则等待
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread 1");
sharedCount = 1; // 修改共享变量的值,表示当前线程已经完成任务
lock.notifyAll(); // 唤醒其他等待线程
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) {
while(sharedCount != 1) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread 2");
sharedCount = 2;
lock.notifyAll();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock) {
while(sharedCount != 2) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread 3");
sharedCount = 3;
lock.notifyAll();
}
}
});
t1.start();
t2.start();
t3.start();
}
}
死锁产生问题
public class LockLock {
//创建资源
private static Object resourceA =new Object();//定义一个共享锁对象
private static Object resourceB =new Object();//定义一个共享锁对象
public static void main(String[] args) throws InterruptedException {
Thread threadA=new Thread(()->{
//线程A使用synchronized(resourceA)方法获取到了resourceA的监视器锁;
synchronized (resourceA){
System.out.println(Thread.currentThread().getName() +" get ResourceA");
try {
Thread.sleep(1000);//调用sleep函数休眠1s 休眠1s是为了保证线程A在获取resourceB
//对应的锁前让线程B抢占到CPU 获取到资源resourceB上的锁;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +" waiting get resourceB");
synchronized (resourceB){
System.out.println(Thread.currentThread().getName()+" get resourceB");
}
}
});
//产生死锁
/*Thread threadB =new Thread(()->{
//获取resourceB 共享资源的监视器锁
synchronized (resourceB){
System.out.println(Thread.currentThread().getName()+" get ResourceB");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" waiting get resourceA");
//获取resourceA共享资源的监视器锁
synchronized (resourceA){
System.out.println(Thread.currentThread().getName()+"get resourceA");
}
}
});*/
//改变执行顺序 避免死锁
Thread threadB =new Thread(()->{
//获取resourceB 共享资源的监视器锁
synchronized (resourceA){
System.out.println(Thread.currentThread().getName()+" get ResourceB");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" waiting get resourceA");
//获取resourceA共享资源的监视器锁
synchronized (resourceB){
System.out.println(Thread.currentThread().getName()+"get resourceA");
}
}
});
/*
首先 resourceA和 resourceB 都是互斥资源,当线程A调用 synchronized(resource A)
方法获取到 resourceA 监视器锁并释放前, 线程B再调 synchronized(resourceA
法尝试获取该资源会被阻塞,只有线程A主动释放该锁,线程B能获得 这满足了资源互斥条件
*/
threadA.start();
//threadA.join();
threadB.start();
//要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可;
//学过操作系统的朋友知道 目前只有请求并持有和环路等待条件是可以被破会的
//1.造成死锁的原因其实和申请资源的顺序有很大关系; 使用资源申请的有序性原则就可以避免死锁,
}
}