一:线程与进程
1 线程:进程中负责程序执行的执行单元
线程本身依靠程序进行运行
线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
2 进程:执行中的程序
一个进程至少包含一个线程
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程
4 多线程:在一个程序中运行多个任务
目的是更好地使用CPU资源
二:线程的实现
1:继承Thread类
1 package Threads; 2 3 public class Thread_one extends Thread{ 4 5 @Override 6 public void run() { 7 // TODO Auto-generated method stub 8 System.out.println(Thread.currentThread().getName()); 9 } 10 }
我们继承Thread类,然后重写run()方法,写入你要执行的操作;
线程了类是创建好了,下面就是使用这个线程类:
1 package Threads; 2 3 public class Test1 { 4 public static void main(String[] args) { 5 for (int i = 0; i < 10; i++) { 6 new Thread_one().start(); 7 } 8 } 9 }
正确的启动线程的方法是start()方法,而不是使用run()方法,下面具体说明 ;
运行的结果:
我们可以发现多次运行结果是不一样的:
下面我们把启动改为run()试一试;
1 package Threads; 2 3 public class Test1 { 4 public static void main(String[] args) { 5 for (int i = 0; i < 10; i++) { 6 new Thread_one().run(); 7 } 8 } 9 }
多次运行发现结果都是一样的,发现运行的都是main这个主线程,而没有创建新的线程:
总结 run()和start()的区别:
①:首次正确启动一个线程是使用start()方法;
②:run()方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。
③:start()在调用start()方法后,线程就进入了就绪的状态,其实也就是一个可运行的状态,什么时候运行是根据cpu的调度的,看这个线程是否抢到了cpu的资源,所以说我们执行多个线程多次执行的顺序结果是不一样的;
2:实现Runnable
1 package Threads; 2 3 public class Thread_two implements Runnable{ 4 5 @Override 6 public void run() { 7 // TODO Auto-generated method stub 8 System.out.println(Thread.currentThread().getName()); 9 } 10 11 }
继承Thread和实现Runnable实现一个线程过程都差不多;
调用启动有些区别:
1 package Threads; 2 3 public class Test2 { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 for (int i = 0; i < 10; i++) { 8 new Thread(new Thread_two()).start(); 9 } 10 } 11 }
我们查看Thread源码发现,他需要一个Runnable类型的对象;
我们附给他我们实现Runnable接口的对象
运行发现实现了效果:
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):可以避免java中的单继承的限制
四:线程的不同状态及转换:
解析:
新建状态:
只是创建了这个线程对象。
就绪状态:
该状态的线程位于可运行线程池中,变得可运行,等争抢到cpu资源后就才会真正的进入到可运行的状态;
运行状态:就绪状态的线程获取了CPU,执行程序代码。
阻塞状态:
①:wait();
只能用 notify唤醒 或者notifyall
②:sleep
定义毫秒数,到时间自动唤醒,继续执行任务;
③:yield
线程的礼让,让正在执行的线程变成可运行状态,然后在重新争抢cpu。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
④:join加入主线程;
让正在运行的主线程停止,然后先运行这个线程。
1 package Threads; 2 3 public class Test3 { 4 public static void main(String[] args) { 5 System.out.println("开始:"); 6 Thread3 t2=new Thread3(); 7 Thread t=new Thread(t2); 8 t.start(); 9 System.out.println("结束:"); 10 } 11 }
这明显不是我们想要的结果:
使用join加入主线程:
1 package Threads; 2 3 public class Test3 { 4 public static void main(String[] args) throws InterruptedException { 5 System.out.println("开始:"); 6 Thread3 t2=new Thread3(); 7 Thread t=new Thread(t2); 8 t.start(); 9 t.join(); 10 System.out.println("结束:"); 11 } 12 }
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
总结:
sleep和wait的区别:
1. 所属的层面不同:Thread类的方法:sleep() Object的方法:wait()。
2.wait停止的线程则需要使用notifyall()和notifi()唤醒;
3. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
4. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 ;
5. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
五:线程常用的方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
六:线程的同步:
常用的同步方法就是使用synchronized关键字
为什么要使用线程同步,因为当一个资源可能会有多个线程访问的时候,当第一个线程进来之后对资源进行了修改,但是在修改的过程中第二个线程也进来了,那第二个线程取到的数据就不是最新的数据,是个虚假的数据,那么这个线程所执行的操作都没有意义了;
看个小例子:
1 package Threads; 2 3 public class Thread3 implements Runnable{ 4 private static int a=10; 5 @Override 6 public void run() { 7 // TODO Auto-generated method stub 8 try { 9 Tong(); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 public static void Tong() throws InterruptedException{ 16 for (int i = 0; i < 10; i++) { 17 System.out.println(Thread.currentThread().getName()+"||||"+(--a)); 18 Thread.sleep(1000); 19 } 20 } 21 } 22 23 24 25 26 package Threads; 27 28 public class Test3 { 29 public static void main(String[] args) throws InterruptedException { 30 System.out.println("开始:"); 31 Thread3 t2=new Thread3(); 32 Thread t=new Thread(t2); 33 Thread t3=new Thread(t2); 34 t.start(); 35 t3.start(); 36 t.join(); 37 t3.join(); 38 System.out.println("结束:"); 39 } 40 }
里面的sleep就是模拟让两个线程同时访问一个资源
运行结果为:
我们可以看到是有重复数据的,也就是虚假的数据 thread-0和thread-1同时访问了count:7这个数据;
解决办法就是使用同步方法:
1 package Threads; 2 3 public class Thread3 implements Runnable{ 4 private static int a=10; 5 @Override 6 public void run() { 7 // TODO Auto-generated method stub 8 try { 9 Tong(); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 public static synchronized void Tong() throws InterruptedException{ 16 for (int i = 0; i < 10; i++) { 17 System.out.println(Thread.currentThread().getName()+"||||"+(--a)); 18 Thread.sleep(1000); 19 } 20 } 21 }
加上synchronized 关键字
总结:其实同步就是对这个资源对象就行加锁,只能同时有一个线程能操作这个资源,如果有第二个线程要访问,那只有等第一个线程执行结束之后才能进行访问;
七:实现经典多线程例子生产者,消费者:
首先创建商品类
1 package Threads; 2 3 public class Piao { 4 private int count=5; 5 6 public synchronized void sheng() throws InterruptedException{ 7 if(count>=10){ 8 System.out.println("商品过多:"+count); 9 wait(); 10 }else{ 11 System.out.println("商品生产成功:"+(++count)); 12 notify(); 13 } 14 } 15 public synchronized void mai() throws InterruptedException{ 16 if(count<=0){ 17 System.out.println("商品库存不够:"+count); 18 wait(); 19 }else{ 20 System.out.println("商品卖出成功:"+(--count)); 21 notify(); 22 } 23 } 24 }
在创建生产者和消费者:
1 package Threads; 2 3 public class Sheng implements Runnable{ 4 private Piao p; 5 6 public Sheng(Piao p) { 7 super(); 8 this.p = p; 9 } 10 @Override 11 public void run() { 12 // TODO Auto-generated method stub 13 for (int i = 0; i < 10; i++) { 14 try { 15 p.sheng(); 16 } catch (InterruptedException e) { 17 // TODO Auto-generated catch block 18 e.printStackTrace(); 19 } 20 } 21 } 22 } 23 24 25 26 27 package Threads; 28 29 public class Mai implements Runnable{ 30 private Piao p; 31 32 public Mai(Piao p) { 33 super(); 34 this.p = p; 35 } 36 @Override 37 public void run() { 38 // TODO Auto-generated method stub 39 for (int i = 0; i < 10; i++) { 40 try { 41 p.mai(); 42 } catch (InterruptedException e) { 43 // TODO Auto-generated catch block 44 e.printStackTrace(); 45 } 46 } 47 } 48 }
测试类:
1 package Threads; 2 3 public class Test4 { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 Piao p=new Piao(); 8 new Thread(new Mai(p)).start(); 9 new Thread(new Sheng(p)).start(); 10 } 11 12 }
运行结果为: