Java中线程(Thread)的知识很重要,没有它,咱们项目中的不少功能都没法实现。跟线程有关的是进程,平常生活中咱们听的比较多的是进程,一般咱们的电脑卡了,咱们就会说要杀进程。进程跟线程是不一样的概念,二者有区别也有联系。进程,通俗的讲就是咱们电脑中运行中的程序,程序的概念是静态的,进程是动态的概念。像咱们电脑运行的视频播放器,音乐播放器都是进程。线程,是运行在进程中的顺序控制流,只能使用分配给进程的资源和环境。一个进程中至少会有一个线程。java
了解线程的相关概念后,咱们如今来将如何实现线程。线程的实现方式有两种。一种是经过继承Thread类,并重写run()方法实现;另外一种是经过实现Runnable接口并实现其run()方法。下面经过例子来分析两种实现的区别。算法
一、经过继承Thread类编程
1 package thread; 2 /** 3 * 4 * @author CIACs 5 *线程的生成经过继承Thread类实现 6 */ 7 public class ThreadTest { 8 public static void main(String[] args) { 9 MyThread1 t1 = new MyThread1(); 10 t1.start(); 11 MyThread2 t2 = new MyThread2(); 12 t2.start(); 13 } 14 15 } 16 17 class MyThread1 extends Thread 18 { 19 20 @Override 21 public void run() { 22 for(int i=0;i<50;i++) 23 { 24 System.out.println("MyThread1 running: "+i); 25 } 26 } 27 } 28 class MyThread2 extends Thread 29 { 30 @Override 31 public void run() { 32 for(int i=0;i<50;i++) 33 { 34 System.out.println("MyThread2 running: "+i); 35 } 36 } 37 }
控制台输出结果:多线程
在这里咱们能够看到两个线程会交叉执行,并非一个先执行完后,另外一个再执行。这就是说当线程启动后咱们是不能控制执行顺序的。(固然是在还没用synchronized、wait()、notify()的时候)dom
二、经过实现Runnable接口ide
1 package thread; 2 /** 3 * 4 * @author CIACs 5 *线程经过实现Runnable接口生成 6 */ 7 public class ThreadTest2 { 8 public static void main(String[] args) { 9 Thread1 t1 = new Thread1(); 10 new Thread(t1).start(); 11 Thread2 t2 = new Thread2(); 12 new Thread(t2).start(); 13 } 14 15 } 16 17 class Thread1 implements Runnable 18 { 19 @Override 20 public void run() { 21 for(int i=0;i<50;i++) 22 { 23 System.out.println("Thread1 running "+i); 24 } 25 26 } 27 } 28 29 class Thread2 implements Runnable 30 { 31 @Override 32 public void run() { 33 for(int i=0;i<50;i++) 34 { 35 System.out.println("Thread2 running "+i); 36 } 37 38 } 39 }
控制台输出结果:学习
在这里我只上传了部分结果的截图,咱们所要的在这部分截图中就能够看出了。this
在编写程序时咱们把但愿线程执行的代码放到run()方法中,而后经过调用start()方法来启动线程,start()方法会为线程的执行准备好资源,以后再去调用run()方法,当某个类继承了Thread类后,该类就是线程类。线程的消亡不能经过调用stop()方法,而是让run()方法天然结束。spa
每一个线程有其优先级,最高为10(MAX_PRIORITY),最低为1(MIN_PRIORITY),设置优先级是为了在多线程环境中便于系统对线程的调度,同等状况下,优先级高的会先比优先级低的执行。固然操做系统也不是彻底按照优先级高低执行的,不然有可能优先级低的会一直处于等待状态,操做系统有本身的调度算法(这里就先不展开讨论了)。当运行中的线程调用了yield()方法,就会让出cpu的占用;调用sleep()方法会使线程进入睡眠状态,此时其余线程也就能够占用cpu资源了;有另外一个更高优先级的线程出现也会致使运行中的线程让出cpu资源。有些调度算法是分配固定的时间片给线程执行,在这段时间内能够占用cpu,一旦用完就必须让出cpu资源。操作系统
线程的生命周期有以下四个
一、建立状态,当用new建立一个新的线程对象时,该线程处于建立状态,但此时系统没有分配资源给它。
二、可运行状态,执行线程的start()方法后系统将会分配线程运行所需的系统资源,并调用线程体run()方法,此时线程处于可运行状态。
三、不可运行状态,当线程调用了sleep()方法,或者wait()方法时,线程处于不可运行状态,线程的输入输出阻塞时也会致使线程不可运行。
四、消亡状态,当线程的run()方法执行结束后,就进入消亡状态。
下图是普通线程的状态转换图:
当使用了synchronized关键字时,线程的状态转换图会有点不一样,以下图
控制多线程同步,使用wait()和notify()的线程状态转换图以下:
对于单核cpu来讲,某一时刻只能有一个线程在执行,但在宏观上咱们会看到多个进程在执行,这就是微观串行,宏观上并行。如今单核的电脑基本上已经没有了。多核的电脑就能够实现微观并行。多线程编程就是为了最大限度的利用cpu资源。例如当某一个线程和外设打交道时,此时它不须要用到cpu资源,但它仍然占着cpu,其余的线程就不能利用,多线程编程就能够解决该问题。多线程是多任务处理的一种特殊形式。可是多线程可能会形成冲突,两个或多个线程要同时访问一个共同资源。
下面以银行取款为例,有一个帐号,里面存1000元,而后建立两个线程模拟从银行柜台和ATM同时取700元。这里咱们的银行卡不能透支。
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class BankAccount { 8 private int MyMoney = 1000; 9 10 public void getMoney(int money) 11 { 12 if(MyMoney<=0||(MyMoney-money)<0) 13 { 14 System.out.println("余额不足"); 15 } 16 else 17 { 18 try { 19 Thread.sleep((long)(Math.random()*1000)); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println("取"+money); 24 MyMoney = MyMoney-money; 25 } 26 System.out.println("帐户剩余的钱"+MyMoney); 27 } 28 }
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Thread1 extends Thread{ 8 private BankAccount MyBank; 9 public Thread1(BankAccount ba) 10 { 11 this.MyBank = ba; 12 } 13 @Override 14 public void run() { 15 16 MyBank.getMoney(700); 17 } 18 19 }
1 package thread; 2 3 public class Client { 4 public static void main(String[] args) { 5 BankAccount MyBank = new BankAccount(); 6 Thread1 t1 = new Thread1(MyBank); 7 Thread1 t2 = new Thread1(MyBank); 8 t1.start(); 9 t2.start(); 10 11 } 12 }
控制台输出结果:
咱们取了1400,帐户余额未-400,很明显银行是不容许咱们这样作的。这就说,可能当一个线程进入到取钱代码部分时先进行了睡眠,另外一个也进来了,且也进入睡眠。当其中一个醒来时,取完钱后,另外一个也醒来继续取钱。咱们如何解决这个问题呢?这时咱们就要用synchronized关键字来解决。只需在取钱的方法处加上synchronized关键字修饰就能够解决该问题。
加上后控制台输出结果:
synchronized关键字是同步的意思,当synchronized修饰一个方法时,该方法叫作同步方法。Java中的每一个对象都有一个锁(lock)或者叫监视器(monitor),当咱们使用synchronized关键字修饰一个对象的方法时,就会把这个对象上锁,当该对象上锁时,其余的线程就没法再访问该对象的方法,直到线程结束或抛出异常,该对象的锁就会释放掉。看似在方法前加上synchronized关键字修饰好像完美的解决了多线程访问同一资源的冲突问题,可是因为synchronized是粗粒度的,也就是使用了该关键字会把方法所对应的类也会锁上,该类的任何其余方法都不会被其余线程所访问,这样就致使了资源利用率下降。为了解决这个方法,咱们仍是要利用synchronized关键字。此时synchronized关键字锁的是咱们建立的任何一个对象。synchronized块代码以下
1 private obj = new Object(); 2 synchronized(obj) 3 { 4 //线程要执行的代码 5 }
此时咱们锁的是咱们建立的一个任一对象,synchronized块外面的方法是没有被锁的,也就是说其余线程能够访问synchronized块外面的方法。
wait()跟notify()方法能够实现线程的等待和唤醒操做,这两个方法都是定义在Object类中的,并且是final的,所以会被全部的java类所继承,且没法重写。调用这两个方法要求线程已经得到了对象的锁,所以会把这两个方法放在synchronized方法或块中,且是成对出现的。执行wait()时,线程会释放对象的锁,还有一个方法也会使线程暂停执行,那就是sleep()方法,不过该方法不会释放对象的锁。
生产者,消费者问题在每一本学习线程的书上都会有提到,大概就是说生产者生产了商品就通知消费者消费商品,当消费者消费了商品酒通知生产者生产商品,这里涉及到了线程间的通讯。咱们用输出0和1来模拟生产者消费者问题。当number为1时就要减一,当number为0时就要加一。要解决该问题就要用wait()跟notify()方法结合synchronized的使用。
Number类
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Number 8 { 9 private int number = 1; 10 public synchronized void AddNumber() 11 { 12 if(number > 0) 13 { 14 try 15 { 16 //当不符合条件时进入等待状态,让出cpu资源 17 wait(); 18 } catch (InterruptedException e) 19 { 20 e.printStackTrace(); 21 } 22 } 23 //执行加操做 24 number++; 25 System.out.println(number); 26 //唤醒减操做的线程 27 notify(); 28 } 29 30 public synchronized void SubNumber() 31 { 32 if(number == 0) 33 { 34 try 35 { 36 //等于0时进入等待状态 37 wait(); 38 } catch (InterruptedException e) 39 { 40 e.printStackTrace(); 41 } 42 } 43 //执行减操做 44 number--; 45 System.out.println(number); 46 //唤醒加操做的线程 47 notify(); 48 } 49 }
AddThread类
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class AddThread extends Thread 8 { 9 //设置线程执行的标志,初始为true 10 boolean flag = true; 11 private Number number; 12 public AddThread(Number number) 13 { 14 this.number = number; 15 } 16 //改变判断标志,使线程中止 17 public void setFlag() 18 { 19 this.flag = false; 20 } 21 22 @Override 23 public void run() 24 { 25 while(flag) 26 { 27 try 28 { 29 Thread.sleep((long)(Math.random()*1000)); 30 this.number.AddNumber(); 31 } catch (InterruptedException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 } 37 }
SubThread类
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class SubThread extends Thread 8 { 9 //设置线程执行的标志,初始为true 10 boolean flag = true; 11 private Number number; 12 public SubThread(Number number) 13 { 14 this.number = number; 15 } 16 //改变标志的值,使线程中止 17 public void setFlag() 18 { 19 this.flag = false; 20 } 21 22 @Override 23 public void run() 24 { 25 while(flag) 26 { 27 try 28 { 29 Thread.sleep((long)(Math.random()*1000)); 30 this.number.SubNumber(); 31 } catch (InterruptedException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 } 37 }
客户端
1 package thread; 2 /** 3 * 4 * @author CIACs 5 * 6 */ 7 public class Client 8 { 9 public static void main(String[] args) 10 { 11 Number num = new Number(); 12 AddThread t1 = new AddThread(num); 13 SubThread t2 = new SubThread(num); 14 t2.start(); 15 t1.start(); 16 try 17 { 18 //过了三秒后使其中一个线程中止,一个线程中止后,另外一个也不能执行,由于另外一个线程处于等待状态。 19 Thread.sleep((long)(Math.random()*3000)); 20 t1.setFlag(); 21 22 } catch (InterruptedException e) 23 { 24 e.printStackTrace(); 25 } 26 27 } 28 }
控制台输出结果:
在这个例子中我利用flag标志来控制线程的中止和执行,咱们不会用stop()方法去控制线程的中止。
在线程的使用中咱们还要注意的是若是线程中有控制的是局部变量则每一个线程对局部变量的改变互不影响,若是是成员变量则会影响到其余线程。使用好线程,咱们能提升作事的效率。