多线程编程总结

实现多线程编程一:继承Thread类,复写父类的run方法,父类引用调用的是子类方法

1.继承Thread类

2.重写run方法(线程要执行的任务)

3.创建并启动(通过start方法)

下面以售票例子说明:

class SaleTicket extends Thread{//继承Thread方法
	private int ticket=100;
	public void run(){//重写run方法
		while(true){
			if(ticket>0)
		System.out.println(Thread.currentThread().getName()+"....."+ticket--);
	
		 }
		}
	
}
public class TicketDemo {

	public static void main(String[] args) {
		//创建并启动四个线程
		SaleTicket st1=new SaleTicket();
		SaleTicket st2=new SaleTicket();
		SaleTicket st3=new SaleTicket();
		SaleTicket st4=new SaleTicket();
		st1.start();//有可能执行权先给st1,先不继续往下	
		st2.start();
		st3.start();
	        st4.start();


	}

}

用这种方法大家可能会发现有些问题,就是原本我们是希望100张票开四个窗口买,可现在变成四个窗口各有100张票。解决方案有我们可以用静态关键字修饰ticket 或者用单例模式解决。但这都不是最佳的因为这种实现继承方式本身有些不好的地方,一、类不符合继承思想,什么时候用继承?当类是某个类的一种,符合is a 关系,而为了实现继承而去继承一个类不符合这个思想。二、Java不支持多继承,如果某个类是子类,那不是无法实现多线程。三、耦合性问题,像这个例子每个线程都包含资源,结果每个线程都用自己的资源,不符合本意,当然是可以修改的,但即使修改了线程与资源耦合性也太高,没有实现线程与资源分离,这与线程思想(轻装上阵,最小的执行单元)不符。

因此推出第二种继承方式,将资源与线程分离,独立封装,更符合oo思想,那就是实现Runnable接口,将实现的类的示例作为参数传给Thread(查阅API文档可知有个Thread(Runnable)构造方法)

class SaleTicket implements Runnable{//实现Runnable接口
	private  int ticket=100;
	public void run(){//重写run方法
		while(true){
			if(ticket>0)
		System.out.println(Thread.currentThread().getName()+"....."+ticket--);
	
		 }
		}
	
}
public class TicketDemo {

	public static void main(String[] args) {
		//给多个线程传共享资源
		SaleTicket st=new SaleTicket();
		Thread t1=new Thread(st);
		t1.start();
		Thread t2=new Thread(st);
		t2.start();
		Thread t3=new Thread(st);
		t3.start();
		Thread t4=new Thread(st);
		t4.start();


	}

}

上述的多线程存在安全问题,因为你对共享资源进行了并发操作,举个例子,你银行卡有500块,又存了200,又花了200,应该是不变的,但如果同时并发操作,一个算300,另一个算700,这两个数据互相覆盖,无论是300还是700都是错的,要出大问题。这个例子也是,在票数为一时,让一个线程进入了减一操作,但还没减一时,又切换了线程,此时的值还是一,于是多个线程进入ticket--操作,有可能结果为负数。可以修改下程序,停顿一会,就能看出效果

if(ticket>0){
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
				
					e.printStackTrace();
				}
		System.out.println(Thread.currentThread().getName()+"....."+ticket--);
			}

因此,我们应该保证对于共享数据同一时间段只有一个线程进行操作,于是引入同步代码块synchronized(对象){需要被同步的代码块;}。就好像一个锁,进入之后把它开关关上,阻止后面的线程进入,等离开后将开关打开允许后续进入。这就是同步的锁机制,不过要重复判断,降低了效率

	synchronized(obj){//Object obj=new Object();
			
			if(ticket>0){
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
				
					e.printStackTrace();
				}
		System.out.println(Thread.currentThread().getName()+"....."+ticket--);
			}
		 }
		}

不直接new Object(),因为这样锁不住,每次进去都新建一个对象,拿的是不一样的锁。加同步代码块的原则是寻找共享数据,在操作共享数据的位置加同步代码块。
要同步还可以用同步函数,加synchronized关键字,那么同步函数用的什么锁呢?其实就是this,我们可以验证一下

class SaleTicket implements Runnable {// 实现Runnable接口
	private int ticket = 100;
	boolean flag = true;

	public void run() {// 重写run方法
		if (flag)
			while (true) {
				synchronized (this) {

					if (ticket > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {

							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + "..block..." + ticket--);
					}
				}
			}
		else {
			while (true)
				sale();
		}
	}

	public synchronized void sale() {
		while (true) {
			if (ticket > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "....func..." + ticket--);
			}
		}

	}
}

public class TicketDemo {

	public static void main(String[] args) {
		
		SaleTicket st = new SaleTicket();
		Thread t1 = new Thread(st);
		t1.start();
		Thread t2 = new Thread(st);
		st.flag = false;
		t2.start();

	}

}


由代码可知如果在这时候输出结果还能保持正确,就说明它们持有的锁是一致的,否则无法达到同步效果。但是运行之后却发现问题了

发现运行结果全都是线程0,执行同步函数,之所以这样是因为线程t1(Thread-0)开启后立刻切换到主线程执行,主线程将flag改为false,所以线程执行的是同步函数。修改如下:

public class TicketDemo {

	public static void main(String[] args) throws InterruptedException {
		
		SaleTicket st = new SaleTicket();
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(st);
		t1.start();
		Thread.sleep(10);//主线程休眠,执行权给t1,防止主线程得到直接改了flag
		st.flag = false;
		t2.start();
		
		
		
	}

}

结果大家去试试吧。接下来引入新问题,同步函数要是被static修饰呢?那就没有this指针。一个函数是被对象调用的,如果是静态可以被类直接调用。有点类似这边的锁为类名.class(字节码对象,静态方法随类加载,没有该类的对象但有该类的字节码文件对象。)。同样可以验证一下:

class SaleTicket implements Runnable {// 实现Runnable接口
	private static int ticket = 100;
	boolean flag = true;
	
	public void run() {// 重写run方法
		if (flag)
			while (true) {
				synchronized (SaleTicket.class) {

					if (ticket > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {

							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName() + "..block..." + ticket--);
					}
				}
			}
		else {
			while (true)
				sale();
		}
	}

	public static synchronized void sale() {
		while (true) {
			if (ticket > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "....func..." + ticket--);
			}
		}

	}
}

截屏大小有限看不出,自己去试试吧,结果应该是可以同步的

同步升级:生产者消费者问题

class Resource{
	private String name;
	private int count=1;
	public void set(String name){
		this.name=name+"...."+count++;
		System.out.println(Thread.currentThread().getName()+"。。。。生产者。。。"+this.name);
	}
	public void get(){
		System.out.println(Thread.currentThread().getName()+"。。。。消费者。。。"+this.name);
	}
}
class Producer implements Runnable{
	private Resource res;
	public Producer(Resource res){
		this.res=res;
	}
	public void run(){
		while(true)
		res.set("面包");
		
	}
	
}
class Consumer implements Runnable{
	private Resource res;
	public Consumer(Resource res){
		this.res=res;
	}
	public void run(){
		while(true)
		res.get();
	}
	
}
public class ProducerConsumerDemo {

	public static void main(String[] args) {
	Resource res=new Resource();
	Producer pro=new Producer(res);
	Consumer con=new Consumer(res);
	new Thread(pro).start();
	new Thread(con).start();
	}

}

在还没加同步前会出现未生产先消费和重复消费的问题,这是由于生产者还没来得及输出就切换线程,消费者多次得到执行权,但是加了同步之后,消费者生产者操作都封闭了。生产者拼命加一,输出,消费者就拼命输出相同的面包(连续重复消费)

这很明显不是我们想要的效果,我们要的是生产一个消费一个交替协调。我们可以使用等待--唤醒机制,应该生产的时候却去消费就让那个线程wait(),让生产线程顺利生产,然后去唤醒(notify)被阻塞的线程。具体我们可以看代码:

class Resource {
	private String name;
	private int count = 1;
	private boolean flag = false;

	public synchronized void set(String name) {
		if (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		this.name = name + "...." + count++;
		System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
		flag = true;// 生产了要消费,置为true
		notify();// 唤醒等待的线程
	}

	public synchronized void get() {
		if (!flag)// 没生产不能消费,等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
		flag = false;// 消费完了,去生产
		this.notify();// 唤醒等待的线程
	}
}

wait()和notify()方法要指明相应的锁对象,默认为this(同步函数)。唤醒也是有针对的唤醒,比如locka.notify()唤醒的一定是locka.wait()的等待线程。notify()是唤醒一个,notifyAll()是唤醒所有。

问题升级:多生产多消费

public class ProducerConsumerDemo {

	public static void main(String[] args) {
		Resource res = new Resource();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		new Thread(pro).start();
		new Thread(con).start();
		new Thread(pro).start();
		new Thread(con).start();
	}

}


出现问题:重复生产重复消费。原因:唤醒的不确定性,比如生产者线程可能唤醒了己方原本等待的生产者线程,生产者线程继续执行生产覆盖了还未被消费的面包,这就是重复生产。重复消费也是这样原因。因此,被唤醒的线程应该同样去判断标记flag。代码修改只需把两处if判断改成while循环。结果却很尴尬的发现死锁了

还是上述的原因,唤醒己方线程,判断标记后wait(),导致所有线程都进入等待状态,就陷入死锁状态。

解决方案一:用notifyAll()全部唤醒,即使本方再一次抢到也会进入wait()状态,让另一方执行。用notifyAll()替换notify()就修改好代码了,测试结果发现是理想的结果。但这种效率低。

解决方案二:根据先前说的针对性唤醒我们可以用locka.wait()....lockb.notify()   lockb.wait()......locka.notify()模式去唤醒对方线程而不是己方,但这个程序同步嵌套明显容易发生死锁。我们需要引入新东西,那就是同步的那套设备全都升级了,我们有新的方法去替代旧的。锁不再是任意对象,而是被封装成对象Lock,更符合面向对象思想。
wait(),notify(),notifyAll()也升级成await(),signal()和signalAll(),不再直接关联在锁上,而是把这些监视器方法封装到Condition对象中,Condition通过lock.newConfition()获得

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Resource {
	private String name;
	private int count = 1;
	private boolean flag = false;
	Lock myLock = new ReentrantLock();
	Condition con = myLock.newCondition();

	public void set(String name) {
		myLock.lock();
		try {
			while (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
				try {
					con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			this.name = name + "...." + count++;
			System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
			flag = true;// 生产了要消费,置为true
			con.signalAll();// 唤醒等待的线程
		} finally {
			myLock.unlock();
		}
	}

	public void get() {
		myLock.lock();
		try {
			while (!flag)// 没生产不能消费,等待
				try {
					con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
			flag = false;// 消费完了,去生产
			con.signalAll();// 唤醒等待的线程
		} finally {
			myLock.unlock();
		}
	}
}


上面的代码效果是与先前代码效果完全一样。而且还把方法从锁转移到Condition对象,就没有先前的顾虑,我们创建多个Condition,代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Resource {
	private String name;
	private int count = 1;
	private boolean flag = false;
	Lock myLock = new ReentrantLock();
	Condition producer_con = myLock.newCondition();
	Condition consumer_con = myLock.newCondition();
	public void set(String name) {
		myLock.lock();
		try {
			while (flag)// 为true说明生产者已经生产了,要让生产者去等待,消费者去消费
				try {
					producer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			this.name = name + "...." + count++;
			System.out.println(Thread.currentThread().getName() + "。。。。生产者。。。" + this.name);
			flag = true;// 生产了要消费,置为true
			consumer_con.signal();// 唤醒等待的线程
		} finally {
			myLock.unlock();
		}
	}

	public void get() {
		myLock.lock();
		try {
			while (!flag)// 没生产不能消费,等待
				try {
					consumer_con.await();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			System.out.println(Thread.currentThread().getName() + "。。。。消费者。。。" + this.name);
			flag = false;// 消费完了,去生产
			producer_con.signal();// 唤醒等待的生产者线程
		} finally {
			myLock.unlock();
		}
	}
}

这就是好的解决方案。不过我们可以发现生产一个面包就消费一个,明显不符合实际情况,所以代码可以进一步升级,在API文档Condition示例恰好给出了代码

 class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }
 




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值