黑马程序员——java基础——多线程的学习总结

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

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元(线程就是进程的一个执行路径)。线程在控制着进程的执行。

多线程则扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。线程(Thread )也被称作轻量级进程(Lightweight Process ),线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立的、并发的执行流。当进程被初始化后,主线程就被创建了。对于绝大多数的应用程序来说,通常仅要求有一个主线程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每条线程也是互相独立的。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,我们必须确保线程不会妨碍同一进程里的其他线程。

一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。

多线程编程包含如下几个优点:
进程间不能共享内存,但线程之间共享内存非常容易。
系统创建进程需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高。
Java语言内置多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编。

线程的创建和启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每条线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。Java使用run方法来封装这样一段程序流。

方式一:继承Thread类创建线程

通过继承Thread类来创建并启动多线程的步骤如下:

1.自定义一个类继承Thread类,并重写该类的run方法,该run方法的方法体就是代表了线程需要完成的任务。因此,我们经常把run方法称为线程执行体。

2.创建Thread子类的对象,即创建了线程对象。

3.调用用线程对象的start()方法来启动该线程。

方式二:实现Runnable接口创建线程
实现Runnable接口来创建并启动多条线程的步骤如下:

1.定义Runnab1e接口的实现类,并重写该接口的run方法,该run方法的方法体同样是该线程的线程执行体。

2.创建Runnable实现类的实例,并以此对象作为创建Thread类对象的构造参数进行传递。

3.创建Thread对象

4.启动线程,用Thread类对象调用start()方法

使用Runnable接口创建线程可以共享同一个线程类实例的资源。

两种方式创建的线程的区别

实现Runnable接口的线程:
优点:
线程类只是实现了Runnable接口,还可以继承其他类。在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

缺点:编程稍稍复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

继承Thread类的线程:
优点:编写简单,如果需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。

缺点:因为线程类已经继承了Thread类,所以不能再继承其他父类。

实际上几乎所有的多线程应用都可采用第一种方式,也就是实现Runnable接口的方式。

线程的生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建、就绪、运行、阻塞和死亡五种状态。

新建状态
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程执行体中的线程执行体。

就绪状态
当线程对象调用了start()方法之后,该线程处于就绪状态拥有CPU执行资格,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,它只是表示该线程拥有CPU执行资格。至于该线程何时开始运行取决于jvm里线程调度器的调度。

不要对已经处于启动状态的线程再次调用start()方法,否则将引发IllegalThreadStateException异常。

运行状态

就绪状态的线程抢夺了CPU执行权的线程就会进入运行状态,如果运行状态的线程被其他线程抢夺了CPU执行权就会进入就绪状态。另外如果运行状态的线程执行到了sleep()方法和wait()方法,运行状态的线程就会进入临时阻塞状态。

临时阻塞状态

临时阻塞状态的线程没有CPU执行资格个执行权。临时阻塞状态的线程如果达到了sleep()方法指定的时间和调用了notifly()方法时会重新进入就绪状态,这个时候该线程拥有CPU的执行资格。

死亡状态

运行状态的线程如果执行完了run()方法里面 的代码,或者调用了stop()方法和interrupt()方法,运行状态的线程就会进入死亡状态。

线程的生命周期图:

黑马程序员——java基础——多线程的学习总结_第1张图片

线程注意的问题

1、一个java程序至少有一个负责程序执行的main线程和一个负责垃圾回收机制的线程两个线程。

2、如果直接调用了run方法,那么这个时候run( )方法不是启动线程而是调用一般的方法,如果要启动线程需要调用线程对象的start( )方法。

3、创建线程重写run()方法是因为run()方法里面是线程要执行的代码。

多线程的意义:

1多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
2程序的执行其实都是在抢CPU的资源,CPU的执行权。
3多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。

多线程实现卖票的案例:

package test;

public class SellTicket1 extends Thread {
	public static int Ticket=100; //定义票数
	@Override
	public void run() {//重写run()方法
		// TODO Auto-generated method stub
		while(true){
			if(Ticket>0){
				System.out.println(Thread.currentThread().getName()+"卖出了第"+Ticket+"张票");
				Ticket--;
			}
		}
	}
}
package test;

public class SellTicketDemo {
	public static void main(String[] args) {
		//创建线程对象
		SellTicket1 st1=new SellTicket1(); 
		SellTicket1 st2=new SellTicket1();
		SellTicket1 st3=new SellTicket1();
		//给线程设置名称
		st1.setName("窗口1");
		st2.setName("窗口2");
		st3.setName("窗口3");
		//启动线程
		st1.start();
		st2.start();
		st3.start();
	}
}
 
  代码运行结果: 
  

黑马程序员——java基础——多线程的学习总结_第2张图片

上面代码运行所出现的问题是线程安全问题,同一张票会卖出多次或者会出现第0张、第负数张票。

线程安全问题出现的前提有三个

1、是在多条线程拥有CPU执行资格的环境下。

2、有共享的数据。

3、多条语句操作共享数据。

解决办法:

       对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

两种办法:

1、同步代码块

2、同步函数

同步代码块的锁是任意类型的对象
同步
函数的锁对象是this
静态的同步
函数使用的锁是该方法所在类的字节码文件对象。 类名.class

同步的作用是必须保证只能有一个线程在运行,所以同步有优点也有缺点
优点:解决了多线程的安全问题。
缺点
:多个线程需要判断锁,较为消耗资源,

用同步解决线程安全问题之后的代码

package test;


public class SellTicket1 extends Thread {
	public static int Ticket=100; //多线程共享的数据
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized("锁"){ //同步代码块
				try {
					Thread.sleep(300);//睡眠300毫秒
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if(Ticket>0){
					System.out.println(Thread.currentThread().getName()+"卖出了第"+Ticket+"张票");//获取当前线程的名称和票数
					Ticket--;
				}
			}
		}	
	}
}


你可能感兴趣的:(Java基础学习)