Java之线程创建方式、状态问题

线程

想要学习线程就必须了解线程和进程之间的区别

线程和进程之间的区别

  1. 进程:对于操作系统而言,进程就是程序,多进程就是多个程序同时执行

    注意:一个进行可包含一到多个线程,每一个进程都有自己的代码和运行空间,进程之间切换开销较大,进程是资源分配的最小单位

  2. 线程:程序中的顺序流,多线程就是一个程序中,多个顺序流同时执行

    注意:一系列线程共享代码和数据空间,每个线程都有自己的程序计数器,线程之间切换开销较小,线程是cpu调度的最小单位

线程和进程的状态:新生->就绪->运行->阻塞->终止

线程的创建方式

1.继承Thread,重写run()方法
  1. 在run()方法中定义线程体
  2. 开启:使用start()方法开启线程
package thread01;
//创建线程的方法一:继承Thread类,并重写run()方法
public class ThreadDemo01 extends Thread{
	@Override
	public void run() {
		//定义线程体
		for(int i=1;i<=10;i++){
			System.out.println("一遍吃饭");
		}
	}
	
	public static void main(String[] args) {
		//1.创建线程体
		ThreadDemo01 th=new ThreadDemo01();
		//2.开启多线程
		th.start();
		//错误示例
//		th.run();  //请注意:直接调用run()方法,是方法的调用,并不是开启线程
		//3.执行同步执行主线程的方法
		for(int i=1;i<=10;i++){
			System.out.println("一遍玩手机");
		}
		//运行结果
		/*  一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍玩手机
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
			一遍吃饭
		 从运行结果可以看出,是先运行主方法里面的,在运行线程的
		 其实两个是同时运行的,但是由于cpu调度和效率的问题
		 所以会造成 先执行主方法在执行线程的假象
		 * 
		*/
	}
}

注意:继承Thread()中run()没有抛出异常,所以在它的子类也无法抛出异常。

2.实现Runnable接口,重写run()方法,推荐
  1. 实现Runnable接口,重写run()方法
  2. 通过Thread类中的start()开启线程,将实现Runnable的实例当成构建Thread对象的时候的参数
package thread01;
/*创建线程的方法二:
	实现Runnable接口,重写run()方法,推荐
	开启:通过Thread类中的start()开启线程
	优点:1.避免了单继承的局限性 2.实现了资源共享
*/
public class ThreadDemo02 implements Runnable{
	//重写方法,当被调用时候,会逐行执行里面的代码
	@Override
	public void run() {
		for(int i=1;i<=10;i++){
			System.out.println("一遍刷微博");
		}
	}
	public static void main(String[] args) {
		//1.创建线程体
		ThreadDemo02 th=new ThreadDemo02();
		//2.开启多线程  将实现Runable的实例当成创建Thread对象时候的参数
		Thread t=new Thread(th);//因为开启线程的方法在Thread中,Thread作为代理类出现
		t.start();
		//3.执行同步执行主线程的方法
		for(int i=1;i<=10;i++){
			System.out.println("一遍看头条");
		}

	}
	/*  运行结果如下
	 *  一遍看头条
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍刷微博
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
		一遍看头条
	 */

}
案例一:模拟12306案例

需求:3个人去买100张火车票

要求:采用多线程的方法,

package thread01;
/*
 * 需求:3个人去买100张火车票
 * 要求:采用多线程的方法,
*/
//分析:100张票是3个共享的,所以可以放在类成员中,3个人购买说明有3个线程同时在操作
public class Web12306 implements Runnable{
	//设置火车票总数
	int tikets=100;
	//设置线程,我们知道每次只能购买一张,直到最后一张票卖完了才结束
	//行为:三个人购买100张票
	//在只有一个线程方法的前提下,每次购买一张,每个人调用一次,每人每次购买一张
	//线程体的方法为:每次购买一张的行为,直到100张结束
	@Override
	public void run() {
		//由于我们不知道什么时候买到100张,所以可以使用死循环
		while(true){
			
			System.out.println(Thread.currentThread().getName()+"购买倒数第"+tikets+"张");
			if(tikets<=0){break;}
			tikets--;
			
		}
	}
	
	public static void main(String[] args) {
		Web12306 web=new Web12306();
		//由于是三个人同抢100张票,所以是在同一个对象里面操作,不过这个对象有三个线程
		Thread th1=new Thread(web,"黄牛一");
		Thread th2=new Thread(web,"黄牛二");
		Thread th3=new Thread(web,"黄牛三");
		//开启三个人同时抢票的模式,就是 开启三个线程
		th1.start();
		th2.start();
		th3.start();
		/*运行结果如下:(部分)
				 *  黄牛一购买倒数第100张
					黄牛一购买倒数第99张
					黄牛一购买倒数第98张
					黄牛一购买倒数第97张
					黄牛一购买倒数第96张
					黄牛一购买倒数第95张
					黄牛一购买倒数第94张
					黄牛一购买倒数第93张
					黄牛一购买倒数第92张
					黄牛一购买倒数第91张
					黄牛一购买倒数第90张
					黄牛一购买倒数第89张
					黄牛一购买倒数第88张
					黄牛一购买倒数第87张
					黄牛一购买倒数第86张
					黄牛一购买倒数第85张
					黄牛三购买倒数第100张
					黄牛三购买倒数第83张
					黄牛三购买倒数第82张
					黄牛三购买倒数第81张
					黄牛三购买倒数第80张
					黄牛三购买倒数第79张
					黄牛二购买倒数第100张
			通过以上我们可以发现,虽然实现了每次每人抢一次票的功能,
			但是有的人抢到同样票,这个是因为线程不安全
		*/
		
	}

}
案例二:龟兔赛跑

需求:兔子、乌龟同时在跑,当某一个人跑到100步时,比赛结束,兔子每跑10步休息5ms

package thread01;
/*
 * 龟兔赛跑: 兔子,乌龟同时在跑,有人跑到100步游戏结束,兔子跑十步休息5ms
 * 
 * 分析:龟兔同时在赛跑,都是在赛跑,说明是执行同样的方法
 * 		当跑满100步时,说明已经比赛结束,那么说明需要有标识,提示说明时候结束比赛
 * 判断条件:某个跑满100步		
 */
public class Race04 implements Runnable{
	//3.设置一个标识
	boolean flag=false;

	//1.龟兔赛跑,每次执行的动作
	//2.在这个方法体中,需要一个判断
	//	2.1某个跑满100步,给一个标识
	//	2.2告诉下一个,上面已经赢了,停止跑步
	@Override
	public void run() {
		int i=1;
		//判断条件一,当跑满100步,存储获胜者的名字
		while(i<=100){
			//如果是乌龟直接跑,但如果是兔子则应跑10步休息5ms
			if("兔子".equals(Thread.currentThread().getName())&&(i%10==0)){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");	
			i++;
			//当跑到100步的时,设置变量flag变为true,其中flag为共享的资源
			if(i==100){
				flag=true;
				System.out.println(Thread.currentThread().getName()+"获胜了");
				break;
			}
			if(flag){
				break;
			}
			
		
		}
	}
	//主方法
	public static void main(String[] args) throws InterruptedException {
		//1.创建对象
		Race04 race=new Race04();
		//2.开启线程 
		//2.1创建Thread对象
		Thread tortise=new Thread(race,"乌龟");
		Thread rabbit=new Thread(race,"兔子");
		//2.2开启线程
		tortise.start();
		rabbit.start();
	
	}
}

3.实现Callable接口,重写call方法
  1. 实现Collable接口,重写call()方法,方法中定义线程体
  2. 开启
    1. 创建本身对象:就是构建自己的实例
    2. 创建执行任务:构建一个线程池
    3. 提交执行:
    4. 获取结果
package thread01;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/*
 * 龟兔赛跑: 兔子,乌龟同时在跑,有人跑到100步游戏结束,兔子跑十步休息5ms
 * 
 * 分析:龟兔同时在赛跑,都是在赛跑,说明是执行同样的方法
 * 		当跑满100步时,说明已经比赛结束,那么说明需要有标识,提示说明时候结束比赛
 * 判断条件:某个跑满100步		
 */
public class Race05 implements Callable{
	//3.设置一个标识
	boolean flag=false;


	//主方法
	public static void main(String[] args) throws InterruptedException, ExecutionException  {
		//01.创建对象
		Race05 race=new Race05();
		//02.创建执行任务 设置两个线程
		ExecutorService server=Executors.newFixedThreadPool(2);
		//03.提交执行
		Future result01=server.submit(race);//传入的参数是 对象race
		Future result02=server.submit(race);
		//04.获取结果
		String s1=(String) result01.get();
		String s2=(String) result01.get();
		//05.打印
		System.out.println(s1+"-->"+s2);
	
	}
	//1.龟兔赛跑,每次执行的动作
	//2.在这个方法体中,需要一个判断
	//	2.1某个跑满100步,给一个标识
	//	2.2告诉下一个,上面已经赢了,停止跑步
	@Override
	public Object call() throws Exception {

		int i=1;
		//判断条件一,当跑满100步,存储获胜者的名字
		while(i<=100){
			//如果是乌龟直接跑,但如果是兔子则应跑10步休息5ms
			if("兔子".equals(Thread.currentThread().getName())&&(i%10==0)){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");	
			i++;
			//当跑到100步的时,设置变量flag变为true,其中flag为共享的资源
			if(i==100){
				flag=true;
				System.out.println(Thread.currentThread().getName()+"获胜了");
				break;
			}
			if(flag){
				break;
			}

		}
		return Thread.currentThread().getName();
	}
}

其他的类也同样能够使用线程 如:成员内部类 局部内部类以及匿名内部类

代码如下:

package thread02;

public class Demo01 {
	//成员内部类
	static class Inner01 implements Runnable{

		@Override
		public void run() {
			for(int i=0;i<10;i++){
				//线程休眠10毫秒
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("成员成员内部类"+i);
			}
		}
		
	}
	
		
	public static void main(String[] args) {
		//方法内部类
		class Inner02 implements Runnable{
			@Override
			public void run() {			
				for(int i=0;i<10;i++){
				try {
					//线程休眠10毫秒
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("局部成员内部类"+i);
			}}
			
		}
		//创建对象
		Inner01 inner01=new Inner01();
		Inner02 inner02=new Inner02();
		//创建Thread对象
		Thread inner001=new Thread(inner01);
		Thread inner002=new Thread(inner02);
		//开启线程
		inner001.start();
		inner002.start();
		//匿名内部类  
		new Thread(new Runnable(){
			//匿名内部内的方法
			@Override
			public void run() {			
				for(int i=0;i<10;i++){
				try {
					//线程休眠10毫秒
					Thread.sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("匿名内部类"+i);
			}}
			
		}){}.start();
		//正常的方法
			for(int i=0;i<10;i++){
			try {
				//线程休眠10毫秒
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("正常方法"+i);
		}
	}
}

线程的状态问题

状态解释
NEW启动
RUNNABLE可运行线程的线程状态。
BLCOKED受阻塞并且正在等待监视器锁的某一线程的线程状态。
WAITING某一等待线程的线程状态。
TIMED_WAITING具有指定等待时间的某一等待线程的线程状态。
TERMINATED已终止线程的线程状态。
线程的状态过程
  1. 新生状态:new
  2. 就绪状态:start(),线程就会进入到就绪队列,等待cpu的调度
  3. 运行状态:
  4. 阻塞状态:非正常执行完毕,通过程序控制
  5. 终止状态:

注意:一个线程一旦进入到终止状态,没有办法恢复了,就算是重写一个new 线程,也不是刚才那个线程了。

一个线程一旦进入到阻塞状态,无法直接恢复到运行,等待阻塞解除之后,恢复到就绪状态。

为什么不是到运行状态呢?这个是因为要等待cpu的调度。

如何进入到就绪状态
  1. 正常的开始 start()
  2. 阻塞状态解除
  3. 线程切换,被切换的线程就进入到就绪状态
  4. yield()礼让线程
如何进入到阻塞状态
  1. sleep()方法 线程休眠
    1. 可以模拟网络延迟
    2. 放大问题的可能性
    3. 也叫做抱着资源睡觉,同步的对象资源,让出cpu资源
  2. join()方法
  3. wait()方法
如何让线程进入到终止状态
  1. 正常执行完毕
  2. destorty()|stop() 已过时
  3. 通过标识手动判断-建议
案例一:模拟网络延迟 (进入到阻塞状态 sleep())
package thread02;

public class StateDemo02 implements Runnable{
	public static void main(String[] args) {
		new Thread(new StateDemo02()).start();;
	}
	//模拟倒计时

	@Override
	public void run() {
		for(int i=10;i>=0;i--){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("倒计时:"+i);
		}
	}
}

案例二:进入到就绪状态 yield
package thread02;

public class YieldDemo03 implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+"start");
		//每次执行到该行时候,让出资源
        Thread.yield();//静态方法   礼让线程
		System.out.println(Thread.currentThread().getName()+"end");
	}
	
	public static void main(String[] args) {
		new Thread(new YieldDemo03(),"我是A的").start();
		new Thread(new YieldDemo03(),"我是B的").start();
	}

}
案例三:合并线程
package thread02;
/*
*/
public class JoinDemo05 {
	public static void main(String[] args) {
		new Thread(new Father()).start();
	}
}


//自定义类    Father
class Father implements Runnable{

	@Override
	public void run() {
		System.out.println("1.Father want somking");
		System.out.println("2.give money to son");
		Thread th=new Thread(new Son());
		//父亲先开始
		th.start();
		//加入儿子
		try {
			th.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("8.somking");
	}
}

线程安全

多个线程同时操作同一个资源的时候,才有可能出现线程安全问题

通过同步synchronized关键字控制线程安全

同步方法:静态方法和成员方法

同步块:synchronized(列|this|资源|代码)

this,锁住当前对象

类名:类名.class 一个类的class对象,一个类只有一个Class对象

几个方法的优缺点
  1. 锁方法简单,但是效率低,因为锁的代码范围太大的整个方法,如果想要提高效率,可以同步块
  2. 同步类,相当于把这个类的所有对象全部同步了,如果想要只同步当前对象就可以实现,可以同步this
  3. 同步this,同步当前对象,锁住这个对象,这个对象的所有资源都被锁住了,如果只想要锁住其中某个资源,可以只锁这个资源
  4. 同步资源,一般指成员变量
    • 锁:要锁不变的东西,自定义引用数据类型的对象地址永远不变

注意

锁,要锁住不变的东西,会变的锁不住

同步的范围太大,效率低,同步的范围太小,锁不住

案例一:锁住当前对象this
package practice05;

public class JDS{

	public static void main(String[] args) {
		//1.创建对象
		SyncThread syncThread = new SyncThread();
		//2.创建线程 sycThread是对象
		Thread thread1 = new Thread(syncThread, "SyncThread1");
		Thread thread2 = new Thread(syncThread, "SyncThread2");
		//3.开启线程
		thread1.start();
		thread2.start();
	}

}
/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public  void run() {
	   //this,锁住当前对象
      synchronized(this) 
	   {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }

   public int getCount() {
      return count;
   }
}

​ 当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值