多线程学习总结(一)

一、进程和线程的定义

进程:进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

线程:线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。

 例:进程 ->车间,线程->车间工人

二、进程和线程的区别

  • 进程是资源分配的最小单位,线程是程序执行的最小单位。
  • 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的
  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

  • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

三、线程的七种状态和其相互间的转换

1.实现线程的方式主要有两种方式:继承Thread类和实现Runnable接口;但只要我们创建(new)对象,这个线程就进入了初始化状态;

2.调用这份线程的start()方法,线程就进入线程池中等待CUP的使用权,就处于就绪状态;

3.处于就绪状态的线程的得到获取CUP时间片,就进入到运行状态;

4.处于运行状态中:

  • run()方法结束获取main方法结束后,进程就进入到了终止状态;
  • 当线程调用了自身的sleep()方法或其他线程的join()方法,进程让出CUP,然后就会进入到阻塞状态(该状态既会停止当前线程,但并不释放所占有的资源,即调用sleep()函数后,线程不会释放它的“锁标志”)。当sleep()结束或者join()方法结束后,该线程进入可以运行状态,继续的部分带OS分贝CPU时间片。
  • 线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态; 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间片从而需要转到另一个线程。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
     
  • 当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池状态,等待获取锁标记(这时的锁池里也许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入就绪状态,等待OS分配CPU时间片;
  • suspend() 和 resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume()被调用,才能使得线程重新进入可执行状态。典型地,suspend()和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume()使其恢复。 

  • wait()和 notify() 方法:当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁池,等待获取锁标记。 

四、在java中实现多线程的几种方式:

(一)继承Thread类的方式:

package com.wcg.thread.t1;

public class Demo1 extends Thread{
	
	public Demo1(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		while(true) {
			System.out.println(this.getName()+"当前线程执行了");			
		}
	}
	public static void main(String[] args) {
		Demo1 demo1 = new Demo1("first_thread");
		Demo1 demo2 = new Demo1("second_thread");
		demo1.start();
		demo2.start();
	}
}

说明:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

(二)实现Runnabale的方式:

package com.wcg.thread.t1;

public class Demo2 implements Runnable{

	@Override
	public void run() {
		while(true) {
			System.out.println("Thread Running");
		}
	}
	public static void main(String[] args) {
                //实例化一个Thread,并传入实例对象
		Thread thread = new Thread(new Demo2());
		thread.run();
		
	}

}

(三)实现Callable接口(带有返回值)通过FutureTask包装器来创建Thread线程:

public class Demo4 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		System.out.println("正在计算中....");
		Thread.sleep(3000);
		return 1;
	}

	public static void main(String[] args) throws Exception{
		Demo4 d = new Demo4();
		FutureTask<Integer> task = new FutureTask<>(d);
		Thread thread = new Thread(task);
		thread.start();
		System.out.println("哈哈哈哈");
		Integer result = task.get();
		System.out.println(result);
	}
	
}

(四)通过定时器Timer的方式实现:

public class Demo5 {
	public static void main(String[] args) {
		Timer timer = new Timer();
                //TimerTask继承了Runable接口
		timer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				//实现定时任务
				System.out.println("timerTask is running");
			}
		}, 0, 1000);
	}
}

(注:只做个人学习总结,如有侵权请联系我,有的资料是网上获取的。。。。。)

猜你喜欢

转载自blog.csdn.net/qq_37776015/article/details/81357282