上一篇多线程java
文章摘要 |
---|
对比继承和实现方法,线程安全与线程同步及其方法 |
对比继承Thread和实现Runable两种方法android
1.java中类是单继承的,若是继承Thread,该类就不能有其余父类了
2.从操做上看,继承方式更简单
3.但没法作到多线程共享同一资源web
1.java可实现多接口,该类还可继承其它父类
2.从操做上看,实现方法更复杂,获取线程名称也更复杂(Thread.currentThread())
3.能作到共享同一资源安全
解释一下共享同一资源:
假设我有50个苹果,有A,B,C三我的同时吃苹果
继承:多线程
public class Xianc { public static void main(String[] args) { new eatapple("A").start(); new eatapple("B").start(); new eatapple("C").start(); } } class eatapple extends Thread{ private int num = 50; public eatapple(String name) { super(name); } public void run() { for(int i = 1;i<=50;i++) { if(num>0) { System.out.println(super.getName()+"吃了"+num--+"个苹果");//获取名称这与实现的不一样 } } } }
实现并发
public class Xianc { public static void main(String[] args) { eatapple e = new eatapple(); new Thread(e,"A").start(); new Thread(e,"B").start(); new Thread(e,"C").start(); } } class eatapple implements Runnable{ private int num = 50; public void run() { for(int i = 1;i<=50;i++) { if(num>0) { System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果"); } } } }
运行后的继承方法控制台输出的是ABC各吃了50个苹果。
而使用实现接口方法,控制台输出的是ABC一共吃了50个苹果app
当多线程并发的访问同一个资源对象时,可能出现线程不安全的状况
举例:svg
public class Xianc { public static void main(String[] args) { eatapple e = new eatapple(); new Thread(e,"A").start(); new Thread(e,"B").start(); new Thread(e,"C").start(); } } class eatapple implements Runnable{ private int num = 50; public void run() { for(int i = 1;i<=50;i++) { if(num>0) { try { Thread.sleep(10); //使线程不安全的现象更加明显,并非sleep()方法致使的线程不安全 }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果"); } } } }
运行后发现:(截取有表明性的一部分)
发现B和C同时吃了49号和48号的苹果。
缘由:上述代码每一个线程的操做分为两部分:
1.展现手上拿到的苹果编号
2.再吃掉苹果(苹果数减一)
而这两部分未能同步进行。
解决方法:
1.同步代码块:
2.同步方法:
3.锁机制:性能
格式:this
syncronized(同步锁) { 须要同步的代码 }
代码以下:
public void run() { for(int i = 1;i<=50;i++) { synchronized (this) { if(num>0) { try { Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果"); } } } }
(使用synchronized修饰的方法叫同步方法,保证A线程执行该方法时,其余线程只能在外边等着)
格式:synchronized public void doWork(){ //TODO }
代码以下
public void run() { //不能用synchronized修饰run方法 for(int i = 1;i<=50;i++) { eat(); } } //定义一个用用synchronized修饰的新方法,并由run方法调用 synchronized private void eat() { if(num>0) { try { Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"吃了"+num--+"个苹果"); } } }
同步锁:
对于非static方法,同步锁是this;
对于static方法,同步锁是当前方法所在的字节码对象(Apple2.class)
使用synchronized的利弊:
好处:保证了多线程并发访问的同步操做,避免了线程的安全性问题
缺点:用synchronized修饰的代码块性能低
建议:尽可能减小synchronized的做用域
Lock机制提供了比synchronized代码块和synchronized方法更普遍的锁定操做,上述两种方法具备的功能Lock都有,并更增强大,更能体现面向对象思想
格式:
class X{ private final ReentrantLock lock = new ReentrantLock(); //...... public void m(){ //m是须要同步的方法 lock.lock(); //实例(第一个lock)调用lock()方法 try{ }catch(){ }finally{ lock.unlock(); //必需要有的步骤,关闭锁! } } }
代码以下
public class Xianc { public static void main(String[] args) { eatapple e = new eatapple(); new Thread(e,"小A").start(); new Thread(e,"小B").start(); new Thread(e,"小C").start(); //经过实例化eatapple建立三个线程 } } class eatapple implements Runnable{ private int num = 50; //苹果数量 private final Lock l = new ReentrantLock(); //建立一个锁对象 public void run() { for(int i = 1;i<=50;i++) { eat(); } } private void eat() { l.lock(); try { if(num>0) { System.out.println(Thread.currentThread().getName()+"吃了"+num+"个苹果"); Thread.sleep(10); num--; } }catch(InterruptedException e){ e.printStackTrace(); }finally { l.unlock(); } } }