线程个人理解总结

一、线程的实现方式

a) 实现Runnable接口(推荐)

b) 继承Thread

c) 实现Callable接口(需要和线程池一起使用)

二、线程部分方法理解

a) setPriority(int newPriority):一定程度上影响线程对cpu资源的占有优先级

b) sleep(long millis):静态方法,在休眠时间到达后,重新回到就绪状态,供操作系统调配

c) interrupt():将该线程标记为中断状态,在遭遇到线程阻塞时(线程在调用Object类的wait方法,或者该类的join方法或sleep方法过程中受阻),则其中断状态将被清除,它还将收到一个InterruptedException但是,run方法仍然会继续执行。我在BigJava这本书里面看到,为了实现interrupt的中断效果,所以要执行的代码写在try语句类。例如:

public class MyRunnableimplements Runnable {
	

	@Override
	public void run() {
		while (true) {
			
					try {
						Thread.sleep();
						//...代码
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				
		}
	}
}

d) join():加入当前线程,并在该线程执行完之后,当前线程才会继续执行。 我画了一张图可以帮助理解:

main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程

e) yield() :让出本次获得的cpu资源,并且处于就绪状态,供操作系统调配

三、线程同步

a) 线程安全的原因

首先我们模拟线程安全问题的经典买票程序:

public class SellTicket implements Runnable {
	// 定义100张票
	private int tickets = 100;

	@Override
	public void run() {
		while (true) {
			// t1,t2,t3三个线程
			// 这一次的tickets = 100;
			if (tickets > 0) {

				try {
					Thread.sleep(100); 
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ (tickets--) + "张票");
				

			}
		}
<pre name="code" class="java">public class SellTicketDemo {
	public static void main(String[] args) {
		// 创建资源对象
		SellTicket st = new SellTicket();

		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");

		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}

}

 
运行该程序会发现有两个问题。一是会出现卖了相同的票,二是票出现了负数。产生该问题的原因: 

i.相同的票:CPU的一次操作必须是原子性的(可以理解为最小操作单位)。也就是ticket--实际上是两个原子性的操作(将ticket的值告诉程序和将ticket的值减一),即当程序运行到ticket--时,假设此时打印正在出售第100张票,还未来的及将ticket的值减一,该线程就得到cpu资源的时间已到,所以另一个线程进来时仍然打印正在出售第100张票

ii.出现负数票:随机性和延迟导致的。假设卖到还剩一张票时,t1线程运行到Thread.sleep(100)时,t2线程抢到cpu资源,同样运行到Thread.sleep(100)时,t3线程抢到cpu资源,由于此时三个线程都进入了if结构,也就产生了负数票

b) 线程安全问题的前提

i. 多线程环境

ii. 有共享数据

iii. 有多条语句操作共享数据

c)线程同步

i.目的:解决线程安全问题

ii.解决方案:

方案一:(1)使用synchronized关键字将多条语句操作共享数据的代码锁住,该方案利用的是每个对象都有自身唯一的一个锁。注意:锁住的对象必须是同一个对象。关键代码:

synchronized(this){
			if (tickets > 0) {
				try {
					Thread.sleep(100); 
				} catch (InterruptedException e) {
					e.printStackTrace();
				
				System.out.println(Thread.currentThread().getName() + "正在出售第"
					+ (tickets--) + "张票");
			}
		}
由于是实现Runnable接口来新建线程( SellTicket st = new SellTicket();),所以三个线程都有一个共同的对象st。因此在本例中可以直接用this关键字来表示该相同 对象

(2)多条语句操作共享数据的代码用方法封装,再用synchronized关键字修饰该方法。注意:该方法默认锁住的是所在类的this对象,若方法是静态方法,则锁住的是 类名.class 对象

方案二:使用lock锁(JDK1.5以后的新方法)。关键代码如下

public class SellTicket implements Runnable {

	// 定义票
	private int tickets = 100;

	// 定义锁对象
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			try {
				// 加锁
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "张票");
				}
			} finally {
				// 释放锁
				lock.unlock();
			}
		}
	}

}

四、线程通信

生产者消费问题

该问题的实质是在现实生活中,如果一个线程用来生产物品,另一个线程用来消费该物品,则需要考虑到实际情况。一方面生产线程不能生产超过实际容量的物品,同样的,消费线程在生产的物品消费完后,也不能继续消费。解决该问题的方法就是利用被锁住对象的wait(),notify(),notifyAll() 方法。根据实际情况当该线程需要等待时,便调用wait()方法,同样的,在生产完或消费完后边调用notify()或notifyAll()方法唤醒其它线程。注意,notify()方法是唤醒在等待池中的随机单个线程,而notifyAll是唤醒等待池中的所有线程。

五、线程池

i.线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

ii.如何实现线程池的代码

A:调用Executors类的方法创建线程池。例如创建一个带有指定线程个数的线程池

ExecutorService pool = Executors.newFixedThreadPool(2);

B:调用ExcutorService类的submit方法执行线程的代码,结束后,重新回到线程池成为空闲状态

C:调用ExcutorService类的shutdown方法启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

这里用实现线程的第三种方式实现Callable(V)接口,注意与Runable接口不同的是:

A:该方法必须和线程池共同使用

B:该线程要重写的方法是call()方法

C:call()方法有一个返回值,返回类型是该方法的泛型V

public class MyCallable implements Callable {

	@Override
	public Object call() throws Exception {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
		return null;
	}

}
public class CallableDemo {
	public static void main(String[] args) {
		//创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		
		//可以执行Runnable对象或者Callable对象代表的线程
		pool.submit(new MyCallable());
		pool.submit(new MyCallable());
		
		//结束
		pool.shutdown();
	}
}

六、线程生命周期图

注:该图是引用传智播客刘意老师的。本博客的实例也是引用刘意老师的

七、定时器类

Timer类:指定任务及该任务执行的时间和频率。

TimerTask:制定任务。该类也是实现了Runnable接口,需要重写run()方法。

使用实例:

// 做一个任务
class MyTask extends TimerTask {

	private Timer t;
	
	public MyTask(){}
	
	public MyTask(Timer t){
		this.t = t;
	}
	
	@Override
	public void run() {
		System.out.println("beng,爆炸了");
		t.cancel();
	}

}
public class TimerDemo {
	public static void main(String[] args) {
		// 创建定时器对象
		Timer t = new Timer();
		// 3秒后执行爆炸任务
		// t.schedule(new MyTask(), 3000);
		//结束任务
		t.schedule(new MyTask(t), 3000);
	}
}
因为本例中是要任务只要实施一次,而计时器run方法结束后不会自动终止。所以将Timer的对象传入MyTask类中(如果直接在main中调用有可能引发线程安全问题)调用 cancel()方法终止计时器。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值