java多线程和线程安全问题

线程的几种状态:

new指的是创建一个thread或者runnable对象;dead是指线程线程无法再占用cpu和内存,不存在了。runnable指的是线程再线程池中等带cpu给他分配资源,等待执行的状态;running就是线程在执行。值得注意的是blocked(阻塞)状态。我们有时候为了线程安全考虑,需要在一些情况下停止线程运行,在需要的时恢复线程运行,为了这个目标引入阻塞的概念。 

线程在Running的过程中可能会遇到阻塞(Blocked)情况

  1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
  2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。

当一个进程内有多个线程同时运行,多个线程之间共享数据时,就会出现线程安全问题,线程安全问题遵循这三个规律:是否是多线程;是否有共享数据;是否有多个线程操作共享数据。

以下列举了三种解决线程安全的方法:

1.synchronize同步代码块

同步代码块把进入的线程锁住,只有获得锁的线程能执行同步代码块内的程序,这个把线程锁住的锁是内置锁(java中的内置锁是互斥锁),也就意味着只有一个线程能去获得这个锁,另一个线程想要获得这个锁,必须等上一个线程释放锁,因此如果这个线程不释放锁,那么另一个线程会一直等下去。

格式:
        synchronized(对象){
            需要同步的代码;
        }
这个对象其实就是一把锁,也可以称之为监视器,监视进程的进出,它是同步代码块保证数据的安全性的一个主要因素。要注意的是这个对象必须被所有线程共享,不能在里面new对象,这样每把锁都不一样了,这个锁也就没有意义了。

由于多个线程共享同一把对象锁,当某个线程拥有对象锁时,其他线程无法执行该对象锁对应的同步代码块里的程序,实现了多并发时的线程安全。当某个同步代码块程序执行完成以后,自动释放对象锁,然后在锁定池内的其他线程争抢该锁,争到该锁的线程即可由runnable状态变为running状态。

有的时候我们要给线程的执行再加上限制条件,使得线程在不满足限制条件时即使抢到对象锁也不执行,这就需要wait()方法,wait方法只能在synchronized{}内部使用,效果是让线程进入阻塞状态,进入等待池,同时释放对象锁。notify和notifyall方法可以将等待池内的线程加入到锁定池争抢锁。从而实现对线程的控制。常见的使用场景:

生产者消费者模型。

使用2个线程,分别代表:生产者、消费者。让他们并发的去生产、消费产品。这里我们使用的是使用信号量去控制线程的生产消费,通过释放令牌的形式去控制生产者消费者的上限。使用互斥锁保证每次最多只有一个角色去修改共享变量。

//生产者线程
public class ProducerThread implements Runnable{
 private int maxorder=100;
 private int order[];
 private Object obj;
 public void produce() {
	 order[0]++;
 }
 public ProducerThread(int order[],Object obj) {
	 this.order=order;
	 this.obj=obj;
 }
 public void run() {
	 while(true) {
			synchronized (obj) {
				//判断仓库的容量
				if(order[0]<100) {
					//生产产品
					produce();
					System.out.println(Thread.currentThread().getName()+"生产者"+":"+order[0]);
				}
				else {
					//唤醒消费者
					obj.notify();	
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
					
				
}
	 }
 }
}
//消费者线程
public class ConsumeThread implements Runnable{
	 private int[] order;
	 private Object obj;
public  Lock lock = new ReentrantLock();
	 public void consume() {
		 order[0]--;
	 }
	 public ConsumeThread(int[] order,Object obj) {
		 this.order=order;
		 this.obj=obj;
	 }
	 public void run() {
				
		 while(true) {
				synchronized (obj) {
					//判断仓库的容量
					    if(order[0]>0) {
						
						 consume();
						 System.out.println(Thread.currentThread().getName()+"消费者"+":"+order[0]);
						 }
					    else {//进入等待状态
					    	obj.notify();
						 try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					    }
						//唤醒生产者
						
		                  
	     }
}
}
	 }

//主线程
public class Main {
	public static void main(String[] args) {
		Ticket tk=new Ticket();
		int[] t=new int[1];
		t[0]=tk.tk;
		Object obj=new Object();
		ProducerThread c = new ProducerThread(t,obj);
		ConsumeThread b = new ConsumeThread(t,obj);
		new Thread(c,"B").start();
		new Thread(b,"A").start();
	

		
		//new Thread(b,"A").start();
		//new Thread(b,"B").start();
		//new Thread(b,"C").start();
	}
}
//输出结果
B生产者:98
B生产者:99
B生产者:100
A消费者:99
A消费者:98
A消费者:97

2.Lock接口

用同步代码块我们可以给线程上锁,但是具体在哪里上锁和解锁,我们都不知道,因此为了让我们更加清楚如何加锁和解锁,在JDK5以后提供了Lock接口和它的ReentrantLock子类,这个锁与synchronized不同,需要手动去加锁和解锁。

void lock()加锁
void unlock()解锁


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值