黑马程序员--javaSE--多线程基础总结

------- android培训java培训、期待与您交流! ----------


在写这篇博客之前,我想说的是:java多线程终于弄通顺了一遍。虽说以前学过,
但是好久没用,现在都忘得差不多了,还好借写博客的机会又重新复习了下,也体会到了
在学习知识时,做笔记,做总结的重要性。言归正传,开始总结多线程吧。


一、关于java线程的一些基本概念
1.进程:是指在系统中正在运行的一个应用程序;
2.线程:线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,
每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。
它们共享内存、文件句柄和其它每个进程应有的状态。

二、java中的多线程
1、任何一个java程序都不是单线程的,即使是一个简单的helloworld的程序也要包括两个线程,
一个是主线程,另一个就是我们常说的jvm的垃圾回收线程。主线程是main开始执行的,完成main中所有的语句后死亡。
垃圾回收线程清除被废弃的对象,并回收他们所占用的内存。

2、jdk为java实现多线程提供的类有Thread和接口Runnable具体的应用后面会介绍

3、线程的创建和启动:
线程的创建有两种方式,第一种是继承Thread类重写其run方法,第二种定义实现Runnable接口的类,并将线程要执行的代码
写入到run()方法中
代码实现:
package com.itheima.thread;

public class TraditionalThread {
	public static void main(String[] args) {
		
	//线程的两种创建方式
	//1.通过继承Thread类,重写run()方法
	new Thread(){
		@Override
		public void run() {
			     System.out.println("1"+this.getName());
			}
		};
	}.start();
	//2.通过Thread的构造方法,传入实现Runnable接口的类的实例
	new Thread(new Runnable() {
		
		@Override
		public void run() {
			System.out.println("2"+ Thread.currentThread().getName());
		}
	}).start();
  }
}

这两种方法在底层都是通过调用Thread类的run方法来实现的,所以当创建一个线程时,又继承了Thread类,又传入了实现
Runnable接口的类的实例对象则,该线程实际上调用的是继承Thread类时,内部重写的方法。


4、线程间的同步、锁、通信
在介绍线程间的通信时,需要先了解线程常用的几个方法和基本状态


线程的创建状态:当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等运行资源。
线程的就绪状态:对处于创建状态的线程调用Thread类的start()方法将线程的状态转换为就绪状态。这时,
                线程已经得到除CPU时间片之外的其他系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,
从而使该线程拥有能够获得CPU时间片的机会。


线程的休眠状态:在线程运行过程中可以调用Thread类的sleep()方法,并在方法参数中指定线程的休眠时间将线程状态转换为休眠状态。
               这时,该线程在指定的休眠时间内,在不释放占用资源的情况下停止运行。时间到达后,线程重新进入运行状态。处于休眠状态的线程,
               可能遇上 java.lang.InterruptedException异常,从而被迫停止休眠。
挂起状态:可以通过调用Thread类的suspend()方法将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,
          直至应用程序调用Thread类的resume()方法恢复线程运行。
死亡状态:死亡状态:当线程体运行结束或者调用线程对象的Thread类的stop()方法后线程将终止运行,由JVM收回线程占用的资源。
 
几个重要的方法:
wait()方法:该方法属于Object的方法,wait方法的最用是是的当前调用wait方法所在部分的线程停止执行,并释放当前获得的调用wait所在的代码块的锁,
并在其他线程代用notify或者noyifyAll方法时恢复到竞争锁状态(一旦获得锁就恢复执行)。
调用wait方法需要注意几点:
        第一点:wait被调用的时候必须在拥有锁(即synchronized修饰的)的代码块中。
        第二点:恢复执行后,从wait的下一条语句开始执行,因而wait方法总是应当在while循环中调用,以免出现恢复执行后继续执行的条件不满足却继续执行的情况。
        第三点:若wait方法参数中带时间,则除了notify和notifyAll被调用能激活处于wait状态(等待状态)的线程进入锁竞争外,在其他线程中interrupt它或者参数时间到了之后,该线程也将被激活到竞争状态。
        第四点:wait方法被调用的线程必须获得之前执行到wait时释放掉的锁重新获得才能够恢复执行。


notify方法和notifyAll方法:
        notify方法通知调用了wait方法,但是尚未激活的一个线程进入线程调度队列(即进入锁竞争),注意不是立即执行。并且具体是哪一个线程不能保证。另外一点就是被唤醒的这个线程一定是在等待wait所释放的锁。
        notifyAll方法则唤醒所有调用了wait方法,尚未激活的进程进入竞争队列。
注意点:以上两个方法所唤醒的线程是:唤醒在此对象上等待的线程。


线程中的同步和锁:


 当多个线程访问同一个数据时,非常容易出现线程安全问题。这时候就需要用线程同步
java中每个对象都有一个内置锁,一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。
这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。
关于锁和同步,有一下几个要点:
1)只能同步方法,而不能同步变量和类;
2)每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)线程睡眠时,它所持的任何锁都不会释放。
7)线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。




代码体现:(线程间通信的具体应用,这里不采用消费者与生产者的问题,而是张老师(张孝祥)所列举的一个面试题


题目要求:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。

package com.itheima.thread;

public class TraditionalCommu {
	public static void main(String[] args) {
		final Business business = new Business();
		//子线程
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1;i<=50;i++){
					business.sub(i);
				}
			}
		}).start();
		
		//主线程
		for(int i=1;i<=50;i++){
				business.main(i);
			}
		}
		
}

class Business{
	//定义一个boolean类型的标志值,用来作为主线程与子线程之间的切换
	boolean flag = true;
	//子线程要调用的方法
	public synchronized void sub(int i){
		//检验标志值是否为true,如果是则执行子线程
		while(flag){
			for(int j=1;j<=10;j++){
				System.out.println("sub thread sequence of " + j + ",loop of " + i);
			}
			//子线程代码执行完时,将标志值改为false,并唤醒同一把锁上的线程
			flag = false;
			this.notify();
		}
		//若程序执行到这,则说明,标志值为false,不能执行子线程,等待
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	//主线程要调用的方法
	public synchronized void main(int i){
		//检验标志值是否为false,如果是则执行主线程
		while(!flag){
			for(int j=1;j<=100;j++){
				System.out.println("main thread sequence of " + j + ",loop of " + i);
		}
		//主线程代码执行完时,将标志值改为true,并唤醒同一把锁上的线程
			flag = true;
			this.notify();
		}
		//若程序执行到这,则说明,标志值为true,不能执行主线程,等待
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
三、线程的补充
1.设置线程的优先级:
 Java将线程的优先级分为10个等级,分别用1~10之间的数字表示。数字越大表明线程的优先级别越高。相应地,
 在Thread类中定义了表示线程最低、最高和普通优先级的成员变量MIN_PRIORITY、MAX_PRIORITY和NORMAL_PRIORITY,
 代表的优先级等级分别为1、10 和5。当一个线程对象被创建时,其默认的线程优先级是5。在创建线程对象之后可以
 调用线程对象的setPriority()方法改变该线程的运行优先级,同样可以调用getPriority()方法获取当前线程的优先级。


 2.守护线程;
在Java中有一类被称为守护(Daemon)线程的,比较特殊的线程,这是一种优先级比较低的线程。守护线程具有最低的优先级,
一般用于为系统中的其他对象和线程提供服务。将一个线程设置为守护线程的方式是在线程对象创建之前调用线程对象的setDaemon()方法。
典型的守护线程例子是Java虚拟机中的垃圾自动回收线程,它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
可以通过调用线程对象的isDaemon()方法来判断某个线程是否是守护线程。
守护线程还有另一层含义:当创建守护线程的父线程终止时,作为子线程的守护线程也自动终止。
反之,如果一个子线程不是守护线程,即使父线程终止了,它也不会终止。
当一个线程被创建时,它默认不是守护线程。
面的代码片段演示守护线程的用法。线程parent创建了一个子线程child,子线程child的循环体是一个无限循环,
不断地打印“Child runs”字样。在child线程不是守护线程的情况下,即使parent线程终止了,child线程仍然在运行:
Thread parent=new Thread()
{
       public void run()
       {
             System.out.println("Parent starts");
             Thread child=new Thread()
             {
                   public void run()
                   {
                         System.out.println("Child starts");
                         while(true)
                         {
                               System.out.println("Child runs");
                         }
                   }
             };
             child.setDaemon(false);
             child.start();
             System.out.println("Parent ends");
       }
};
parent.start();

如果希望父线程终止时,子线程自动终止,只需要将“child.setDaemon(false);”改为“child.setDaemon(true);”即可。

总结:线程是比较繁琐的和注重细节的,所以一定要打好线程基础知识。

ps:终于写完了对于线程的简单总结,但还有复杂的多个线程访问共享对象和数据需要总结,不过趁热打铁,
先去买点零食回来接着写。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值