java多线程

一、多线程的创建

1. 继承Thread类创建线程

public class MyThread extends Thread {
	public void run() {
		super.run();
		System.out.println("MyThread");
	}
}
public class Run {
	public static void main(String[] args) {
		MyThread mythread = new MyThread();
		mythread.start();
		System.out.println("运行结束");
	}
}

2. 实现Runnable接口创建线程

public class MyRunnable implements Runnable {
	public void run() {
		System.out.println("MyRunnable");
	}
}
public class Run {
	public static void main(String[] args) {
		Runnable runnable=new MyRunnable();
		Thread thread=new Thread(runnable);
		thread.start();
		System.out.println("运行结束!");
	}
}

3、匿名内部类创建线程

public class HashMapTest {
    public static void main(String[] args) {
       new Thread("线程1"){
           @Override
           public void run() {
               System.out.println(currentThread().getName()+"开始执行");
           }
       }.start();
    }
}

  • Lambda表达式写法:
public class HashMapTest {
    public static void main(String[] args) {
       new Thread(()->{
           System.out.println(Thread.currentThread().getName()+"开始执行");
       },"线程1").start();
    }
}

  • Thread和Runnable的区别
    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

总结:
实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

二、线程的生命周期

1、新建状态

  • 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
    注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。

2、就绪状态

  • 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
    提示:如果希望子线程调用start()方法后立即执行,可以使Thread.sleep()方式使主线程睡眠一会儿,转去执行子线程。

3、运行状态

  • 处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。

注: 当发生如下情况是,线程会从运行状态变为阻塞状态:

 ①、线程调用sleep方法主动放弃所占用的系统资源

 ②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

 ③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有

 ④、线程在等待某个通知(notify)

 ⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。

当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。

4、阻塞状态

  1. 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:

5、死亡状态

  1. 当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

三、Java多线程常用方法

1、start()与run()

start() 启动线程并执行相应的run()方法

run() 子线程要执行的代码放入run()方法

2、getName()和setName()

getName() 获取此线程的名字

setName() 设置此线程的名字

3、isAlive()

是判断当前线程是否处于活动状态。活动状态就是已经启动尚未终止。

4、currentThread()

返回代码段正在被哪个线程调用

public class Test extends Thread {
    @Override
    public void run() {
    //当前线程的名字
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args) {
         Test test=new Test();
         //设置线程名字
         test.setName("线程1");
         //启动线程
         test.start();
    }
}

5、interrupt

interrupt方法常用来“吵醒”休眠的线程。当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法吵醒自己,导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。

6、getPriority()和setPriority(int newPriority)

这两个方法是用于获取当前和设置线程的优先级。优先级高的线程得到的cpu多。也就是说,两个等待的线程,优先级高的线程容易被cpu执行。

默认情况下,线程的优先级是5。线程的优先级分为1~10等级。

7、getId()

取得线程唯一标识

8、stop()

强制停止,已废弃,可能释放锁导致数据不对。,可能导致清理工作做不好。

9、suspend和resume

暂停和恢复,会造成独占(永远的暂停)(方法),造成不同步

10、sDaeMon、setDaemon(boolean on)

java线程有两种,一种是用户线程,一种是守护线程。守护线程是一个特殊的线程,任何一个守护线程都是jvm中所有非守护线程的保姆。当进程中不存在非守护线程时,守护线程会自动销毁。典型的守护线程就是垃圾回收线程。
第一个是判断线程是不是守护线程,第二个是设置线程为守护线程,必须在线程start之前setDaemon(true)。

四、线程管理

1、线程睡眠——sleep

  如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法。

2、线程让步——yield

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。
实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。

3、线程合并——join

join()的作用是:

“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法。

  • 从上面的方法的列表可以看到,它有3个重载的方法:
void join()      
     当前线程等该加入该线程后面,等待该线程终止。    
void join(long millis)  
     当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  
void join(long millis,int nanos)   
     等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  
package 多线程.join方法;
/**
 * 子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
 */
public class Test  {
    public static void main(String[] args) {
        Shop shop=new Shop();
         Shop shop=new Shop();
         shop.coustem.start();
    }
}
class Shop implements Runnable{
    Thread cooker;
    Thread coustem;
      public Shop(){
        cooker=new Thread(this,"cooker");
        coustem=new Thread(this,"Tom");
}
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Tom")){
            System.out.println(Thread.currentThread().getName()+"点了一碗汤,快点");
            cooker.start();
            try {
                cooker.join();        //cooker线程结束了,join方法后面的代码才会执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"开始喝汤了");
        }
        else if(Thread.currentThread()==cooker){
            System.out.println(Thread.currentThread().getName()+"开始做汤了");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("汤好了");
        }
    }
}
输出:
  Tom点了一碗汤,快点
  cooker开始做汤了
  汤好了
  Tom开始喝汤了

4、设置线程的优先级

 每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。

每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。

注:Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~·0之间,也可以使用Thread类提供的三个静态常量:

MAX_PRIORITY   =10

MIN_PRIORITY   =1

NORM_PRIORITY   =5
public class Test1 {  
        public static void main(String[] args) throws InterruptedException {  
            new MyThread("高级", 10).start();  
            new MyThread("低级", 1).start();  
        }  
    }  
      
    class MyThread extends Thread {  
        public MyThread(String name,int pro) {  
            super(name);//设置线程的名称  
            setPriority(pro);//设置线程的优先级  
        }  
        @Override  
        public void run() {  
            for (int i = 0; i < 100; i++) {  
                System.out.println(this.getName() + "线程第" + i + "次执行!");  
            }  
        }  
    }

注:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。

5、后台(守护)线程

 守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。守护线程的用途为:
 • 守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。

 • Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。

setDaemon方法的详细说明:

public final void setDaemon(boolean on)        将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。    
         该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。   
  参数:
     on - 如果为 true,则将该线程标记为守护线程。    
  抛出:    
    IllegalThreadStateException - 如果该线程处于活动状态。    
    SecurityException - 如果当前线程无法修改该线程。

注:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。

6、正确结束线程

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法:

• 正常执行完run方法,然后结束掉;

• 控制循环条件和判断条件的标识符来结束掉线程。
class MyThread extends Thread {  
    int i=0;  
    boolean next=true;  
    @Override  
    public void run() {  
        while (next) {  
            if(i==10)  
                next=false;  
            i++;  
            System.out.println(i);  
        }  
    }  
}

sleep和yield的区别

①、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。

②、sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。

③、sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。

sleep和wait的区别

  1. sleep()是Thread类中的方法,而wait()则是属于Object类中的方法。
  2. sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。
    sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,当指定的时间到了又会自动恢复运行状态。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

五、线程同步

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

1、同步方法

即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

public synchronized void save(){}

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

2、同步代码块

即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

public class Bank {
   //账户余额            
  private int count =0;              
    //存钱          
  public   void addMoney(int money){    
       synchronized (this) {                
           count +=money;              
              }              
    System.out.println(System.currentTimeMillis()+"存进:"+money);          
        }              
    //取钱        
  public   void subMoney(int money){             
        synchronized (this) {           
           if(count-money < 0){   
             System.out.println("余额不足");                                                                   
                  return;            
               }          
                count -=money;         
         }                    
         System.out.println(+System.currentTimeMillis()+"取出:"+money);          }    
    //查询     
    public void lookMoney(){            
      System.out.println("账户余额:"+count);      
          }
}

注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

同步与异步——synchronized详解

同步:
我做完,你再做。当一个线程访问该资源时,不允许其他线程访问。
例 如,公司就一个饮水机,二狗同学在接水时其他同学都不能使用饮水机。
异步:
我做我的,你做你的。多个线程可以同时访问一个资源,可能会发生线程安全问题。 例如,公司就一个饮水机,二狗同学在接水时刚好二蛋同学在给饮水机换水,这就是安全问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值