多线程>>>>>2

本文深入探讨了线程安全问题,包括分析错误数据产生的原因,使用同步代码块解决线程安全问题,以及如何在多线程环境中实现线程间的协作。通过具体实例,如银行账户存款和生产者消费者模式,详细解释了synchronized关键字和Lock接口的使用,以及如何避免死锁和正确停止线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程安全问题

线程安全问题:分析:4个线程共用了一个数据,出现了-1,-2,-3等错误的数据。

具体分析:

1、共用了一个数据
2、共享语句有多条,一个线程使用cpu,没有执行完cpu就被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生。

解决:
在代码中使用同步代码块儿(同步锁)
解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了cpu,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,其他的线程才能有资格进入。

 同步代码块儿的构成:
synchronized(锁(对象)){
      同步的代码
 }

对作为锁的对象的要求:1、必须是对象;2、必须保证被多个线程共享
 可以充当锁的:1、一个普通的对象;2、当前对象的引用--this;3、类的字节码文件

同步代码块儿的特点:1、可以保证线程的安全;2、由于每次都要进行判断处理,所以降低了执行效率。

总结:什么时候使用同步代码块儿
1、多个线程共享一个数据
2、至少有两个线程

public class Demo {
		Ticket ticket = new Ticket();
		Thread thread1 = new Thread(ticket);
		Thread thread2 = new Thread(ticket);
		Thread thread3 = new Thread(ticket);
		Thread thread4 = new Thread(ticket);
		thread1.start();
		thread2.start();
		thread3.start();
		thread4.start();
		
	}
}
class Ticket implements Runnable{
	//所有的线程共享num
        int num = 20;
        boolean flag = false;
	/*
	 * 可以作为锁的条件:
	 * 1.必须是对象
	 * 2.锁必须被多个线程共享
	 * 
	 * 可以作为锁的:普通的对象,this,类的字节码文件(.class)
	 */
        Object object = new Object();
	public void run() {
		
	    while(!flag){
	    
	    	synchronized (object) {//同步代码块儿---实现了多个线程间的互斥
		    	try {
					Thread.sleep(100);
		    	                //制造一个延迟,相当于让当前的线程睡一会儿(临时让出cpu)
                                        //注意:线程在哪里睡的,就会停留在哪里. 
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		    	
		    	if (num > 0) {
		    		System.out.println(Thread.currentThread().getName()+"   "+ --num);
				}else {
					flag = true;
				}
	    	}
	    }
	}
}

代码上,普通方法上,静态方法上实现同步(加锁)

/*
     实例:
       两个人同时向银行账户存钱
       两个人相当于两个线程
       操作的是一个数据
*/
public class Demo {
	public static void main(String[] args) {
		//创建任务对象
		CunQian cunQian = new CunQian();
		//创建线程绑定任务
		Thread thread1 = new Thread(cunQian);
		Thread thread2 = new Thread(cunQian);
		//开始存钱
		thread1.start();
		thread2.start();
	}
}

class Bank{
	int sum;

	//使用同步代码块儿实现同步
	public void  addMoney(int num) {
		synchronized (this) {
			sum+=num;
			System.out.println(sum);
		}
	}
	//同步函数---将函数直接变成同步的
	//非静态的同步函数
	/*
	 * 默认在synchronized后面有锁,这把锁默认是this
	 */
	public synchronized void addMoney(int num) {
		sum+=num;
		System.out.println(sum);
	}
	
	//静态的同步函数
	/*
	 * 默认在synchronized后面有锁,这把锁默认是当前类的字节码文件
	 */
	public synchronized static void  addMoney1(int num) {
		//sum+=num;
		//System.out.println(sum);
	}
}

class CunQian implements Runnable{
	Bank bank = new Bank();
	public  void run() {
		for(int i=0;i<4;i++){
			bank.addMoney(100);
		}
	}
}

理解synchronized关键字

1、synchronized关键字的作用域有二种: 
               1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 
               2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,
               表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 
              在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

单例多线程下的安全问题

//饿汉式
//由于共享的代码只有一行,不会发生线程安全
class SingleInstance{
	private final static SingleInstance singleInstance = new SingleInstance();
	private  SingleInstance() {
	}
	public static SingleInstance getInstance() {
		return singleInstance;
	}
}


//懒汉式
class SingleInstance{
	private static SingleInstance singleInstance;
	private SingleInstance(){
		
	}
	public static SingleInstance getInstance() {
		if(singleInstance == null) {
                //目的:尽量减少线程安全代码的判断次数,提高效率
			synchronized (SingleInstance.class) {
				if(singleInstance == null)
                                //保证只创建一个对象
					singleInstance = new SingleInstance();
			}
		}
		return singleInstance;
	
	
	} 
}

线程之间的协作

/*
   实例:
      打印机打印----不断输入不断输出
 */
//分析:
/*
      我们可以将数据抽成一个类,类内包含数据的准备工作
      要有两个线程,一个输入线程一个输出线程。任务区,要实现操作一份数据的输入,输出。
      因为两个线程要操作同一份的数据,所以我们要考虑线程安全问题
      
      使用唤醒等待机制---notify(),notifyAll(),wait()
      wait():让当前的线程就地等待,放入一个池子(线程池),失去了抢cpu的能力,等待被唤醒(锁相当于给当前的线程做了一个标记)
      notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
      notifyAll():唤醒的是同一把锁下的所有线程
      注意:我们只能在锁内或者说是同步的情况下使用这三个方法,否则会有InterruptedException异常
*/  

class Des{
	String name;
	String sex;
	boolean flag;//控制唤醒和等待之间的切换
	
	//给输入准备数据
	public synchronized void setData(String name,String sex){
		if (flag == true) {//当flag值为true的时候,让当前的线程处于等待
			try {
				//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name;
		this.sex = sex;
		//更换flag的值
		flag = !flag;
		//唤醒输出线程,如果输出线程现在本身是活跃状态,即我们没有线程可唤醒,称为空唤醒,程序允许空唤醒.
		//原理:唤醒同一把锁下的任意一个线程.
		notify();
	}
	//给输出准备数据
	public synchronized void getData() {
		if (flag == false) {//当flag值为false的时候,让当前的线程处于等待
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}//让输出线程等待
		}
	    System.out.println("姓名:"+name+"    性别:"+sex);
	    flag = !flag;
	    //唤醒输入线程
	    notify();
	}
}

//建立输入任务,输出任务
class Input implements Runnable{
	Des des;
	public Input(Des des) {
		super();
		this.des = des;
	}
	public  void run() {

		int i=0;
		while (true) {
			/*
			 * 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的
			 * 给两个任务加一把锁:可以是des或者Object.class
			 * 分析:
			 * 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.
			 * 使用des最合适,因为他只被当前的两个任务共享.
			 * 
			 *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.
			 */
			if (i==1) {
				des.setData("特没谱", "男");
			}else {
				des.setData("小晋三", "女");
			}
			i=(i+1)%2;
			
		}
	}
}

class Output implements Runnable{
	Des des;
	public Output(Des des) {
		super();
		this.des = des;
	}
	public void run() {
		while (true) {
			des.getData();
		}
	}
}

生产者与消费者

单生产者与消费者

public class Demo {
	public static void main(String[] args) {
		//准备数据
		Product product = new Product();
		//准备任务
		Producer producer = new Producer(product);
		Consumer consumer = new Consumer(product);
		//准备线程
		Thread proThread = new Thread(producer);
		Thread conThread = new Thread(consumer);
		//开启线程
		proThread.start();
		conThread.start();	
	}
}

//创建产品
class Product{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//准备生产
	public synchronized void setProduce(String name,double price){
		if (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		
		count++;
		flag = ! flag;
		notify();//唤醒消费线程
	}
	//准备消费
	public  synchronized void getConsume() {
		if (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		
		count--;
		flag = ! flag;
		notify();//唤醒生产线程
	}
}
//创建生产任务
class Producer implements Runnable{
	Product product;
	public Producer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer implements Runnable{
	Product product;
	public Consumer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

多消费者多生产者

/*
   当我们在上一个Demo中直接再加上两个线程的时候,会发生一些安全问题:
   错误描述:
   当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
   原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码
   解决办法:将标记处的if改成while
 
   问题描述:继续运行程序,会出现死锁的情况(4个线程同时处于等待状态)
   原因:唤醒的是本方的线程,最后导致所有的线程都处于等待状态.
   解决办法:将notify改成notifyAll.保证将对方的线程唤醒
*/

public class Demo {
	public static void main(String[] args) {
		//准备数据
		Product product = new Product();
		//准备任务
		Producer producer = new Producer(product);
		Consumer consumer = new Consumer(product);
		//准备线程
		Thread t0 = new Thread(producer);
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(consumer);
		Thread t3 = new Thread(consumer);
		//开启线程
		t0.start();
		t1.start();	
		t2.start();	
		t3.start();	
	}
}

//创建产品
class Product{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//准备生产
	public synchronized void setProduce(String name,double price){
		while (flag == true) {
			try {
				wait();//让生产线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		this.name = name;
		this.price = price;
		System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
		
		count++;
		flag = ! flag;
		//notify();//唤醒消费线程
		notifyAll();
	}
	//准备消费
	public  synchronized void getConsume() {
		while (flag == false) {
			try {
				wait();//让消费线程等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);

		count--;
		flag = ! flag;
		//notify();//唤醒生产线程
		notifyAll();
	}
}
//创建生产任务
class Producer implements Runnable{
        Product product;
	public Producer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer implements Runnable{
	Product product;
	public Consumer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

死锁的情况

 1、所有的线程处于等待状态 ---上个Demo中把notifyAll()方法改成notify()方法会出现这种情况
 2、锁之间进行嵌套调用

//了解

class MyModel1 {

    public synchronized void action1(MyModel2 myModel2) {
        System.out.println("MyModel1---action1");
        myModel2.action2();
    }

    public synchronized void action2() {
        System.out.println("MyModel1---action2");
    }
}

class MyModel2 {

    public synchronized void action1(MyModel1 myModel1) {
        System.out.println("MyModel2---action1");
        myModel1.action2();
    }

    public synchronized void action2() {
        System.out.println("MyModel2---action2");
    }
}

class DeadAction implements Runnable {
    private MyModel1 myModel1;
    private MyModel2 myModel2;

    public DeadAction(MyModel1 myModel1, MyModel2 myModel2) {
        this.myModel1 = myModel1;
        this.myModel2 = myModel2;
        new Thread(this).start();
        this.myModel1.action1(this.myModel2);
    }

    @Override
    public void run() {
        this.myModel2.action1(this.myModel1);
    }
}

//入口
public class MyDemo {
    public static void main(String[] args) {

        new DeadAction(new MyModel1(),new MyModel2());
    }
}

lock锁

比较synchronized和Lock
       1、synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步
            synchronized(锁对象){  //获取锁      我们将锁还可以称为锁旗舰或者监听器
            同步的代码
            }//释放锁
       2、Lock:从jdk1.5开始使用的同步方法-称为显示同步
            原理:Lock本身是接口,要通过他的子类创建对象干活儿
            使用过程:
            首先调用lock()方法获取锁
            进行同步的代码块儿
            使用unlock()方法释放锁
 
            使用的场景:
                   当进行多生产者多消费者的功能时,使用Lock,其他的一般都使用synchronized
  
            使用效率上:Lock高于synchronized

package com.qf;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo {
	public static void main(String[] args) {
		//准备数据
		Product product = new Product();
		//准备任务
		Producer producer = new Producer(product);
		Consumer consumer = new Consumer(product);
		//准备线程
		Thread t0 = new Thread(producer);
		Thread t1 = new Thread(producer);
		Thread t2 = new Thread(consumer);
		Thread t3 = new Thread(consumer);
		//开启线程
		t0.start();
		t1.start();	
		t2.start();	
		t3.start();	
	}
}

//创建产品
class Product{
	String name;//产品的名字
	double price;//产品的价格
	int count;//生产的产品数量
	
	//标识
	boolean flag = false;
	
	//创建Lock对象
	Lock lock = new ReentrantLock();
	
	//创建用于生产线程的Condition对象
	Condition proCon = lock.newCondition();
	//创建用于消费线程的Condition对象
	Condition conCon = lock.newCondition();
	//准备生产
	public  void setProduce(String name,double price){
		try {
			lock.lock();//获取锁
			while (flag == true) {
				try {
					proCon.await();//让生产线程等待
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
			this.name = name;
			this.price = price;
			System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
			
			count++;
			flag = ! flag;
		    conCon.signal();//唤醒消费线程
		} finally {//必须实现的代码,用于资源的释放
			lock.unlock();//释放锁
		}
		
	}
	//准备消费
	public   void getConsume() {
		try{
			lock.lock();
			while (flag == false) {
				try {
					conCon.await();//让消费线程等待
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);
			
			count--;
			//唤醒生产线程
			flag = ! flag;
		    proCon.signal();
		}finally {
			lock.unlock();
		}
	}
}
//创建生产任务
class Producer implements Runnable{
	Product product;
	public Producer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.setProduce("bingbing", 10);
		}
		
	}
}
//创建消费任务
class Consumer implements Runnable{
	Product product;
	public Consumer(Product product) {
		super();
		this.product = product;
	}
	public void run() {
		while (true) {
			product.getConsume();
		}
	}
}

让线程停止的方法:

1、通过建立一个标识的(flag)的方法

public class Demo {
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		Thread thread1 = new Thread(test1);
		
		thread1.start();
		
		//让主线程睡一会儿
		try{
		    Thread.sleep(100);
		}catch (Exception e) {
			
		}
		
		int i=0;
		while (true) {
			if (++i == 10) {
				test1.flag = false;//当主线程运行到某个阶段的时候,让flag值变成false,来间接的控制子线程的结束
				
				break;//目的让主线程结束
			}
		}
	}
}

class Test1 implements Runnable{
	boolean flag = true;
	public void run() {
		while (flag) {
			System.out.println(Thread.currentThread().getName()+"很开心");
		}
		
	}
}

2、通过调用stop---已经被弃用,因为他有固有的安全问题

3、调用interrupt()方法将一个处于等待状态的线程停止

public class Demo {
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		Thread thread1 = new Thread(test1);
		
		thread1.start();
		
		//让主线程睡一会儿
		try{
		    Thread.sleep(100);
		}catch (Exception e) {
			
		}
		
		int i=0;
		while (true) {
			if (++i == 10) {
				thread1.interrupt();//当主线程运行到某个阶段的时候,让子线程调用interrupt()方法
									//,来触发InterruptedException异常,从而将flag的值 设置成false
				
				break;//目的让主线程结束
			}
		}
	}
}

class Test1 implements Runnable{
	boolean flag = true;
	public synchronized void run() {
		while (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				flag =false;
			}
			System.out.println(Thread.currentThread().getName()+"很开心");
		}
		
	}
}

守护线程

 守护线程:相当于是后台线程,依赖于前台线程,会随着前台线程的结束而结束,不管他当前处于什么状态。
 典型的守护线程:垃圾回收线程

守护线程必须在start之前执行


public class Demo{
	public static void main(String[] args) throws InterruptedException {
		Text text = new Text();
		Thread t = new Thread(text);
		t.setDaemon(true);
		//将thread1变成守护线程
		//当线程调用setDaemon方法,并将参数设置成true时,当前线程就变成了守护线程
		//注意:这行代码必须在start之前执行
		t.start();
		Thread.sleep(100);
	}
}
class Text implements Runnable {

	@Override
	public void run() {
		while(true){
			System.out.println("ha ha ha");	
		}	
	}
	
}

 join()方法
原理:线程一旦调用了join方法,优先级会高于主线程,主线程会等当前的线程执行完任务再去执行自己的任务。
注意点:这个线程优先级只是比主线程高,对其他的线程没有影响。

join方法必须在线程执行了start方法之后执行

public class Demo{
	public static void main(String[] args) {
		Text text = new Text();
		Thread t1 = new Thread(text);
		Thread t2 = new Thread(text);
		t1.start();
		t2.start();
		try {
			t2.join();
                        //注意:join方法必须在线程执行了start方法之后执行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		int i = 0;
		while(++i < 10) {
			System.out.println(Thread.currentThread().getName()+"    ha ha ha");
		}
	}
}
class Text implements Runnable {

	@Override
	public void run() {
		int i = 0;
		while(++i < 10){
			System.out.println(Thread.currentThread().getName()+"    ha ha ha");	
		}	
	}
	
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值