线程类Java多线程要点分析总结
发一下牢骚和主题无关:
1:继承Thread类跟实现Runnable接口
实现Runnable接口的优点:
解脱单继承的局限
可以实现资源共享
来看一下Thread类的部份源码:
public class Thread implements Runnable { private Runnable target; // What will be run. public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); //Initializes a Thread. } public void run() { //覆写了run方法 if (target != null) { target.run(); } } public synchronized void start() { if (threadStatus != 0) // 如果线程已经启动,就抛出异常 throw new IllegalThreadStateException(); group.add(this); start0(); // 调用本地方法启动线程 if (stopBeforeStart) { stop0(throwableFromStop); } } private native void start0(); }
API中对Runnable接口的说明:计划该接口的目的是为希望在活动时执行代码的对象供给一个公共协议。例如,Thread 类实现了 Runnable。
多线程编程中,涉及到两个类((多线程代码封装类,Runnable接口实现类),Thread类(可以启动新线程)),
上面结合上面Thread类的源码来分析一下继承Thread类跟实现实现Runnable接口的小区分:
继承Thread类:我们的类会覆写Thread类的run方法,并把线程执行代码封装进run方法,然后调用Thread类的start方法启动线程。
要注意的是,Thread类也实现了Runnable接口,这就是为什么我们能把Thread类当成多线程代码封装类来应用的原因,因为它能去做这件事。
但这不是他的本职工作,顶多算扩展功能。他的实质工作是启动新线程。
实现Runnable接口:在线程对象初始化的时候通过构造方法传入Runnable接口实现类,然后Thread类的start方法会调用run方法,
run方法中判断target不即是null,于是执行的是我们Runnable接口实现类中的run方法。这也就是为什么能实现对象资源共享的原因,因为可以操纵同一个对象。
有很多人说实现Runnable接口来创立线程对象,应用了代理模式。我们来看一下吧,
代理模式的定义:一个角色代表另一个角色来实现某些特定的功能
代理模式有三个角色: 1. 抽象主题角色 2. 代理主题角色 3. 实际被代理角色,如果应用了静态代理模式,那么对应的角色如下:
这里的Runnable接口作为抽象主题角色供给一个公共协议,Thread类作为主题角色实现了该协议,我们的Runnable实现类作为实际被代理角色也实现了该协议。
如果是代理模式,那么我们对被代理角色的访问应当可所以Runnable thread = new Thread(),然后thread.run方法执行被Thread类代理的方法。
但在这里,这样调用对于多线程来说是没有意义的,不会启动新的线程,仅仅就是普通方法调用,抛弃多线程,那么这里的确是静态代理模式。
这对于Thread类来说,他是通过Thread t = new Thread(); t.start(); start方法再去调用run方法。仅仅就是多态模式下的方法调用罢了。
Thread类之所以实现Runnable接口,是为了本身的功能扩展。上面已经分析过了。即使不实现Runnable接口,一样可以应用第二种方式来创立新线程对象,那这还属于代理模式吗?
2:多线程同步问题
问题的引出:通过实现Runnable接口的方式来创立新线程处理了多条线程数据共享问题。那么项目在实际运行的过程中,
由于操纵过程可能不仅仅是对数据进行单纯的修改操纵,还可能有对数据的判断操纵,临时缓存,数据库操纵等,
在这些操纵过程中,因为网络延时,线程切换等就会触发数据覆盖,判断异常等问题。
例A<数据覆盖问题>:
class DoSomething { private int a = 10; public void add(int b) { int temp = a; // 缓存现有值 temp += b; // 缓存累加后的值 try { Thread.sleep(100); // 模拟处置数据的时间或者线程切换。 } catch (Exception e) { } a = temp; // 模拟写入操纵。 System.out.println(getA()); // 输出当前线程对a进行累加操纵后的值 } public int getA() { return this.a; } } public class ThreadDemo { private static Thread[] threads = new Thread[10]; public static void main(String[] args) { final DoSomething ds = new DoSomething(); for (int i=0; i<10; i++) { // 开启10条线程进行操纵 threads[i] = new Thread(new Runnable(){ @Override public void run() { ds.add(1); } }); threads[i].start(); } } }
视察以上程序,发现10个线程每次写入之后,a都即是11,10个线程写了10次,应当+10即是20啊?
为什么是11呢?详细分析一下:
比如A线程进入add方法开始操纵数据(将累加的数据缓存给局部变量temp),实现了数据的缓存,还没修改a的值,A线程进入休眠状态,
然后B线程进入add方法开始操纵数据(将累加的数据缓存给局部变量temp),实现了数据的缓存,还没修改a的值,B线程进入休眠状态,
然后A线程从新进入运行状态,实现了数据的修改操纵,a=11;B线程进入运行状态,实现了对数据的修改操纵,a=11。
哪里有问题?应当出来了吧?局部变量temp的值被后来的b线程覆盖掉了。所以,即使10个线程执行add方法,每次temp都是11。
处理方式:
对add方法进行同步。修改后,public synchronized void add(int b),这样便应用同步,处理了以上问题。输出如下:
例B<判断异常问题>
class DoSomething { private int ticket = 50; public void sell() { for (int i=0; i<50; i++) { if (ticket > 0) { // 还有票 try { Thread.sleep(100); // 模拟处置数据的时间或者线程切换。 } catch (Exception e) {} System.out.println(Thread.currentThread().getName() + "卖出第" + ticket-- + "张票"); } } } } public class ThreadDemo { public static void main(String[] args) { final DoSomething ds = new DoSomething(); for (int i=0; i<3; i++) { // 开三条线程开始卖票 new Thread(new Runnable(){ @Override public void run() { ds.sell(); } }).start(); } } }
以上程序最后输出了-1;在哪出问题了呢?在判断ticket>0的时候,比如当初ticket=1,A线程进入判断大于0,然后休眠了。
B线程进入判断大于0,执行了ticket--,ticket成0了,A线程回到运行状态,执行了ticket--,ticket成-1了。
处理方法,对sell方法进行同步,或者对if判断应用同步代码块进行同步。
我们实现线程安全的方式是互斥同步,即应用原语synchronized进行同步。
Synchronized关键字:
编译后会在同步块前后分别构成monitorenter和monitorexit这两个字节码指令。
这两个指令都需要一个引用类型的参数来指明要锁定和解锁的对象。如果没有明白指定对象参数,
那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。
在执行monitorenter指令时,首先实验获取对象的锁,如果没有被锁定或者当前线程已经具有了该对象的锁,则将锁计数器加1,
相应的执行moniterexit时,将锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象锁失败,则当前线程就要阻塞等待。
3:多线程死锁问题
死锁就是两个或两个以上的线程被无限的阻塞,线程之间互相等待所需资源。
可能发生在以下情况:
当两个线程互相调用Thread.join();
当两个线程应用嵌套的同步块,一个线程占用了另外一个线程必须的锁,互相等待时被阻塞就有可能出现死锁。
写一个死锁出来:
class MyLock { static final Object lockA = new Object(); static final Object lockB = new Object(); } class DoSomething implements Runnable { private boolean flag; public DoSomething(boolean flag) { this.flag = flag; } @Override public void run(){ if (flag) { synchronized(MyLock.lockA) { System.out.println("锁住A,我要进B"); synchronized(MyLock.lockB) { System.out.println("MyLock.lockB"); } } } else { synchronized(MyLock.lockB) { System.out.println("锁住B,我要进A"); synchronized(MyLock.lockA) { System.out.println("MyLock.lockA"); } } } } } public class ThreadDemo2 { public static void main(String[] args) { new Thread(new DoSomething(true)).start(); new Thread(new DoSomething(false)).start(); } }
4:出产者花费者
出产者不断出产,花费者不断花费。
class Person { private String name; private char sex; private boolean flag; public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setSex(char sex) { this.sex = sex; } public char getSex() { return this.sex; } public void setFlag(boolean flag) { this.flag = flag; } public boolean getFlag() { return this.flag; } } /* 出产者 */ class Producer implements Runnable { private Person per; public Producer(Person per) { this.per = per; } public void run() { // 出产者不断出产内容 int i = 0; while(true) { synchronized(per) { if(per.getFlag()) { // 如果为真就等待 try { per.wait(); // 等待线程 } catch (Exception e) { } } else { if(i == 0) { per.setName("西门庆"); per.setSex('男'); } else { per.setName("潘金莲"); per.setSex('女'); } System.out.println(Thread.currentThread().getName() + "号线程开始出产:" + per.getName() + " - " + per.getSex()); i = (i+1)%2; per.setFlag(true); per.notify(); // 唤醒等待线程 } } } } } /* 花费者 */ class Consumer implements Runnable { private Person per; public Consumer(Person per) { this.per = per; } public void run() { // 花费者不断取走内容 while(true) { synchronized(per) { if(!per.getFlag()) { // 如果为假 try { per.wait(); // 等待线程 } catch (Exception e) { } } else { System.out.println("\t" + Thread.currentThread().getName() + "号线程开始花费:" + per.getName() + " - " + per.getSex()); per.setFlag(false); per.notify(); } } } } } /** * 出产者与花费者 线程通信问题 应用等待唤醒机制 */ public class ThreadCommunication { public static void main(String[] args) { Person per = new Person(); Producer prod = new Producer(per); Consumer cons = new Consumer(per); new Thread(prod).start(); // 启动线程开始出产 new Thread(cons).start(); // 启动线程开始花费 } }
5:模拟银行存取案例
业务分析:
模拟银行中多个线程同时对一个储蓄账户进行存款,取款操纵。
Account:个人账户
Bank:取款,存款,查询余额
class Account { private String name; private double amount; public Account(String name, double amount) { this.name = name; this.amount = amount; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setAmount(double amount) { this.amount = amount; } public double getAmount() { return this.amount; } } class Bank { private Account account; public Bank(Account account) { this.account = account; } // 存钱 public synchronized void deposit(double amount) { double tmp = account.getAmount(); // 缓存现有金额 tmp += amount; // 缓存累加之后的金额 try { Thread.sleep(100); // 模拟处置需要的时间 } catch (Exception e) { } account.setAmount(tmp); // 写入操纵 System.out.println(getBalance()); } // 取钱 public synchronized void withdraw(double amount) { double tmp = account.getAmount(); // 缓存现有金额 tmp -= amount; try { Thread.sleep(100); // 模拟处置需要的时间 } catch (Exception e) { } account.setAmount(tmp); // 写入操纵 } // 查询余额 public String getBalance() { return account.getName() + "---" + account.getAmount(); } } public class ThreadDemo { private static int NUM_OF_THREAD = 10; static Thread[] threads = new Thread[NUM_OF_THREAD]; public static void main(String[] args) { final Bank bank = new Bank(new Account("张三",3000.00d)); for (int i=0; i<NUM_OF_THREAD; i++) { threads[i] = new Thread(new Runnable(){ @Override public void run() { bank.deposit(100.00d); bank.withdraw(100.00d); } }); threads[i].start(); } for (int i=0; i<NUM_OF_THREAD; i++) { try { threads[i].join(); } catch (Exception e) {} } System.out.println("余额:" + bank.getBalance()); } }
其它常用方法:
object.wait()
wait()方法用在同步的代码块里,当wait()被执行时,锁会被释放,当前线程进入等待队列。
只有该对象的锁被释放,并不会释放当前线程持有的其他同步资源。
如果当前线程在等待之前或在等待时被任何线程中断,则会抛出InterruptedException。
object.notify()
方法notify()同样用在同步的代码块里。唤醒在此对象锁上等待的单个线程。此方法只能由具有该对象锁的线程来调用。
notify()将通知等待相同锁的线程,如果有多个线程在等待这个锁,将从中随机选择一个进行通知。
从锁的等待队列里面随机唤醒某一个线程,唤醒之后的线程进入就绪队列,等待被选择执行。
Thread.sleep()
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操纵受到系统计时器和调度程序精度和准确性的影响。
监控状态依然保持、会自动恢复到可运行状态,不会释放对象锁。如果任何线程中断了当前线程。
当抛出InterruptedException异常时,当前线程的中断状态被清除。让出CPU分配的执行时间。
不推荐在同步方法中调用sleep方法,原因你懂的,不懂看上面例子就知道了吧,那叫没事找事。
thread.join():在一个线程对象上调用,使当前线程等待这个线程对象对应的线程结束。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
interrupt()
使当前线程的状态变为中断线程,中断一个不处于活动状态的线程不会有任何作用。
如果线程在调用Object类的wait()方法,或者join()、sleep()方法过程中受阻(被中断),则其中断状态将被清除,并收到一个InterruptedException。
Thread.interrupted():检测当前线程是否已经中断,并且清除线程的中断状态(回到非中断状态)。
isAlive():如果线程已经启动且尚未终止,则为活动状态。
setDaemon():需要在start()方法调用之前调用。当正在运行的线程都是后台线程时,Java虚拟机将退出。否则当主线程退出时,其他线程仍然会继续执行。
原创文章,转载请注明出处:http://blog.csdn.net/thinking_in_android
文章结束给大家分享下程序员的一些笑话语录:
人工智能今天的发展水平:8乘8的国际象棋盘其实是一个体现思维与创意的强大媒介。象棋里蕴含了天文数字般的变化。卡斯帕罗夫指出,国际象棋的合法棋步共有1040。在棋局里每算度八步棋,里面蕴含的变化就已经超过银河系里的繁星总数。而地球上很少有任何数量达到这个级别。在金融危机之前,全世界的财富总和大约是1014人民币,而地球人口只有1010。棋盘上,所有可能的棋局总数达到10120,这超过了宇宙里所有原子的总数!经典语录网