转载

浅谈java中的线程

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

  1. 继承Thread类,重写run()方法
  2. 实现Runnable接口,实现run()方法
  3. 实现Callable接口,并实现call()方法

下面主要说一下前两种方法.

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

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

示例1:

public class ThreadDemo01 extends Thread{
    	//多线程的线程体
    	@Override
    	public void run() {
    		for(int i=1;i<=20;i++){
    			System.out.println("一遍吃饭...");
    		}
    	}
    	public static void main(String[] args) {
    		//开启多线程 创建线程
    		ThreadDemo01 th=new ThreadDemo01();
    		//开启线程
    		th.start(); 
    		//th.run();   注意:这是方法的调用,不是多线程的开启		
    		for(int i=1;i<=20;i++){
    			System.out.println("一遍说话...");
    		}
    	}
    }
复制代码

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

开启新线程之后就像有了两辆车在两个车道上跑了起来,互不影响.

如果没有用线程写这段代码的话,只能把run()方法中的代码排在主方法的for循环之后,就好比两个人过一条窄胡同,只能一个人跟着一个人走.

这样写我们就可以实现一遍吃饭一遍说话

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

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

示例2:

public class ThreadDemo02 implements Runnable{
    	//定义线程体的方法,当被调用的时候,会逐行执行里面的代码
    	@Override
    	public void run() {
    		for(int i=1;i<=100;i++){
    			System.out.println("一边跑步...");
    		}
    	}
    	public static void main(String[] args) {
    		ThreadDemo02 th=new ThreadDemo02();
    		//开启线程//创建线程
    		Thread t=new Thread(th);  //因为开启线程的方法在Thread类中,Thread做为代理类出现
    		t.start();
    		for(int i=1;i<=100;i++){
    			System.out.println("一边听歌...");
    		}
    	}
    }
复制代码

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

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

示例3:

public class BuyTikets implements Runnable{
    	//车票
    	int tikets=20;
    	@Override
    	public void run() {
    		//循环买票
    		while(true){
    			if(tikets<=0){
    				break;
    			}
    			try {
    				Thread.sleep(200);  //线程睡眠100ms
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			System.out.println(Thread.currentThread().getName()+"正在购买第"+tikets--+"张票");
    		}
    	}
    	public static void main(String[] args) {
    		BuyTikets bt=new BuyTikets();
    		//开启三个线程
    		new Thread(bt,"张三").start();
    		new Thread(bt,"李四").start();
    		new Thread(bt,"王五").start();
    	}
    }
复制代码

程序运行结果为:

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

可以看到三个人买20张票.一个线程代表一个人.20张票由这三个人共同分享.但是这个程序还是有些问题,竟然有人可以买到第0张票,第-1张票.还有两个人买到同一张票.

2.线程状态

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

  1. 新生状态: new
  2. 就绪状态: runnable
  3. 运行状态: running
  4. 阻塞状态: blocked
  5. 执行完毕: dead

各个状态的转换图如下:

浅谈java中的线程

进入就绪状态的方式:

  1. start()方法
  2. 阻塞解除
  3. 线程切换,被切换的线程进入到就绪状态
  4. yield()----礼让进程

进入阻塞状态的方式:

  1. sleep()方法
  2. join()方法
  3. wait()方法

进入终止状态的方式:

  1. 正常执行完毕
  2. destroy() |stop() 已过时
  3. 通过标识手动判断

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

作用:

  1. 模拟网络延迟
  2. 放大问题的可能性

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

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

示例4:

public class Countdown implements Runnable{
    	public static void main(String[] args) {
    		 new Thread(new Countdown()).start();
    	}
    	@Override
    	public void run() {
    		for(int i=10;i>=0;i--){
    			try {
    				Thread.sleep(1000);
    			} 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()---高风亮节,礼让线程

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

示例5:

public class YieldDemo implements Runnable{
    	public static void main(String[] args) {
    		new Thread(new YieldDemo(),"A").start();
    		new Thread(new YieldDemo(),"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()---合并线程,插队线程

示例6:

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("路边有个电玩城,进去玩10s...");
		for(int i=1;i<=10;i++){
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(i+"s...");
		}
		System.out.println("赶紧去买醋...");
		System.out.println("把醋递给爸爸...");
	}
}
复制代码

程序运行结果为:

吃饺子没醋...
给儿子钱,让儿子去买醋..
接过钱,去买醋...
路边有个电玩城,进去玩10s...
1s...
2s...
3s...
4s...
5s...
6s...
7s...
8s...
9s...
10s...
赶紧去买醋...
把醋递给爸爸...
饺子蘸醋~
复制代码

3.线程安全

上面写的模拟买火车票我们看到有的人买到第-1张票,有的是两个人买到同一张票.这种情况就是多个线程同时操作同一个资源的时候,才可能会出现线程安全问题.

为了处理这个安全问题,我们可以通过同步 synchronized 关键字控制线程安全

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

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

同步块的方法:

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

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

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

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

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

重写模拟买票:

示例7:

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;
    }
复制代码

程序运行结果为:

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

这样就不会再出现买票出错的情况.

原文  https://juejin.im/post/5dbec4376fb9a0206721928d
正文到此结束
Loading...