多线程之同步代码块与锁

上篇的代码,存在一个问题,那就是多线程有可能操作到同一个资源块,解决这个问题的方式就是同步代码快或者加上锁。锁的作用就是让资源的占用总是被一个线程调用,而不会使多个线程发生抢占。

那么如何解决线程安全问题呢,第一是同步代码快:

package com.qf.duoxian;

public class Ticket1 implements Runnable {

	public Ticket1() {
		// TODO Auto-generated constructor stub
	}

	int ticket = 100;
	
  Object
     lock = new Object();
	
	
	@Override
	public void run() {
		while (true){
			synchronized (lock) {
				if(ticket>0){
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						
					}
					System.out.println(Thread.currentThread().getName()+"谁买了票"+ticket--);
				}
			}
		}
		
	}

}
class ThreadDemo2{
	public static void main(String[] args) {
		Ticket1 ticket1 = new Ticket1();
	    
		Thread t1 = new Thread(ticket1,"卖给我火车票a");
		Thread t2 = new Thread(ticket1,"卖给我火车票b");
		Thread t3 = new Thread(ticket1,"卖给我火车票c");
	
	    t1.start();
	    t2.start();
	    t3.start();
	}
}

这是打印印出来的结果:

卖给我火车票a谁买了票100
卖给我火车票a谁买了票99
卖给我火车票a谁买了票98
卖给我火车票a谁买了票97

还有就是同步方法。

第二种就是上锁:

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

void lock() 获取锁。

void unlock() 释放锁。

实例:更简单。

public class Ticket2 implements Runnable {

	public Ticket2() {
		// TODO Auto-generated constructor stub
	}
    int  ticket = 100;
	
    Lock lock = 	new  ReentrantLock();
	@Override
	public void run() {
		
		while(true){
			lock.lock();
			if(ticket>0){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+"买票了"+ticket--);
            lock.unlock();
			}
		}
	}

}
class ThreadPool{
	public static void main(String[] args) {
		Ticket2 ticket = new Ticket2();
		
		Thread 他 = new Thread(ticket,"窗口1");
		Thread 他1 = new Thread(ticket,"窗口2");
		
		Thread 他2 = new Thread(ticket,"窗口3");
		他.start();
		他1.start();
		他2.start();
	    
	}
}

死锁:

 

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

等待唤醒机制:

 

在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即——等待唤醒机制。

比如:实现一功能,input为一线程,output为一线程,main为主线程,实现input对成员变量赋值,output获取成员变量值。

但有一个问题是当input给一个成员变量赋值后,output开始获取成员变量值,此时还没有获取完全,紧接着input给下一个成员变量赋值,这就导致了output获取的值可能是input第二次给成员变量赋的值,要想解决此办法,必须让input和output这两个线程实现通信,保证一边input赋值后,等待output获取值,当其获取值后,给input一个信号,使其再赋值。才能保证不会出现乱值。

 

等待唤醒机制所涉及到的方法:

 wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

 notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

 notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

 

1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

public class Resources {

	private String name;
	private String sex;
	private boolean flag = false;
	public synchronized void  set(String name, String sex) {
	  if(flag)
		  try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		  this.name = name;
		  this.sex = sex;
		  
		  flag = true;
		  
		  this.notify();
	  
		
	}
	public synchronized void out(){
		if(!flag)
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("姓名是:"+name+"年龄:"+sex);
			flag = false;
			this.notify();
		
	}
	
	
	

}
public class Output implements Runnable {

	private Resources r;
	
	public Output(Resources r) {
		this.r = r;
	}

	@Override
	public void run() {
		while (true){
			r.out();
		}
		
	}

}
class ThreadDemo3{
	public static void main(String[] args) {
		Resources r = new Resources();
		Input in = new Input(r);
		Output out = new Output(r);
	
	    Thread t1 = new Thread(in);
	    Thread t2 = new Thread(out);
	
	    t1.start();
	    t2.start();
	}
}
public class Input implements Runnable {
    private Resources r;

	public Resources getR() {
		return r;
	}


	public Input(Resources r) {
		this.r = r;
	}


	public void setR(Resources r) {
		this.r = r;
	}

	@Override
	public void run() {
	int count = 0;
	while(true){
		if(count ==0){
			r.set("小明", "男");
		}else{
			r.set("小花", "女");
		}
		count = (count+1)%2;
	}
		
	}

}

线程池

线程池概念:

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

 

我们详细的解释一下为什么要使用线程池?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

使用线程池方式:

1. Runnable接口

2. Callable接口

你可能感兴趣的:(面试,并发基础)