线程

线程

学习线程首先要明确几个概念:程序,进程,线程

  1. 程序:为了完成指定任务,用某种语言编写指令的合集。
  2. 进程:程序一次执行的过程,或者是正在运行的一个程序。(动态的)进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
  3. 线程:一个进程中的多个线程共享相同的内存单元/内存地址空间—他们从堆中分配对象,可以访问相同的变量和对象。

使用线程的目的:程序都是从上到下执行的,只有一段程序结束之后,才会执行下一段程序。有时候我们为了提高效率,往往会使用多线程,比如我们下载东西的时候喜欢看斗鱼直播。

比如:

public class Stu {
	public void study() {
		System.out.println("我正在下载recurdyn");
	}
	
	public void watch() {
		System.out.println("我想看斗鱼直播");
	}
	
	public static void main(String[]args) {
		Stu su = new Stu();
		for(int i=0;i<5;i++) {
			su.study();
		}
		for(int i=0;i<3;i++) {
			su.watch();
		}
	}
}


//这段代码很简单,输出的一定是:
我正在下载recurdyn
我正在下载recurdyn
我正在下载recurdyn
我正在下载recurdyn
我想看斗鱼直播
我想看斗鱼直播
我想看斗鱼直播

这就是代码执行的顺序性,从上到下依次执行。

要想边下载recurdyn边看腾讯视频,我们就要用到线程,首先来看下线程的创建。

线程的创建的两种方法:

  1. 继承 Thread
  2. 实现 Runable

继承 Thread:

通过java.lang.Thread类来实现

  1. 创建一个继承与Thread()的子类
  2. 重写Thread里面的run()方法
  3. 创建Thread的子类的对象
  4. 通过此对象调用start()方法
//创建一个继承与Thread()的子类
public class StuThread extends Thread{
	//重写Thread里面的run()方法
	public void run() {
		Stu rt = new Stu();
		for(int i=0;i<50;i++) {
			rt.watch();
		}
	}
}

public class Stu {
	public void study() {
		System.out.println("我正在下载recurdyn");
	}
	
	public void watch() {
		System.out.println("我想看斗鱼直播");
	}
	
	public static void main(String[]args) {
		//创建Thread的子类的对象
		StuThread st = new StuThread();
		
		//通过此对象调用start()方法
		//注:这里不能写st.run();如果这样写,线程将不会启动。这样写只是我们创建了一个对象,并调用其中的重写的方法而已。
		st.start();
		
		
		Stu su = new Stu();
		for(int i=0;i<500;i++) {
			su.study();
		}
	}
}

//这样的话在输出里面会看到这样一段

我正在下载recurdyn
我正在下载recurdyn
我想看斗鱼直播
我想看斗鱼直播
我想看斗鱼直播
我想看斗鱼直播
我想看斗鱼直播
我正在下载recurdyn
我正在下载recurdyn
我正在下载recurdyn
我正在下载recurdyn

这其实就实现了我们一边看斗鱼直播,一边下载recurdyn

这段代码的执行过程为:

  1. 先执行主函数里面的代码
  2. 执行主函数代码期间,Thread的子类的对象的start()方法启动,开始执行线程里面的代码。主线程和分线程之间其实就是谁快谁慢的问题,他们在时间上是同时之执行的。
    在这里插入图片描述

通过实现Runnable接口

Runnable方式实现多线程 :

  • 1.创建实现了Runnable接口的类
  • 2.实现Runnable中的抽象方法:run()
  • 3.创建实现类的对象
  • 4.将此对象传递到Thread类的构造器中,创建Thread类的对象
  • 5.通过Thread类的对象来调用start()方法

下面通过代码来演示:

主线程实现遍历1-100的偶数;分线程实现遍历1-100的奇数

public class MyThread {
	public static void main(String[] args) {
		//3.创建实现类的对象
		TThread tt = new TThread();	
		
		//4.将此对象传递到Thread类的构造器中,创建Thread类的对象
		Thread t = new Thread(tt);
		
		//5.通过Thread类的对象来调用start()方法
		t.start();
		
		for(int i=0;i<100;i++) {
			if(i % 2 !=0) {
				System.out.println("100以内的奇数为:"+Thread.currentThread().getName()+":"+i);
			}
		}

	}
}	

//1.创建实现了Runnable接口的类
class TThread implements Runnable{

	//2.实现Runnable中的抽象方法:run()
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			if(i % 2 == 0) {
				System.out.println("100以内的偶数为:"+Thread.currentThread().getName()+":"+i);
			}
		}
		
	}
}
/*
输出:(一部分)
00以内的偶数为:Thread-0:0
100以内的奇数为:main:13
100以内的奇数为:main:15
100以内的偶数为:Thread-0:2
100以内的奇数为:main:17
100以内的偶数为:Thread-0:4
100以内的偶数为:Thread-0:6
*/

其实现过程与上图一致;
以上便是常见的创建线程的两种方式。

在这里我们要明确两个问题:

  1. start();的作用: a.启动线程 b.调用其中的run()方法
  2. 同一个线程,只能start一次(第一次调用start,threadStatus==0,第二次调用start,threadStatus!=0)
 public synchronized void start() {
 
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
            。。。。
    	}

以上两种线程创建方式的对比:

  1. 类都是单继承多实现的,如果我们使用继承Thread类的方式,会造成不便,而实现Runnable接口的方式不会受此限制。
  2. 在实现Runnable中其实只需要创建一个类,实现了数据共享,而继承Thread中需要多个类,在数据共享时,需要将共享数据变为静态成员变量。
  3. 综上所述,实现Runnable接口的方式会更加实用一些。

既然我们已经知道了线程的实现方式及其启动方法,那我们便需要了解一下线程的生命周期。

线程的安全问题

因为之前提到过,多个线程可以对同一个数据进行操作,这就会出现线程的安全问题。要想解决线程的安全问题,我们首先要知道线程的安全问题是怎么出现的。

这就要求我们了解线程的生命周期
在jdk中Thread.State定义了线程的几种状态:

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
         //Thread t = new Thread(tt);此时start还没有被调用
         //创建线程
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

以上是jdk中根据方法分类的,而为了简便,我们自己分类,如下图
在这里插入图片描述

  1. 首先通过继承Thread或者实现Runnable接口创建一个线程
  2. 通过start方法使得该线程准备就绪,可以运行,但此时不是准备就绪之后便可以运行的,要等CPU,等到该线程获得CPU执行权的时候,该线程才可以执行。
  3. 获得了CPU执行权,运行其中的run方法。
  4. run方法执行结束之后,或者是调用stop方法,或者出现出现错误的时候,该线程结束。
  5. 在运行时,可能会出现线程阻塞,比如sleep,等sleep运行结束之后,会继续从start开始运行该线程。
			try{
				Thread.sleep(30);
			}catch(Exception ef){
				ef.printStackTrace();
			}

介绍了线程的生命周期之后,就可以聊一聊线程的安全问题了:
这里我以儿子,母亲,父亲取钱为例,写一段代码
比如,我们执行以一段代码:

public class Test2 {
	public static void main(String[] args) {
		TThread2 t = new TThread2();
		
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		
		t1.setName("儿子");
		t2.setName("父亲");
		t3.setName("母亲");
		
		t1.start();
		t2.start();
		t3.start();	
	}
}
class TThread2 implements Runnable{
	
	private int money = 1000;
	
	public void run() {
		while(money > 0) {
			System.out.println(Thread.currentThread().getName()+"取完之后,余额为:"+ money);
			money--;
		}		
	}	
}

/*
其运行结果的一部分为:
儿子取完之后,余额为:1000
母亲取完之后,余额为:1000
母亲取完之后,余额为:998
母亲取完之后,余额为:997
母亲取完之后,余额为:996
母亲取完之后,余额为:995
母亲取完之后,余额为:994
母亲取完之后,余额为:993
母亲取完之后,余额为:992
母亲取完之后,余额为:991
父亲取完之后,余额为:1000
父亲取完之后,余额为:989
父亲取完之后,余额为:988
父亲取完之后,余额为:987
母亲取完之后,余额为:990
儿子取完之后,余额为:999
*/

以上代码操作的是同一个对象,而出现了三次1000,而且钱的余额也不对,这便是出现了线程的安全问题。
接下来,来分析下线程安全问题出现的原因。

线程安全问题出现的原因

我以一个图的形式来展示下:
在这里插入图片描述

  1. 一家三口去取钱,因为取钱这个程序执行需要时间,所以可能存在一种情况,就是儿子再取钱的时候,取钱的程序没有结束的时候,恰好父亲也在取钱,这就会出现余额相同的情况,从而导致线程不安全。
  2. 解决:加锁,在一个线程执行时,加一个锁来锁住他,不让下一个线程进来,等到前面的线程结束之后,下一个才能进来。
  synchronized(同步监视器){
  		需要被同步的代码
  		 }

其中,同步监视器可以是任何一个类的对象(常用object,this)
对于同步监视器也是有要求的:

  1. 多个线程要共用同一把锁。

优化之后的代码:

public class Test2 {
	public static void main(String[] args) {
		TThread2 t = new TThread2();
		
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		
		t1.setName("儿子");
		t2.setName("父亲");
		t3.setName("母亲");
		
		t1.start();
		t2.start();
		t3.start();	
	}
}
class TThread2 implements Runnable{
	
	private int money = 1000;
	
	public void run() {
		while(true) {
			synchronized(this) {
				if(money>0) {
					System.out.println(Thread.currentThread().getName()+"取完之后,余额为:"+ money);
					money--;
				}	
			}
		}
	}	
}

/*
一部分运行结果:
儿子取完之后,余额为:1000
儿子取完之后,余额为:999
儿子取完之后,余额为:998
儿子取完之后,余额为:997
儿子取完之后,余额为:996
儿子取完之后,余额为:995
儿子取完之后,余额为:994
儿子取完之后,余额为:993
儿子取完之后,余额为:992
儿子取完之后,余额为:991
儿子取完之后,余额为:990
儿子取完之后,余额为:989
儿子取完之后,余额为:988
儿子取完之后,余额为:987
儿子取完之后,余额为:986
儿子取完之后,余额为:985
儿子取完之后,余额为:984
儿子取完之后,余额为:983
儿子取完之后,余额为:982
儿子取完之后,余额为:981
儿子取完之后,余额为:980
儿子取完之后,余额为:979
儿子取完之后,余额为:978
儿子取完之后,余额为:977
儿子取完之后,余额为:976
儿子取完之后,余额为:975
儿子取完之后,余额为:974
儿子取完之后,余额为:973
儿子取完之后,余额为:972
儿子取完之后,余额为:971
儿子取完之后,余额为:970
*/
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页