Java-并发入门
本文由@呆代待殆原创,转载请注明出处:http://www.cnblogs.com/coffeeSS/
Java中实现多线程的方法
实现Runnable接口
实现Runnable接口里的run()方法,并将这个实例提交给一个Thread构造器,最后调用Thread.start()就可以启动一个线程。
1 public class MyRunnable implements Runnable { 2 String name; 3 public MyRunnable(String name){ 4 this.name=name; 5 } 6 @Override 7 public void run() { 8 for(int i=0;i<5;++i){ 9 System.out.println(name+" 第 "+i+" 次运行"); 10 try { 11 Thread.sleep(1000); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 } 16 System.out.println(name+" 运行完毕停机"); 17 } 18 19 }
1 public class Test { 2 public static void main(String[] args){ 3 new Thread(new MyRunnable("0号机")).start(); 4 new Thread(new MyRunnable("1号机")).start(); 5 } 6 }
继承Thread
直接继承Thread类,重写run()方法,调用Thread.start()即可
1 public class MyThread extends Thread { 2 public static void main(String[] args) { 3 MyThread zero=new MyThread("0号机"); 4 MyThread one =new MyThread("1号机"); 5 zero.start(); 6 one.start(); 7 } 8 String name; 9 public MyThread(String name){ 10 this.name=name; 11 } 12 public void run(){ 13 for(int i=0;i<5;++i){ 14 System.out.println(name+" 第 "+i+" 次运行"); 15 try { 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 System.out.println(name+" 运行完毕停机"); 22 } 23 }
线程的状态
线程有五个状态。
1、初始状态
Thread aThread = new Thread();
2、就绪状态
Thread aThread = new Thread();
Thread.start();
3、阻塞状态
suspend()
sleep()
wait()
输入输出流发生阻塞。
线程同步时试图锁住另一个线程锁住的对象。
...
4、运行状态
run()方法正在执行中。
5、死亡状态
stop(),或非预期的异常终止run()方法,线程突然死亡。
run()正常退出,线程自然死亡。
线程相关的常用方法简介(都有相应的在线API链接)
start()
//启动线程,调用run()方法。
run()
//一般我们需要重写这个方法给线程安排要执行的任务。
interrupt()
//中断这个线程。
yield()
//会让调用这个方法的线程睡眠。
wait()
//让调用此方法的线程阻塞。
wait与sleep是不同的,调用了sleep 的线程仍然会占用cpu,且不会释放已经拿到的锁,但是不会有任何动作,调用了wait的线程不会占用cpu,会让出已经占用的锁。
sleep(long millis)
//让当前线程睡眠millis毫秒
sleep(long millis, int nanos)
//让当前线程睡眠millis毫秒nanos纳秒。
notify()
//随机唤醒一个阻塞状态的线程。
notifyAll()
//唤醒所有阻塞状态的线程。
setPriority(int newPriority)
//用来设置线程的优先级JDK提供了10个优先级所以newPriority的取值是1-10,其中10是最高优先级。
注意大多数操作系统并不能和这10个优先级很好的映射,比如window只有7个优先级,而Sun的Solaris有231个优先级,所以要想使优先级的设置操作是可移植的,最好用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这三个预定义的常数来设置优先级。
getPriority()
//返回线程优先级。
toString()
//返回线程的名字、优先级和线程组。
setName(String name)
//设置线程的名称。
getName()
//获得线程的名字。
getId()
//获得线程的identifier
setDaemon(boolean on)
//标识是否是一个守护线程。
join()
//当前线程会被挂起,等待调用这个方法的目标线程执行结束后再被唤醒(如:你在某个线程里写t.join(),则当前线程会挂起,t线程开始运行,t线程返回后,当前线程继续执行)。
join(long millis)
//作用同上,但是如果millis毫秒后目标线程还没返回的话,目标线程会被强制中断,然后返回当前线程。
join(long millis, int nanos)
//同上,只是时间更精确了。
isAlive()
//测试这个线程是否还活着。
interrupted()
//测试这个线程是否被中断。
关于线程的同步
synchronized关键字
synchronized有两种用法,用来修饰方法和用来修饰代码块。
1,修饰方法
synchronized关键字修饰方法的时候,这个方法只能被一个线程调用,当第二个线程想同时访问的时候,它将被阻塞直到第一个线程从方法返回。
如果一个对象中有很多个方法都被synchronized关键字修饰,由于它们是共享同一把锁的,也就是说,对其中某一个方法进行调用,将使得所有synchronized方法都不能被这个线程以为的其他线程调用。
这里要注意类的每一个实例都有一个自己的对象锁,一个实例里面的synchronized方法被访问会导致这个实例内其他synchronized方法不能访问,但是不会导致这个类的其他实例的synchronized方法不能用。
2,修饰代码块
修饰代码块的时候synchronized可以指定需要获取的锁,而且可以让同步的代码块更加精确。
关于锁,每个类有一个类锁,每个类的实例有一个对象锁,这两把锁互相不干扰。
当synchronized修饰static方法或者修饰的代码块指定的锁为XXX.class的话,将获得类锁,类锁管理所有的static方法的访问,获取类锁后,其他类的实例将无法访问所有的static方法,但是非static方法即使是用synchronized修饰的也可以访问。同理,即使获得对象锁后将使得其他非static的synchronized修饰的方法无法访问,但是static方法可以被访问,当然同一个对象可以同时获得者两把锁。
另外类锁只是一个抽象概念,并不存在真正的类锁,这里的类锁是代表着这个类的对象的对象锁。
使用Lock对象进行并发控制。
使用Lock对象的时候要注意有良好的格式,如下(图片来自网络)
lock.lock()方法将会让线程获得锁,如果这个时候有别的线程来取锁,则会陷入阻塞状态。
lock.unlock()方法会释放锁,写在finally里的原因是为了让任何情况下,锁都能得到释放,这里要强调的是return语句要写在try里面,以确保unlock()不会过早的发生。
使用Lock与使用synchronized的区别在于,如果使用synchronized,如果线程没有得到锁,它将一直等待下去,如果使用Lock,通过调用tryLock方法可以实现在失败的时候中断等待去干别的事情。详细情况如下。
tryLock()
//如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。
tryLock(long timeout, TimeUnit unit)
//如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false。
效率上,在资源竞争不是很激烈的情况下,synchronized的性能要更好,否则synchronized的性能会下降的很快,而Lock的性能一直很稳定。
死锁简介
当以下4个条件同时满足的时候就会发生死锁
1,互斥条件:存在不能共享的资源。
2,不可抢占条件:资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放。
3,请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
4,环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
对于java线程的锁机制来说,互斥条件和不可抢占条件先天满足,所以我们能做到的就是不要让条件3和条件4也都满足就可以了。具体细节这里不做讨论。
管道流用于线程间通信
管道流的应用非常简单,相关的类有四个
用于字节流的PipedInputStream与PipedOutputStream
用于字符流的PipedReader与PipedWriter
它们都是配套使用的,用于两个线程间的通信,并且通信是单向的。
使用的基本流程如下,
1,创建对应的输出流和输入流。
2,用connect方法将它们连接起来,然后就可以使用了。
3,调用close方法释放资源。
输入流的read方法在没有更多数据的时候会自动阻塞。
另外用管道流进行读写的时候必须保证相对应的两个线程都不能退出,也不能阻塞。否则就会报java.io.IOException: Write/Read end dead 的错误。
下面给出一个实例,用PipedReader与PipedWriter实现两个线程间的通信,程序会不停的打印一段文字。
1 public class TestPiped { 2 PipedWriter pWriter; 3 PipedReader pReader; 4 5 public TestPiped() { 6 pWriter = new PipedWriter(); 7 try { 8 pReader = new PipedReader(pWriter); 9 } catch (IOException e) { 10 e.printStackTrace(); 11 } 12 new Thread(new MyReader(pReader)).start(); 13 new Thread(new MyWriter(pWriter)).start(); 14 } 15 16 public static void main(String[] args) { 17 new TestPiped(); 18 } 19 } 20 21 class MyReader implements Runnable { 22 PipedReader pReader; 23 24 public MyReader(PipedReader pReader) { 25 this.pReader = pReader; 26 } 27 28 @Override 29 public void run() { 30 while (true) { 31 char c; 32 int i; 33 while (true) { 34 try { 35 i = pReader.read(); 36 if (i == -1) 37 break; 38 c = (char) i; 39 System.out.print(c); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } 43 } 44 System.out.println(); 45 notifyAll(); 46 } 47 } 48 } 49 50 class MyWriter implements Runnable { 51 PipedWriter pWriter; 52 53 public MyWriter(PipedWriter pWriter) { 54 this.pWriter = pWriter; 55 } 56 57 @Override 58 public void run() { 59 while (true) { 60 try { 61 pWriter.write(new String("番茄,番茄,这里是西红柿,收到请回答\n")); 62 Thread.sleep(2000); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 }
参考资料:
1,《java编程思想》中文 第四版
2,在线java API http://tool.oschina.net/apidocs/apidoc?api=jdk_7u4