标签:android style blog http java color
- - - - - android培训、java培训、期待与您交流! - - - - - -
进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。进程负责的是应用程序的空间的标示。
线程:其实就是进程中一个程序执行控制单元,一条执行路径。线程负责的是应用程序的执行顺序。
jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数,主线程执行的代码都在main方法中。当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样主线程中的代码执行会停止,而去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。
随机性的原理:哪个线程获取到了cpu的执行权,哪个线程就执行,实质是cpu的快速切换造成。
返回当前线程的名称:Thread.currentThread().getName();线程的名称是由:Thread-编号定义的。编号从0开始。线程要运行的代码都统一存放在了run方法中。
线程要运行必须要通过类中指定的方法【start方法】开启。(启动后,就多了一条执行路径)
start方法:
创建线程的第一种方式:继承Thread ,由子类复写run方法:
步骤:
class Show extends Thread { public void run() { for (int i =0;i<5 ;i++ ) { System.out.println(name +"_" + i); } } public Show(){} public Show(String name) { this.name = name; } private String name; public static void main(String[] args) { new Show("csdn").start(); new Show("黑马").start(); } }
【可能的运行结果】:
为什么我们不能直接调用run()方法呢?原因是线程的运行需要本地操作系统的支持。
查看start的源代码发现:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
这个方法用了native关键字,native表示调用本地操作系统的函数,多线程的实现需要本地操作系统的支持。
线程状态:
创建线程的第二种方式:实现一个接口Runnable:
步骤:
Ticket t = new Ticket();
直接创建Ticket对象,并不是创建线程对象。【因为创建线程对象只能通过new Thread类,或者new Thread类的子类才可以】
Thread t1 = new Thread(t); //创建线程。
只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联。【为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法】
t1.start();//开启线程
为什么要有Runnable接口的出现?
1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。
可是该类中的还有部分代码需要被多个线程同时执行,这时怎么办呢?
只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。
所以,通常创建线程都用第二种方式。【因为实现Runnable接口可以避免单继承的局限性】
2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。
所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。
实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。
new Thread(new Runnable(){ //匿名 public void run() { System.out.println("runnable run"); } }) { public void run() { System.out.println("subthread run"); } }.start(); //结果:subthread run
Try { Thread.sleep(10); }catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu 切换情况。
Thread和Runnable的区别:
如果一个类继承Thread,则不能资源共享(有可能是操作的实体不是唯一的);但是如果实现了Runable接口的话,则可以实现资源共享。
class Show implements Runnable { private int count = 10;//假设有10张票 @Override public void run() { for (int i = 0; i < 5 ; i++ ) { if (this.count > 0) { System.out.println(Thread.currentThread().getName()+"正在卖票" + this.count--); } } } public static void main(String[] args) { Show s = new Show(); //注意必须保证只对1个实体s操作 new Thread(s,"窗口1").start(); new Thread(s,"窗口2").start(); new Thread(s,"窗口3").start(); } }
实现Runnable接口比继承Thread类所具有的优势:
设置线程优先级
Thread t = new Thread(myRunnable); t.setPriority(Thread.MAX_PRIORITY);//一共10个等级,Thread.MAX_PRIORITY表示最高级10 t.start();
//MAX_PRIORITY : 其值是 10
//MIN_PRIORITY : 其值是 1
//NORM_PRIORITY: 其值是 5
提示:主线程的优先级是5,不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源。
控制线程的方法:
package heimablog; // 定义Runnable 接口的实现类 public class JoinTest implements Runnable { // 重写run() 方法 public void run() { for (int i = 0; i <= 10; ++i) System.out.println(Thread.currentThread().getName() + "..." + i); } public static void main(String args[]) throws InterruptedException { // 创建Runnable 接口实现类的实例 JoinTest t = new JoinTest(); // 通过 Thread(Runnable target) 创建新线程 Thread thread = new Thread(t); Thread thread1 = new Thread(t); // 启动线程 thread.start(); // 只有等thread 线程执行结束,main 线程才会向下执行; thread.join(); System.out.println("thread1 线程将要启动"); thread1.start(); } }
这三个方法都必须用synchronized块来包装,而且必须是同一把锁,不然会抛出java.lang.IllegalMonitorStateException异常。
多线程安全问题的原因:
发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据,导致了错误数据的产生。
涉及到两个因素:
如下面程序:
package heimablog; /* * 多个线程同时访问一个数据时,出现的安全问题。 * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票 */ class Ticks implements Runnable { private int ticks = 100 ; public void run() { while (ticks > 0) { //加入sleep 方法是为了更明显的看到该程序中出现的安全问题。 try{Thread.sleep(10);}catch (Exception e) {} System.out.println(Thread.currentThread().getName() +"...卖出了第"+ticks+"张票"); ticks -- ; } } } public class ShowTest { public static void main(String args[]) { //创建 Runnable 实现类 Ticks 的对象。 Ticks t = new Ticks() ; //开启4个线程处理同一个 t 对象。 new Thread(t , "一号窗口").start() ; new Thread(t , "二号窗口").start() ; new Thread(t , "三号窗口").start() ; new Thread(t , "四号窗口").start() ; } }
运行的结果会出现"一号窗口...卖出了第0张票,四号窗口...卖出了第-1张票,二号窗口...卖出了第-2张票"这样的安全问题。
解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
解决这类问题的方法:
1、同步代码块。
2、同步函数。
同步代码块
synchronized(obj) // 任意对象都可以,这个对象就是锁。 { //此处的代码就是同步代码块,也就是需要被同步的代码; }
synchronized 后括号里面的 obj 就是同步监视器。代码含义:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。即只有获得对同步监视器的锁定的线程可以在同步中执行,没有锁定的线程即使获得执行权,也不能在同步代码块中执行。
注意:虽然JAVA 程序允许使用任何对象来作为同步监视器。但是还是推荐使用可能被并发访问的共享资源来充当同步监视器。
修改代码如下:
package heimablog; /* * 多个线程同时访问一个数据时,出现的安全问题。 * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票 */ class Ticks implements Runnable { private int ticks = 100; public void run() { while (ticks > 0) { synchronized (Ticks.class) { if (ticks > 0) { try { Thread.sleep(10); } catch (Exception e) {} System.out.println(Thread.currentThread().getName() + "...卖出了第" + ticks + "张票"); ticks--; } } } } } public class ShowTest { public static void main(String args[]) { // 创建 Runnable 实现类 Ticks 的对象。 Ticks t = new Ticks(); // 开启4个线程处理同一个 t 对象。 new Thread(t, "一号窗口").start(); new Thread(t, "二号窗口").start(); new Thread(t, "三号窗口").start(); new Thread(t, "四号窗口").start(); } }
加入同步监视器之后的程序就不会出现数据上的错误了。
虽然同步监视器的好处是解决了多线程的安全问题。但也也因为多个线程需要判断锁,较为消耗资源。
同步前提:
如果加入了synchronized 同步监视器,还出现了安全问题,则可以按照如下步骤找寻错误:
同步函数
把 synchronized 作为修饰符修饰函数。则该函数称为同步函数。
注意:同步函数无需显示的指定同步监视器,函数都有自己所属的对象this,同步函数的同步监视器是this,也就是该对象本身。
注意:synchronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等。
上面通过模拟火车卖票系统的小程序,通过加入 synchronized 同步监视器,来解决多线程中的安全问题。下面模拟银行取钱问题,通过同步函数来解决多线程的安全问题。
package heimablog; class Account { // 账户余额 private double balance; public Account(double balance) { this.balance = balance; } // get和set方法 public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } // 同步函数 // 提供一个线程安全的draw的方法来完成取钱操作。 public synchronized void Draw(double drawAmount) { if (balance >= drawAmount) { System.out.println(Thread.currentThread().getName() + "取钱成功!吐出金额" + drawAmount); try { Thread.sleep(10); } catch (Exception e) { } balance -= drawAmount; System.out.println("卡上余额:" + balance); } else { System.out.println(Thread.currentThread().getName() + "取钱失败!卡上余额:" + balance); } } } class DrawThread implements Runnable { // 模拟账户 private Account account; // 希望所取钱的金额 private double drawAmount; private boolean flag = true; public DrawThread(Account account, double drawAmount) { this.account = account; this.drawAmount = drawAmount; } // 当前取钱 public void run() { try { while (flag) { // 调用取钱函数 account.Draw(drawAmount); } } catch (Exception e) { flag = false; } } } public class ShowTest { public static void main(String args[]) { Account account = new Account(10000); DrawThread draw = new DrawThread(account, 50); Thread t = new Thread(draw, "A....."); Thread t1 = new Thread(draw, "B."); t.start(); t1.start(); } }
同步方法的监视器是 this ,因此对于同一个 Account 而言,任意时刻只能有一条线程获得 Account 对象的锁定。
提示:可变类的线程安全是以降低运行程序的运行效率作为代价,为了减少线程安全所带来的负面影响,程序可以采用如下策略:
当如下情况发生时会释放同步监视器的锁定:
当同步函数被static修饰时,这时的同步用的是哪个锁呢?
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是 类名.class
同步代码块和同步函数的区别?
在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。
死锁
当两线程相互等待对方释放锁时,就会发生死锁。由于JVM没有监测,也没有采用措施来处理死锁,所以多线程编成时应该采取措施来避免死锁。
单例模式之懒汉式
懒汉式:延迟加载方式。
当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作,所以容易出现线程安全问题。
class Single { private static Single s = null; private Single() {
} public static Single getInstance() { if (s == null) { synchronized (Single.class) {//用字节码文件对象作为锁; if (s == null) s = new Single(); } } return s; } }
同步死锁:通常只要将同步进行嵌套,就可以看到现象。
线程通信
思路:多个线程在操作同一个资源,但是操作的动作却不一样。
模拟生产消费者:系统在有两条线程,分别代表生成者和消费者。
程序的基本流程:
通过上诉流程要了解:生成者和消费者不能连续生成或消费商品,同时消费者只能消费已经生产出的商品。
package heimablog; class Phone { // 定义商品的编号 private int No; // 定义商品的名字 private String name; private boolean flag = true; // 初始化商品的名字和编号,同时编号是自增的 public Phone(String name, int No) { this.name = name; this.No = No; } // 定义商品中的消费方法和生产方法。用synchronized 修饰符修饰 public synchronized void Production() { // 导致当前线程等待,知道其他线程调用notify()或notifyAll()方法来唤醒 // if (!flag) while (!flag) try { this.wait(); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ":生产" + name + ";编号为:" + ++No); // 唤醒在此同步监视器上等待的单个线程。 // this.notify() ; // 唤醒在此同步监视器上等待的所有线程。 this.notifyAll(); flag = false; } public synchronized void Consumption() { // if(flag) while (flag) try {this.wait();} catch (Exception e) {} System.out.println(Thread.currentThread().getName() + ";消费商品:" + name + "商品的编号为" + No); // this.notify() ; this.notifyAll(); flag = true; } } class ProducerThread implements Runnable { Phone phone; private boolean flag = true; // 同步监视器的对象 public ProducerThread(Phone phone) { this.phone = phone; } public void run() { try { while (flag) phone.Production(); } catch (Exception e) { flag = false; } } } class ConsumptionThread implements Runnable { Phone phone; private boolean flag = true; // 同步监视器的对象 public ConsumptionThread(Phone phone) { this.phone = phone; } public void run() { try { while (flag) phone.Consumption(); } catch (Exception e) { flag = false; } } } public class ShowTest { public static void main(String args[]) { Phone phone = new Phone("iPhone 5", 0); new Thread(new ProducerThread(phone), "生成者000").start(); new Thread(new ProducerThread(phone), "生成者111").start(); new Thread(new ConsumptionThread(phone), "消费者000").start(); new Thread(new ConsumptionThread(phone), "消费者111").start(); } }
上面的程序中:flag 标志位 是判断 是由生产者生成还是由消费者进行消费。其实,在现实生活中,不可能只有一个生成者和消费者,而是多个生成者和消费者。所以用 while 循环来进行 flag 的判断 , 而不是用 if 。如果用if 容易出现线程安全问题;而且在用while 循环进行flag的判断时,则必须用 notifyAll() 方法来唤醒同步监视器中所有等待中的线程,而不是 用notify() 方法。用notify() 则会导致所有线程进入等待状态。
上面的小程序借助Object 类提供的 wait()、notify()、notifyAll 三个方法【等待唤醒机制涉及的方法】
注意:
wait和sleep区别
分析这两个方法从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。
wait:线程会释放执行权,而且线程会释放锁。
Sleep:线程会释放执行权,但不是不释放锁。
线程的停止【stop方法已过时】
原理:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。
Thread‘s functions
同步锁LOCK
JDK 1.5之后,JAVA提供了另外一种线程同步机制:显示定义同步锁来实现同步,解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。同步锁应该使用Lock对象充当。在面向对象中谁拥有数据谁就对外提供操作这些数据的方法 ,发现获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
class X { // 定义锁对象 private final ReentrantLock lock = new ReentrantLock(); // 定义需要保证线程安全的方法 public void m() { // 加锁 lock.lock(); try { // 需要保证线程安全的代码 } finally { lock.unlock(); } } }
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装,并提供了功能一致的方法 await()、signal()、signalAll().
Condition接口:await()、signal()、signalAll();
Condition 实例实质上被绑定在一个Lock 对象上。如:
//定义锁对象 private final ReentrantLock lock = new ReentrantLock() ; //指定Lock 对象对应的条件变量 private final Condition condition = lock.newCondition() ;
标签:android style blog http java color
原文地址:http://www.cnblogs.com/iadanac/p/3828822.html