文档章节

Java多线程技术

PandaDQ
 PandaDQ
发布于 2016/10/27 17:57
字数 6296
阅读 9
收藏 0

一、技术背景:线程与进程

    现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

    现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

1、进程

    对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

2、线程

有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

3、进程与线程关系:

由于每个进程至少要干一件事,所以,一个进程至少有一个线程。当然,像Word这种复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。当然,真正地同时执行多线程需要多核CPU才可能实现。

总结:

进程是系统分配资源的最小单位,线程是最小的执行单元,而进程由至少一个线程组成,线程分享所属进程的资源。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

二、Java多线程编程

1、实现方式

JAVA多线程实现方式主要有两种种:继承Thread类或者实现Runnable接口。

  • 继承Thread类

            需要继承Thread类,并复写run方法,通过start方法来启动线程。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。实际上Thread就是一个实现了Runnable接口的类。

class ThreadDemo extends Thread{//继承Thread类
	public void run(){//复写run方法
		for(int i=1;i<=20;i++){
			System.out.println(i);
		}		
	}
}
public class Demo
{
	public static void main(String[] agrs){
		String s = "abcdefghijklmnopqrstuvwxyz";
		char[] sArray = s.toCharArray();
		Thread t = new ThreadDemo1();//创建线程对象
		t.start();//使用start方法启动线程
		for(int i=0;i<sArray.length;i++){//主线程循环
			System.out.println(sArray[i]);
		}	
	}
}
/*
运行结果:
a
1
b
2
3
c
d
4
5
e
f
6
7
g
8
h
9
i
10
j
k
11
l
12
13
m
14
n
15
o
16
p
17
q
18
r
19
s
20
t
u
v
w
x
y
z
*/
  • 实现Runnable接口

            实现Runnable接口,并覆盖run方法。 实例化一个Thread实例,并将自己创建的实例传进去,通过Thread实例来启动进程。

class ThreadDemo implements Runnable//实现Runnable接口
{
	public void run(){//复写run方法
		for(int i=1;i<=20;i++){
			System.out.println(i);
		}		
	}
}
public class Demo
{
	public static void main(String[] args) 
	{
		Runnable r = new ThreadDemo();//创建Runable子类对象
		
		Thread t = new Thread(r);//将Runnable子类对象作为参数创建线程对象

		t.start();///启动线程

		String s = "abcdefghijklmnopqrstuvwxyz";
		char[] sArray = s.toCharArray();
		for(int i=0;i<sArray.length;i++){//主线程循环
			System.out.println(sArray[i]);
		}	
	}
}
/*
运行结果:
a
1
b
2
c
3
d
4
5
e
f
6
7
g
h
8
9
i
j
10
k
11
l
12
13
m
n
14
15
o
16
p
17
q
18
r
19
s
20
t
u
v
w
x
y
z
*/

小结:

    实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去处理同一个资源。
  2. 可以避免java中的单继承的限制。
  3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

2、同步

    (1).同步问题的引出

class WithoutSynchronizeCode implements Runnable//实现Runnable接口
{
	private int num = 20;
	public void run(){//复写run方法
		while(true){
			if(num>0){
				try{Thread.sleep(10);}catch(Exception e){}
				System.out.println(Thread.currentThread().getName()+"...."+num--);
					
			}else{
				break;
			}
		}
	}
}
class  WithoutSynchronizeDemo
{
	public static void main(String[] args) 
	{
		Runnable o = new WithoutSynchronizeCode();//创建Runnable子类
		Thread t1 = new Thread(o);//创建线程对象1
		Thread t2 = new Thread(o);//创建线程对象2
		Thread t3 = new Thread(o);//创建线程对象3
		Thread t4 = new Thread(o);//创建线程对象4
		t1.start();//启动线程1
		t2.start();//启动线程2
		t3.start();//启动线程3
		t4.start();//启动线程4
	}
}
/*
运行结果:
Thread-1....20
Thread-3....17
Thread-2....18
Thread-0....19
Thread-1....16
Thread-3....14
Thread-0....15
Thread-2....13
Thread-1....12
Thread-2....9
Thread-3....10
Thread-0....11
Thread-1....8
Thread-3....5
Thread-2....6
Thread-0....7
Thread-2....4
Thread-3....2
Thread-0....3
Thread-1....4
Thread-2....1
Thread-3....-2
Thread-0....-1
Thread-1....0
*/

 打印结果显示既有重复的数字还有负值,这和我们预期的结果并不一致,那么是什么造成这样的结果呢?

这是因为 CPU 在不同的线程间进行切换时的随机性导致的。当 CPU 切换到线程 1的时候,此时num为1,程序判断if语句成立所以进入if代码块中;此时CUP 切换到了线程3也进行了if判断,由于此时num并没有被操作还是1,线程3进入if代码块;此时CUP 又切换到了线程0并进行了if判断,由于此时num并没有被操作还是1,线程0进入if代码块;这时CPU 又切换到线程 1,接着上一次中断的地方继续执行,计算num--,计算后num为0,执行权被剥夺,没有进行打印,紧接着CUP 切换到了线程0进行计算,此时num为-1,执行权被剥夺,没有进行打印,此后线程3页获得了执行权对num进行计算,计算后num为-2并打印结果,后线程0和线程1相继打印结果。正因为这样的原因,当程序中包含有多个线程时,又操作相同的资源时,由于对资源的操作不能一次完成,造成了结果与预期不一样的情况。

(2)同步问题的解决

对于上面的这种情况,我们应该如何解决呢?

在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。

Java提出的解决方案是:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

我们通过在相应代码上添加synchronized关键字,来使相应代码互斥访问,解决以上问题。

class SynchronizeCode implements Runnable//实现Runnable接口
{
	private Object obj = new Object();
	private int num = 20;
	public void run(){//复写run方法
		while(true){
			synchronized(obj){//将块内代码进行互斥访问,obj就是锁
				if(num>0){
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"...."+num--);
					
				}else{
					break;
				}
			}
		}
	}
}
class  SynchronizeDemo
{
	public static void main(String[] args) 
	{
		Runnable o = new SynchronizeCode();//创建Runnable子类
		Thread t1 = new Thread(o);//创建线程对象1
		Thread t2 = new Thread(o);//创建线程对象2
		Thread t3 = new Thread(o);//创建线程对象3
		Thread t4 = new Thread(o);//创建线程对象4
		t1.start();//启动线程1
		t2.start();//启动线程2
		t3.start();//启动线程3
		t4.start();//启动线程4
	}
}
/*
运行结果:
Thread-3....20
Thread-0....17
Thread-2....18
Thread-1....19
Thread-0....16
Thread-3....13
Thread-2....14
Thread-1....15
Thread-1....12
Thread-3....9
Thread-0....10
Thread-2....11
Thread-2....8
Thread-0....5
Thread-3....6
Thread-1....7
Thread-1....4
Thread-0....1
Thread-3....2
Thread-2....3
*/

使用了相同锁对象的同步代码块具有原子性,在进行执行的时候会持续的拥有 CPU 资源直到同步代码块执行完毕,要么继续持有 CPU 资源,要么 CPU 切换到到另一个线程,这样保证了在执行一组代码的时候不会有其他线程插入执行。

当synchronized修饰代码块时,后面需要跟一个参数,此参数就是该同步代码块的锁,必须保证使用的是同一把锁才能保证同步安全性。

synchronized关键字不仅能修饰代码块,同时也能修饰函数。

class SynchronizeCode implements Runnable//实现Runnable接口
{
	private Object obj = new Object();
	private int num = 20;
	public synchronized void run(){//复写run方法,并且用synchronized进行修饰,将该函数进行互斥访问
		while(true){
				if(num>0){
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"...."+num--);
					
				}else{
					break;
				}
		}
	}
}
class  SynchronizeDemo
{
	public static void main(String[] args) 
	{
		Runnable o = new WithoutSynchronizeCode();//创建Runnable子类
		Thread t1 = new Thread(o);//创建线程对象1
		Thread t2 = new Thread(o);//创建线程对象2
		Thread t3 = new Thread(o);//创建线程对象3
		Thread t4 = new Thread(o);//创建线程对象4
		t1.start();//启动线程1
		t2.start();//启动线程2
		t3.start();//启动线程3
		t4.start();//启动线程4
	}
}
/*
运行结果:
Thread-1....20
Thread-2....17
Thread-0....18
Thread-3....19
Thread-1....16
Thread-0....15
Thread-3....13
Thread-2....14
Thread-1....12
Thread-3....9
Thread-2....10
Thread-0....11
Thread-1....8
Thread-3....5
Thread-2....6
Thread-0....7
Thread-1....4
Thread-0....3
Thread-3....2
Thread-2....1
Thread-1....0
Thread-0....-1
*/

但是我们在查看同步函数执行后的结果时发现,运行结果有0 的存在,这显然是不对的,难道说是同步函数有问题?

当然不是,这里就要牵扯出锁的统一性的问题:

同步代码块和同步函数都是互斥访问的,即只有被锁上,其他线程就不能访问该代码,要实现这种互斥访问要求大家使用的是同一把锁,这样才能互斥访问,如果锁不同,造成的就是某线程上锁后,其他线程仍能拿到锁(因为锁不同,好比一个房间有三个门,你锁上了一号门,并不能阻止其他人从2和3号门进入),也就不能实现互斥访问了。但如果是同一把锁,一个线程拿到了,其他线程想访问该代码只能等待拿到锁的线程释放该锁(好比一个房间只有一个门,你锁上了门,其他人就不能进入该房间,只有等这个人把门打开才行)。

同步代码块是我们自己定义的Object对象作为同步锁,保证的锁的统一性。那么同步函数的锁是什么呢?答案是this,对于上面这个代码来说就是线程对象。四个线程对象也就有4个this,这就是造成问题的原因。

总结:

  1. 同步函数使用的锁是this,即synchronized(this){}
  2. 同步函数效率较慢,因为函数每次允许一个线程执行
  3. 静态同步函数和普通同步函数只有锁是不同的,静态同步函数使用的锁为类的字节码,即Class对象

3、线程间通信

我们来看一段生产者消费者的问题代码:

class Product//产品类
{
	Product(){}
	int productIndex = 0;//产品数量
}
class Poducter implements Runnable//生产者,实现Runnable接口
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){//复写run方法
		while(true){
			p.productIndex++;
			try{Thread.sleep(10);}catch(Exception e){};
			System.out.println(Thread.currentThread().getName()+"...product..."+p.productIndex);
		}
	}
}
class Consumer implements Runnable//消费者,实现Runnable接口
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){//复写run方法
		while(true){
			System.out.println(Thread.currentThread().getName()+"...consume..."+p.productIndex);
			try{Thread.sleep(10);}catch(Exception e){};
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//创建Product对象
		Poducter poducter = new  Poducter(p);//创建生产者
		Consumer consumer = new Consumer(p);//创建消费者

		Thread t = new Thread(poducter,"poducter");
		Thread ot = new Thread(consumer,"consumer");

		t.start();//启动线程
		ot.start();//启动线程
	}
}
/*
运行结果:
consumer...consume...1--未生产,先消费
poducter...product...1
consumer...consume...1--重复消费
poducter...product...2--未消费
consumer...consume...3
poducter...product...3
consumer...consume...3
poducter...product...4
consumer...consume...4
poducter...product...5
consumer...consume...5
poducter...product...6
consumer...consume...7
poducter...product...7
consumer...consume...8
poducter...product...8
consumer...consume...8
consumer...consume...9
*/

根据代码运行结果可以看出,现在出现了一些问题,包括一出现了未生产先消费的问题,二、重复消费的问题。问题出现原因和上面的同步的原因是一样的,因为 CPU 在不同的线程间进行切换时的随机性导致的。这里解决这样的问题和上面是一样的,同步加锁,一定要保证锁一致。所以代码修改如下:

class Product//产品类
{
	Product(){}
	int productIndex = 0;//产品数量
}
class Poducter implements Runnable//生产者,实现Runnable接口
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){//复写run方法
		while(true){
			synchronized(p){//同步代码块
				p.productIndex++;
				try{Thread.sleep(10);}catch(Exception e){};
				System.out.println(Thread.currentThread().getName()+"...product..."+p.productIndex);
			}
		}
	}
}
class Consumer implements Runnable//消费者,实现Runnable接口
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){//复写run方法
		while(true){
			synchronized(p){//同步代码块
				System.out.println(Thread.currentThread().getName()+"...consume..."+p.productIndex);
				try{Thread.sleep(10);}catch(Exception e){};
			}
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//创建Product对象
		Poducter poducter = new  Poducter(p);//创建生产者
		Consumer consumer = new Consumer(p);//创建消费者

		Thread t = new Thread(poducter,"poducter");
		Thread ot = new Thread(consumer,"consumer");

		t.start();//启动线程
		ot.start();//启动线程
	}
}
/*
运行结果:
poducter...product...612
poducter...product...613
poducter...product...614
poducter...product...615
poducter...product...616
poducter...product...617
poducter...product...618
poducter...product...619
consumer...consume...619
consumer...consume...619
poducter...product...620
poducter...product...621
poducter...product...622
poducter...product...623
poducter...product...624
poducter...product...625
poducter...product...626
poducter...product...627
poducter...product...628
poducter...product...629
*/

在加入同步以后没有出现先消费后生产的问题,但是这个结果也并不是我们想要的,我们想要的是一个生产然后消费的情况,而不是随机生产,然后重复消费的情况。
想实现正常的单生产-单消费消费就需要就引入线程通信。

Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义(wait()、notify()、notifyAll()方法要求在调用时线程已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法或synchronized块中。)。

  • wait()

            调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到其他的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。

            wait()方法在声明的时候被声明为抛出InterruptedException异常。

            与sleep方法的区别:

                    ●    当线程调用了wait()方法时,它会释放掉对象的锁。

                    ●    Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。  

  • notify()

            调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。

  • notifyAll()

                调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。

根据上面提供的方法,我们把程序调整如下:

class Product//产品类
{
	Product(){}
	int productIndex = 0;
	boolean flag = false;//是否已生产
}
class Poducter implements Runnable//实现Runnable接口的生产者
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			synchronized(p){
				while(p.flag){
					try{p.wait();}catch(Exception e){};//判断如果已生产,则生产者等待
				}
				p.productIndex++;
				try{Thread.sleep(10);}catch(Exception e){};
				System.out.println(Thread.currentThread().getName()+"...product..."+p.productIndex);
				p.flag = true;//将已生产置为true
				try{p.notify();}catch(Exception e){};//唤醒等待线程
			}
		}
	}
}
class Consumer implements Runnable//实现Runnable接口的消费者
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			synchronized(p){
				while(!p.flag){
					try{p.wait();}catch(Exception e){};//判断如果没有生产,则消费者等待
				}
				System.out.println(Thread.currentThread().getName()+"...consume..."+p.productIndex);
				try{Thread.sleep(10);}catch(Exception e){};
				p.flag=false;//消费完后将生产标识置为false
				try{p.notify();}catch(Exception e){};//唤醒等待线程
			}
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//产品
		Poducter poducter = new  Poducter(p);//生产者
		Consumer consumer = new Consumer(p);//消费者

		Thread t = new Thread(poducter,"poducter");
		Thread ot = new Thread(consumer,"consumer");

		t.start();//启动线程
		ot.start();//启动线程
	}
}
/*
运行结果:
poducter...product...1
consumer...consume...1
poducter...product...2
consumer...consume...2
poducter...product...3
consumer...consume...3
poducter...product...4
consumer...consume...4
poducter...product...5
consumer...consume...5
poducter...product...6
consumer...consume...6
poducter...product...7
consumer...consume...7
*/

我们根据运行结果可以看到,已经实现生产-消费的模式了。但这只是每个任务单个线程,我们看一下一个任务多个线程的情况:

class Product//产品类
{
	Product(){}
	int productIndex = 0;
	boolean flag = false;//是否已生产
}
class Poducter implements Runnable//实现Runnable接口的生产者
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			synchronized(p){
				while(p.flag){
					System.out.println(Thread.currentThread().getName()+"...product...wait");
					try{p.wait();}catch(Exception e){};//判断如果已生产,则生产者等待
				}
				p.productIndex++;
				try{Thread.sleep(10);}catch(Exception e){};
				System.out.println(Thread.currentThread().getName()+"...product..."+p.productIndex);
				p.flag = true;//将已生产置为true
				try{p.notify();}catch(Exception e){};//唤醒等待线程
			}
		}
	}
}
class Consumer implements Runnable//实现Runnable接口的消费者
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			synchronized(p){
				while(!p.flag){
					System.out.println(Thread.currentThread().getName()+"...consume...wait");
					try{p.wait();}catch(Exception e){};//判断如果没有生产,则消费者等待
				}
				System.out.println(Thread.currentThread().getName()+"...consume..."+p.productIndex);
				try{Thread.sleep(10);}catch(Exception e){};
				p.flag=false;//消费完后将生产标识置为false
				try{p.notify();}catch(Exception e){};//唤醒等待线程
			}
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//产品
		Poducter poducter = new  Poducter(p);//生产者
		Consumer consumer = new Consumer(p);//消费者
		Thread pt1 = new Thread(poducter,"poducter1");//生产线程1
		Thread pt2 = new Thread(poducter,"poducter2");//生产线程2
		Thread ct1 = new Thread(consumer,"consumer1");//消费线程1
		Thread ct2 = new Thread(consumer,"consumer2");//消费线程2
		pt1.start();//启动线程
		pt2.start();//启动线程
		ct1.start();//启动线程
		ct2.start();//启动线程
	}
}
/*
运行结果:
poducter1...product...1
poducter1...product...wait
consumer2...consume...1
consumer2...consume...wait
consumer1...consume...wait
poducter2...product...2
poducter2...product...wait
consumer2...consume...2
consumer2...consume...wait
consumer1...consume...wait
poducter1...product...3
poducter1...product...wait
poducter2...product...wait
死锁状态
*/

上面的的运行结果在正常运行后进入了死锁状态。

死锁,是指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。

上面这个程序进入死锁的的原因主要是四个进程都进入等待状态,无法被唤醒。

这里我们使用notifyAll()方法,防止生产后唤醒生产,或者消费后唤醒消费造成死锁的产生。修改代码如下:

import java.util.concurrent.locks.*;
class Product//产品类
{
	Product(){}
	int productIndex = 0;
	boolean flag = false;//已生产
	public void product(){
		synchronized(this){
			while(flag){
				//System.out.println(Thread.currentThread().getName()+"...product...wait");
				try{this.wait();}catch(Exception e){};//判断如果已生产,则生产者等待
			}
			try{Thread.sleep(100);}catch(Exception e){};
			productIndex++;
			System.out.println(Thread.currentThread().getName()+"...product..."+productIndex);
			flag = true;//将已生产置为true
			try{this.notifyAll();}catch(Exception e){};//唤醒等待线程
		}
	}
	public void consume(){
		synchronized(this){
			while(!flag){
				//System.out.println(Thread.currentThread().getName()+"...consume...wait");
				try{this.wait();}catch(Exception e){};//判断如果没有生产,则消费者等待
			}
			try{Thread.sleep(100);}catch(Exception e){};
			System.out.println(Thread.currentThread().getName()+"...consume..."+productIndex);
			flag=false;//消费完后将生产标识置为false
			try{this.notifyAll();}catch(Exception e){};//唤醒等待线程
		}
	}
}
class Poducter implements Runnable//实现Runnable接口的生产者
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			p.product();
		}
	}
}
class Consumer implements Runnable//实现Runnable接口的消费者
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			p.consume();
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//产品
		Poducter poducter = new  Poducter(p);//生产者
		Consumer consumer = new Consumer(p);//消费者
		Thread pt1 = new Thread(poducter,"poducter1");//生产线程1
		Thread pt2 = new Thread(poducter,"poducter2");//生产线程2
		Thread ct1 = new Thread(consumer,"consumer1");//消费线程1
		Thread ct2 = new Thread(consumer,"consumer2");//消费线程2
		pt1.start();//启动线程
		pt2.start();//启动线程
		ct1.start();//启动线程
		ct2.start();//启动线程
	}
}
/*
运行结果:
poducter1...product...1
consumer2...consume...1
poducter1...product...2
consumer2...consume...2
poducter2...product...3
consumer1...consume...3
poducter1...product...4
consumer2...consume...4
poducter2...product...5
consumer1...consume...5
*/

4、JDK5之后提供的Lock、Condition

    JDK1.5以后使用了新的技术取代synchronized这种方式,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

    Lock接口:Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。 

Lock下常用方法:

  • lock() 
  • unlock()
  • newCondition() 

示例:

Lock l = ...; 
l.lock();
try {
  // access the resource protected by this lock
} finally {
  l.unlock();
}

    Condition接口:Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用。

Condition下常用方法:

  • await() 
  • signal() 
  • signalAll() 

通过新技术我们上面的代码可以改写为:

import java.util.concurrent.locks.*;
class SynchronizeCode implements Runnable//实现Runnable接口
{
	private Object obj = new Object();
	private int num = 200000;
	Lock lock = new ReentrantLock();//新建lock子类对象
	public void run(){//复写run方法
		while(true){
			try{
				lock.lock();//利用lock()方法进行加锁
				if(num>0){
					try{Thread.sleep(10);}catch(Exception e){}
					System.out.println(Thread.currentThread().getName()+"...."+num--);
					
				}else{
					break;
				}
			}catch(Exception e ){
				System.out.println("Lock实现可能能够检测到锁的错误使用可能抛出一个异常。Lock 实现必须对环境和异常类型进行记录。");
			}finally{
				lock.unlock();//利用unlock()方法进行解锁
			}
		}
	}
}
class  SynchronizeDemo
{
	public static void main(String[] args) 
	{
		Runnable o = new SynchronizeCode();//创建Runnable子类
		Thread t1 = new Thread(o);//创建线程对象1
		Thread t2 = new Thread(o);//创建线程对象2
		Thread t3 = new Thread(o);//创建线程对象3
		Thread t4 = new Thread(o);//创建线程对象4
		t1.start();//启动线程1
		t2.start();//启动线程2
		t3.start();//启动线程3
		t4.start();//启动线程4
	}
}

而在解决同步问题时,由于一把锁可以绑定多个监视器,我们可以根据实际情况,灵活的创建监视器:

import java.util.concurrent.locks.*;
class Product//产品类
{
	Product(){}
	int productIndex = 0;
	boolean flag = false;//已生产
	Lock lock = new ReentrantLock();
	Condition conp = lock.newCondition();
	Condition conc = lock.newCondition();
	public void product(){
		lock.lock();//加锁
		try{
			
			while(flag){
				//System.out.println(Thread.currentThread().getName()+"...product...wait");
				//try{p.wait();}catch(Exception e){};//判断如果已生产,则生产者等待
				try{conp.await();}catch(Exception e){};
			}
			try{Thread.sleep(100);}catch(Exception e){};
			productIndex++;
			System.out.println(Thread.currentThread().getName()+"...product..."+productIndex);
			flag = true;//将已生产置为true
			//try{p.notifyAll();}catch(Exception e){};//唤醒等待线程
			conc.signal();//唤醒一个消费者线程
		//}
		}catch(Exception e){
			System.out.println("Lock 实现可能能够检测到锁的错误使用,比如会导致死锁的调用,在那种环境下还可能抛出一个 (unchecked) 异常。Lock 实现必须对环境和异常类型进行记录。");
		}finally{
			lock.unlock();
		}
	}
	public void consume(){
		//synchronized(p){
		lock.lock();
		try{
			while(!flag){
				//System.out.println(Thread.currentThread().getName()+"...consume...wait");
				//try{p.wait();}catch(Exception e){};//判断如果没有生产,则消费者等待
				try{conc.await();}catch(Exception e){};
			}
			try{Thread.sleep(100);}catch(Exception e){};
			System.out.println(Thread.currentThread().getName()+"...consume..."+productIndex);
			flag=false;//消费完后将生产标识置为false
			//try{p.notifyAll();}catch(Exception e){};//唤醒等待线程
			conp.signal();//唤醒一个消费者线程
		//}
		}catch(Exception e){
			System.out.println("Lock 实现可能能够检测到锁的错误使用,比如会导致死锁的调用,在那种环境下还可能抛出一个 (unchecked) 异常。Lock 实现必须对环境和异常类型进行记录。");
		}finally{
			lock.unlock();
		}
	}
}
class Poducter implements Runnable//实现Runnable接口的生产者
{	
	Product p;
	Poducter(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			p.product();
		}
	}
}
class Consumer implements Runnable//实现Runnable接口的消费者
{	
	Product p;
	Consumer(Product p){
		this.p = p;
	}
	public void run(){
		while(true){
			p.consume();
		}
	}
}
class  Demo
{
	public static void main(String[] args) 
	{
		Product p = new Product();//产品
		Poducter poducter = new  Poducter(p);//生产者
		Consumer consumer = new Consumer(p);//消费者
		Thread pt1 = new Thread(poducter,"poducter1");//生产线程1
		Thread pt2 = new Thread(poducter,"poducter2");//生产线程2
		Thread ct1 = new Thread(consumer,"consumer1");//消费线程1
		Thread ct2 = new Thread(consumer,"consumer2");//消费线程2
		pt1.start();//启动线程
		pt2.start();//启动线程
		ct1.start();//启动线程
		ct2.start();//启动线程
	}
}
/*
运行结果:
poducter1...product...1
consumer2...consume...1
poducter1...product...2
consumer2...consume...2
poducter2...product...3
consumer1...consume...3
poducter1...product...4
consumer2...consume...4
poducter2...product...5
consumer1...consume...5
*/

 

© 著作权归作者所有

下一篇: package
PandaDQ
粉丝 1
博文 8
码字总数 17848
作品 0
济南
程序员
私信 提问
你分得清分布式、高并发与多线程吗?

当提起这三个词的时候,是不是很多人都认为分布式=高并发=多线程? 当面试官问到高并发系统可以采用哪些手段来解决,或者被问到分布式系统如何解决一致性的问题,是不是一脸懵逼? 确实,在一...

宋庆离
01/17
0
0
Java多线程可以分组,还能这样玩!

前面的文章,栈长和大家分享过多线程创建的3种方式《实现 Java 多线程的 3 种方式》。 但如果线程很多的情况下,你知道如何对它们进行分组吗? 和 Dubbo 的服务分组一样,Java 可以对相同性质...

Java技术栈
2018/11/09
0
0
JNI 的多线程

之前的博文中讲述了JNI的基础知识: Java 类型和C/C++类型的转换 cygwin + gcc+makeFile入门(三): JNI的编译 这两篇文章讲述了JNI最普遍的两个问题, 环境的建立以及参数的传递. JNI作为连接J...

晨曦之光
2012/03/09
1K
0
Java面试无非也就这几个知识点,大家是否都掌握了

Java语言的关键点 掌握静态方法和属性 重视接口 学好集合框架 例外捕捉 多线程需要理解机理(多线程原理和多线程安全) 了解网络编程 不需要精通,掌握以下知识点,面试基本没有问题。 这里没有...

土豆宝
2016/08/22
9.4K
37
多线程 start 和 run 方法到底有什么区别?

昨天栈长介绍了《Java多线程可以分组,还能这样玩!》线程分组的妙用。今天,栈长会详细介绍 Java 中的多线程 start() 和 run() 两个方法,Java 老司机请跳过,新手或者对这两个不是很理解的...

Java技术栈
2018/11/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Java编程学习-Java虚拟机(JVM)以及跨平台原理

相信大家已经了解到Java具有跨平台的特性,可以“一次编译,到处运行”,在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的。 那么,跨平台是怎样实现的呢?这...

Java领航员
32分钟前
0
0
学JFinal不迷路,JFinal优质资源列表(欢迎反馈更新)

学JFinal不迷路,记录一下JFinal相关的资源、产品、讲师等信息(所有信息排名不分先后)。 一、相关站点: 1、JFinal官网-问答、分享、文档、交流、俱乐部 http://www.jfinal.com 2 、JFina...

山东-小木
35分钟前
1
0
项目学习(2)-order-job

在当前的系统中,因为并发量并不高,服务之间发起异步请求或者异步调用时,没有使用到消息中间件。 而是在各个服务(子系统)的数据库中,创建了event_queue事件对列表和event_handler事件处...

太猪-YJ
44分钟前
1
0
gradle grovvy中的闭包

1. 无参数的闭包 //这b1就是一个闭包def b1={ println "hello b1"}//定义方法,包含闭包类型的参数def method1(Closure closure){closure()}//执行method1method1(b1) 执行结果 ...

edison_kwok
今天
3
0
基于Spring Boot + Dubbo的全链路日志追踪(一)

一、 概要 当前公司后端整体架构为:Spring Boot + Dubbo。由于早期项目进度等原因,对日志这块没有统一的规范,基本上是每个项目自己管自己的日志。这也对后面的问题排查带来了很大的困难,...

明天以后
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部