JAVA中的线程

151 阅读6分钟

Java中线程创建方式

在java中有三种创建线程的方式

1.继承Thread类,重写run()方法

2.实现Runnable接口,实现run()方法

3.实现Callable接口,并实现call()方法

接下来我来给大家介绍前两种方法

1.通过继承Thread类来创建线程

继承Thread类必须重写run()方法,在run()方法中定义线程体,该方法是新线程的入口处.只有通过调用start()方法才能开启这个线程,如果直接调用run()方法并不能开启线程.

public class ThreadDemo01 extends Thread{
	public static void main(String[] args) {
		ThreadDemo01 th=new ThreadDemo01();
		//开启多线程
		th.start();
		//th.run() 注意:这是方法的调用 而不是多线程
		for(int i=1;i<=20;i++){
			System.out.println("我要喝水");
		}
		
	}
	@Override
	public void run() {
		for(int i=1;i<=20;i++){
			System.out.println("一遍吃饭...");
		}
	}
	
}

在执行th.start()之后,就会开启这个线程,这个线程中的语句就是写在run()方法中语句.

开启新线程之后就是就像两辆马车同时奔跑,互不影响

如果我们没有开启多线程,就相当于一辆马车先跑,跑到终点后另外一辆马车开炮.

2.通过实现Runnable接口来创建线程

通过实现Runnable接口来创建线程可以避免单继承的局限性,可以实现资源的共享

public class ThreadDemo01 implements Runnable{
	public static void main(String[] args) {
		ThreadDemo01 th=new ThreadDemo01();
		Thread t=new Thread(th);//因为开启线程的方法在Thread类中,Thread做为代理类出现
		//开启多线程
		t.start();
		//th.run() 注意:这是方法的调用 而不是多线程
		for(int i=1;i<=20;i++){
			System.out.println("我要喝水");
		}
		
	}
	@Override
	public void run() {
		for(int i=1;i<=20;i++){
			System.out.println("一遍吃饭...");
		}
	}
	
}

因为在Runnable接口中没有开启线程的方法,所以需要用Thread类作为代理类,调用start()方法,开启多线程

3.通过多线程模拟买火车票

public class BuyTest implements Runnable{
	//车票
	int tikets=20;
	
	public static void main(String[] args) {
		BuyTest bt=new BuyTest();
		//开启三个线程
		new Thread(bt,"张三").start();
		new Thread(bt,"李四").start();
		new Thread(bt,"王五").start();
	}

	@Override
	public void run() {
		//循环买票
		while(true){
			if(tikets<=0){
				break;
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"正在购买第"+tikets--+"张票");
			
		}
		
		
	}
	
	
}

程序运行结果如下:

张三正在购买第20张票
王五正在购买第19张票
李四正在购买第18张票
王五正在购买第17张票
李四正在购买第16张票
张三正在购买第17张票
王五正在购买第15张票
李四正在购买第13张票
张三正在购买第14张票
李四正在购买第12张票
张三正在购买第12张票
王五正在购买第11张票
王五正在购买第10张票
李四正在购买第8张票
张三正在购买第9张票
张三正在购买第7张票
李四正在购买第6张票
王五正在购买第5张票
王五正在购买第4张票
李四正在购买第3张票
张三正在购买第3张票
王五正在购买第2张票
李四正在购买第0张票
张三正在购买第1张票

可以看到三个人买20张票,一个线程代表一个人,但是在买票过程中最后面出现了买0张的问题,这是由于线程的不安全问题引起的.

2.线程状态

线程是一个动态执行的过程,它也有一个从产生到死亡的过程

1.新生状态:new

2.就绪状态:runnable

3.运行状态:running

4.阻塞状态:blocked

5.执行完毕:dead

实际状态转换图如下:

进入就绪状态的方式:

1.start()方法

2.阻塞解除

3.线程切换,被切换的线程进入到就绪状态

4.yield()------礼让线程

进入阻塞状态的方式:

1.sleep()方法

2.join()方法

3.wait()方法

进入终止状态的方式:

1.正常执行

2.destroy()|stop()-----已经过时

3.通过标识手动判断

1.sleep()---线程休眠


作用:

1.模拟网络延迟

2.放大问题的可能性

通过sleep()方法进入休眠的线程只会让出CPU资源,并不会释放拥有对象的资源

下面通过sleep()方法模拟倒计时:

public class Demo01 extends Thread{
	public static void main(String[] args) {
		Demo01 demo01=new Demo01();
		demo01.start();
		
	}
		public void run() {
			for (int i = 10; i >=0; i--) {
				
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				if(i==0){
					System.out.println("对我开炮....");
					break;
				}
				System.out.println("倒计时:"+i);
			}
		}
	
}

程序运行结果为:

倒计时:10
倒计时:9
倒计时:8
倒计时:7
倒计时:6
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1
对我开炮....

2.yield()---高风亮节,礼让线程


当一个线程调用yeild()方法,暂停当前正在执行的线程对象,并执行其它线程

public class Demo02 implements Runnable{
	public static void main(String[] args) {
		new Thread(new Demo02(),"A:").start();
		new Thread(new Demo02(),"B:").start();
	}

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+"start...");
		Thread.yield();
		System.out.println(Thread.currentThread().getName()+"end...");
		
	}
}

程序运行结果为:

A:start...
B:start...
A:end...
B:end...

3.join()---合并线程,插队线程

例子:

public class JoinDemo {
	public static void main(String[] args) {
		new Thread(new Father()).start();
	}
}
class Father implements Runnable{

	@Override
	public void run() {
		System.out.println("上厕所没纸");
		System.out.println("打电话,让儿子带纸回来");
		Thread th=new Thread(new Son());
		th.start();
		try {
			th.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("儿子没了");
		}
		System.out.println("老爸提裤子,出来了");
		
	}
	
}
class Son implements Runnable{

	@Override
	public void run() {
		System.out.println("接到电话,去买纸...");
		System.out.println("路上遇到美女,跟其搭讪十秒...");
		for(int i=1;i<=10;i++){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i+"s....");
		}
		System.out.println("赶紧去买纸");
		System.out.println("把纸递给爸爸..");
	}
	
}

程序运行的结果为:

上厕所没纸
打电话,让儿子带纸回来
接到电话,去买纸...
路上遇到美女,跟其搭讪十秒...
1s....
2s....
3s....
4s....
5s....
6s....
7s....
8s....
9s....
10s....
赶紧去买纸
把纸递给爸爸..
老爸提裤子,出来了

3.线程安全

上面写的模拟买火车票我们看到还会有人买到第零张票,为了解决出现这样的线程不安全问题.

我们需要用到同步synchronized关键字去控制线程安全

关键字synchronized可以同步方法,同步块.

同步方法可以同步静态方法及其成员方法

同步块方法

synchronized(类|this|资源){
    代码
}

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

同步的范围太大,效率低.

同步的范围太小,锁不住.

使用关键字synchronized锁的事物应该为不变的事物,变化的事物锁不住

重写模拟买票

public class BuyTikets implements Runnable {
    	@Override
    	public void run() {
    		while (true) {
    			synchronized (Tikets.class) {
    				if (Tikets.tikets <= 0) {
    					break;
    				}
    				try {
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName() + "正在购买第" + Tikets.tikets-- + "张票");
    			}
    		}
    	}
    	public static void main(String[] args) {
    		BuyTikets td = new BuyTikets();
    		new Thread(td, "张三").start();
    		new Thread(td, "李四").start();
    		new Thread(td, "王五").start();
    	}
    }
    class Tikets {
    	static int tikets = 20;
    }