Java多线程--基础

原文链接: http://www.imrookie.cn/article/java-thread-base

Java使用Thread类代表线程,所有的线程都必须是Thread或其子类的实例。

执行代码写在run()方法中。
使用start()方法启动线程。
线程的生命周期:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。

线程相关常用方法:

方法 描述
run() 线程执行体,写线程实际执行的代码
start() 启动线程,当线程处于新建状态时调用
Thread.sleep(x) 当前线程睡眠x毫秒
yield() 线程让步,将该线程转为就绪状态
Thread.currentThread() 获取当前线程
isAlive() 判断当前线程是否活着,线程处于就绪,运行和阻塞时返回true,新建和死亡返回false
join() 当前线程进入阻塞状态,执行完某线程的join()方法后,线程回复为就绪状态;在线程执行中间插入另外一个线程
在调用join前要start
join(x) 等待被join的线程最长时间为x毫秒,超时则不再等待
setDaemon(true) 将线程设置为守护线程
isDaemon() 判断线程是否为守护线程

一.创建方式

方法一: 继承Thread类创建线程
测试代码:
  1. package com.rookie.topic.test;
  2. /**
  3. * 多线程测试
  4. */
  5. publicclassThreadTestextendsThread{
  6. /**
  7. * 重写父类的run()方法
  8. */
  9. publicvoid run(){
  10. //getName()获取该线程的名字
  11. System.out.println("测试线程["+this.getName()+"]启动...");
  12. for(int i=0;i<3;i++){
  13. System.out.println("测试线程["+this.getName()+"]正在执行-"+i);
  14. }
  15. System.out.println("测试线程["+this.getName()+"]执行完毕!");
  16. }
  17. publicstaticvoid main(String[] args){
  18. //创建10个测试线程
  19. for(int i=0;i<10;i++){
  20. newThreadTest().start();//创建实例后调用start()方法来执行该线程
  21. }
  22. }
  23. }
方法二: 实现Runnable接口创建线程
测试代码:
  1. package com.rookie.topic.test;
  2. /**
  3. * 多线程测试: 实现Runnable接口创建线程
  4. */
  5. publicclassThreadTest2implementsRunnable{
  6. /**
  7. * 实现接口的run方法
  8. */
  9. @Override
  10. publicvoid run(){
  11. /**
  12. * 注意这里的区别
  13. * 在实现类中没有getName()方法,所以要使用Thread.currentThread()来获取当前线程
  14. */
  15. System.out.println(Thread.currentThread().getName()+"线程执行...");
  16. }
  17. publicstaticvoid main(String[] args){
  18. //1.创建实现类实例
  19. ThreadTest2 tt =newThreadTest2();
  20. /**
  21. * 将实现类实例作为参数创建Thread实例,并执行
  22. * 参数2为线程名字,也可以使用new Thread(tt),不传入名字
  23. */
  24. for(int i=0;i<10;i++){
  25. newThread(tt,"name_"+i).start();
  26. }
  27. }
  28. }

二.线程的生命周期

1. 新建:当使用new关键字创建线程对象后,该线程处于新建状态;
2. 就绪:当线程对象调用了start()方法后,该线程处于就绪状态, java虚拟机会为其创建方法调用栈和程序计数器
3. 运行:处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,该线程处于运行状态;失去CPU资源,线程会回到就绪状态,或者调用yield()方法使线程转为就绪状态;
4. 阻塞:对于采用 抢占式调度策略的系统(现代的桌面和服务器操作系统)而言,系统会给每个可执行的线程一个小时间段来处理任务,当该时间段用完,系统就会剥夺该线程所占据的资源,让其他线程获得执行的机会;在采用 协作式调度策略的系统(小型设备如有些手机)中,只有当一个线程调用sleep()方法或yield()方法后才会放弃所占用的资源,也就是必须由该线程主动放弃所占用的资源。
当发生如下情况,线程将会进入阻塞状态:
对应的解除阻塞的办法:
线程结束阻塞后会进入就绪状态
5. 死亡:线程以以下三种方式结束后处于死亡状态;
    1.run()方法执行完成,线程正常结束;
    2.线程抛出一个未捕获的Exception或Error;
    3.调用该线程的stop方法来结束该线程(该方法容易导致死锁,不推荐);
    4.使用interrupt()方法中断线程

三.守护线程  

    有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。
特征:如果所有的前台线程都死亡,后台线程会自动死亡。
通过setDarmon(true)可以将线程设置为守护线程,要在start()方法执行前设置;
isDaemon()判断该线程是否为守护线程;

四.线程睡眠

使用Thread.sleep()使当前线程睡眠,进入阻塞状态,当睡眠时间过去后,线程进入 就绪状态。

五.线程让步

使用yield()方法将该线程暂停,转为就绪状态,将执行机会让给和自己优先级同级或者更高优先级的线程。
自己测试表示很不稳定,指不定哪个线程继续干活。

六.线程优先级

每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程获得较少的机会。
每个线程的默认优先级都与创建它的线程优先级相同,默认情况下,main线程的优先级为普通。
getPriority()获取线程优先级
setPriority(int a)设置优先级,取值为[1,10];
优先级常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)
应该尽量避免使用自定义的数字作为优先级,要使用 MAX_PRIORITY、 MIN_PRIORITY、 NORM_PRIORITY静态常量来设置优先级。
线程让步与优先级测试代码:
  1. package com.rookie.topic.test;
  2. /**
  3. * 线程测试:线程让步与线程优先级
  4. */
  5. publicclassThreadTest3extendsThread{
  6. /**
  7. * 线程执行体
  8. */
  9. publicvoid run(){
  10. for(int i=0;i<10;i++){
  11. System.out.println("当前线程名字: "+this.getName()+" ; 线程优先级为: "+this.getPriority());
  12. if(i==5&&this.getPriority()==10){
  13. System.out.println("_____高优先级线程["+this.getName()+"]执行让步...");
  14. this.yield();
  15. }
  16. }
  17. }
  18. /**
  19. * 带参构造函数,为线程起名字
  20. * @param name
  21. */
  22. publicThreadTest3(String name){
  23. super(name);
  24. }
  25. publicstaticvoid main(String[] args){
  26. System.out.println("主线程"+Thread.currentThread().getName()+"的优先级为: "+Thread.currentThread().getPriority());
  27. ThreadTest3 t1 =newThreadTest3("高高高");
  28. t1.setPriority(MAX_PRIORITY);
  29. ThreadTest3 t2 =newThreadTest3("低低低");
  30. t2.setPriority(MIN_PRIORITY);
  31. t1.start();
  32. t2.start();
  33. }
  34. }

七.线程同步,关键字synchronized

当多个线程共享同一个资源时,就可能发生“错误情况”。
无同步处理示例:
  1. package com.rookie.topic.test;
  2. /**
  3. * 线程同步测试类
  4. */
  5. publicclassThreadTest4extendsThread{
  6. privateUser user;
  7. //构造器
  8. publicThreadTest4(User user){
  9. this.user = user;
  10. }
  11. publicvoid run(){
  12. if(this.user.getMoney()>800){
  13. System.out.println("钱还剩"+this.user.getMoney()+",够了,取800!");
  14. this.user.setMoney(this.user.getMoney()-800);
  15. }else{
  16. System.out.println("钱不够...");
  17. }
  18. System.out.println("钱还剩"+user.getMoney());
  19. }
  20. publicstaticvoid main(String[] args){
  21. User user =newUser(1000);
  22. //模拟对同一个账户并发操作
  23. newThreadTest4(user).start();
  24. newThreadTest4(user).start();
  25. }
  26. }
  27. /**
  28. * 同步测试模型
  29. */
  30. classUser{
  31. privateint money;
  32. publicint getMoney(){
  33. return money;
  34. }
  35. publicvoid setMoney(int money){
  36. this.money = money;
  37. }
  38. //构造器
  39. publicUser(int money){
  40. this.money = money;
  41. }
  42. }
执行结果:
显然,错误发生了,余额产生了负值。
解决办法:
1. 同步代码块
在共享资源操作处添加同步监视器
示例:
  1. publicvoid run(){
  2. /**
  3. * 同步代码块
  4. * 括号里是需要同步的对象,多线程下,同时最多只有一个线程可以进入同步代码块,
  5. * 一个线程在请求同步监视器时如果同步监视器正在被另一个线程所占用,则该请求线程为阻塞状态,直到获取到对应的同步监视器(取到后会转为就绪状态)
  6. */
  7. synchronized(this.user){
  8. if(this.user.getMoney()>800){
  9. System.out.println("钱还剩"+this.user.getMoney()+",够了,取800!");
  10. this.user.setMoney(this.user.getMoney()-800);
  11. }else{
  12. System.out.println("钱不够...");
  13. }
  14. System.out.println("钱还剩"+user.getMoney());
  15. }
  16. }
2. 同步方法
   与同步代码块类似,使用 synchronized修饰的方法就是同步方法,同步监视器为this,即当前对象本身。
   与同步代码块一样,当有线程正在执行同步方法时,其他线程无法执行该方法,为阻塞状态,直到获取到同步监视器时转为就绪状态。
示例:
  1. package com.rookie.topic.test;
  2. /**
  3. * 线程同步测试类
  4. */
  5. publicclassThreadTest4extendsThread{
  6. privateUser user;
  7. //构造器
  8. publicThreadTest4(User user){
  9. this.user = user;
  10. }
  11. publicvoid run(){
  12. //调用同步方法
  13. this.user.pay();
  14. }
  15. publicstaticvoid main(String[] args){
  16. User user =newUser(1000);
  17. //模拟对同一个账户并发操作
  18. newThreadTest4(user).start();
  19. newThreadTest4(user).start();
  20. }
  21. }
  22. /**
  23. * 同步测试模型
  24. */
  25. classUser{
  26. privateint money;
  27. //构造器
  28. publicUser(int money){
  29. this.money = money;
  30. }
  31. //对象本身提供同步方法供外部调用
  32. publicsynchronizedvoid pay(){
  33. if(this.money >800){
  34. System.out.println("钱还剩"+this.money+",够了,取800!");
  35. this.money =this.money -800;
  36. }else{
  37. System.out.println("钱不够...");
  38. }
  39. System.out.println("钱还剩"+this.money);
  40. }
  41. }
   线程同步是以牺牲性能为代价的,所以不要每个方法都用 synchronized 关键字修饰,只对那些会改变共享资源的方法同步。
   如果某方法有时用在单线程环境,有时用在多线程环境,就拆分为2个方法,各用各的。

同步监视器释放锁定的几种情况:

3.同步锁(Lock)
通过锁对象来对资源进行加解锁。
锁对象更灵活,可以在代码里面合适的行进行加解锁。解锁条件判断更灵活。
  1. /**
  2. * 同步测试模型
  3. */
  4. classUser{
  5. //锁对象
  6. privatefinalReentrantLock lock =newReentrantLock();
  7. privateint money;
  8. publicint getMoney(){
  9. return money;
  10. }
  11. publicvoid setMoney(int money){
  12. this.money = money;
  13. }
  14. //构造器
  15. publicUser(int money){
  16. this.money = money;
  17. }
  18. //对象本身提供同步方法供外部调用
  19. public/*synchronized*/void pay(){
  20. lock.lock();//加锁
  21. try{
  22. if(this.money >800){
  23. System.out.println("钱还剩"+this.money+",够了,取800!");
  24. this.money =this.money -800;
  25. }else{
  26. System.out.println("钱不够...");
  27. }
  28. }finally{
  29. lock.unlock();//解锁
  30. }
  31. System.out.println("钱还剩"+this.money);
  32. }
  33. }

八.死锁

当两个线程互相等待对方释放同步监视器时就会发生死锁。
sleep()会抱着锁进行睡眠
 

安全关闭线程

可以使用标志位或者interrupt()中断
interrupt中断类似于标志位,可以用isInterrupted()判断是否被中断过
但是当线程结束后中断标识会被清除,或者当sleep方法中抛出中断异常后也会清除中断标志.
 
 
 
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/wangjiyu/p/9163882.html