多线程

一、多线程的创建方式

  1. 通过继承Thread类,重写其中的run方法:
class MyThread extends Thread{
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()); // 输出正在运行该行代码的线程名称
	}
}
public class Main() {
	public static void main(String[] args) {
		new MyThread().start(); // 开启了一个新线程
	}
}
  1. 通过实现Runnable接口,重写其中的run方法:
class RunnableImpl implements Runnable() {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}
public class Main() {
	public static void main(String[] args) {
		new Thread(new RunnableImpl()).start(); // 开启了一个新线程
	}
}

给Thread传递的是一个接口的实现类,故可以采用匿名内部类:

public class Main() {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName());
			}
		}).start(); // 开启了一个新线程
	}
}

而当传入的参数是匿名内部类时,又可以使用lambda表达式(函数编程思想):

public class Main() {
	public static void main(String[] args) {
		new Thread(()->{
			System.out.println(Thread.currentThread().getName());
		}).start(); // 开启了一个新线程
	}
}

此时代码就变得越来越简洁。

  1. 通过实现Callable接口创建线程,可以得到线程的返回值:
public class CallableImpl implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for(int i = 0; i<10; i++) {
			sum += i;
		}
		return sum;
	}
}
public class Main() {
	public static void main(String[] args) {
		// 将Callable包装成一个Runnable对象
		FutureTask<Integet> task = new FutureTask<>(new CallableImpl());
		new Thread(task).start();
		// 通过调用FutureTask的方法get, 得到线程的返回值
		int num = task.get().intValue();
	}
}

二、线程类的常用方法

1.设置线程的优先级:

	int getPriority(); 得到线程的优先级
	void setPriority(int newPriority); 改变线程的优先级

2.强迫一个线程睡眠N毫秒:

	Thread.sleep(N);

3.设置守护线程:

	boolean isDaemon(); 判断是否为守护线程
	void setDaemon(); 设置守护线程

4.插入子线程:

	void join();
	// 在主线程中插入子线程,主线程被阻塞,直到子线程执行完毕
	// 主线程才继续执行

三、线程同步问题

  1. 什么是线程同步问题?
    当多个线程需要访问同个共享数据时,为了避免混乱,就使数据同一时刻只能被一个线程访问。
    可以通过给数据加锁来实现同步,即只有拿到锁钥匙的线程,才能够访问这个共享数据,可以有多把相同的锁,但只能有一把钥匙。

  2. 锁个名称都叫synchronized,每个对象都有属于它的无数把锁。

  3. 案例:火车站多窗口卖票

    (1).不对共享数据加锁:

public class demo01_SellTicket {
	public static void main(String[] args) {
		new SellWindow().start();
		new SellWindow().start();
	}
}

class SellWindow extends Thread {
	static int ticket = 10;
	@Override
	public void run() {
		while(ticket>0) {
			System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");
			// 卖票要办手续,需要时间,让线程睡1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
		}
	}
}
/*
输出
Thread-1:正在售卖第10张票
Thread-0:正在售卖第10张票
Thread-0:正在售卖第8张票
Thread-1:正在售卖第8张票
Thread-0:正在售卖第7张票
Thread-1:正在售卖第7张票
Thread-1:正在售卖第6张票
Thread-0:正在售卖第6张票
Thread-0:正在售卖第4张票
Thread-1:正在售卖第4张票
Thread-1:正在售卖第3张票
Thread-0:正在售卖第2张票
Thread-1:正在售卖第0张票
*/

可以看出售票窗口卖出了很多重复的票,有的票又没有卖出,还卖出了一张不存在的票。

System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");

当两个线程并发执行到这行代码时,就会打印出两张相同的票

ticket--;

当两个线程并发执行到这行代码时,ticket会被减两次,ticket由10变成8,这时第9张票就卖不出去了

		while(ticket>0) {
			System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");

在Thread-1线程:当ticket的值为1时,while(ticket>0)编译通过,将执行第二行语句打印这张票,但此时该线程却失去了cup,轮到Thread-0来执行:

ticket--;

当Thread-0执行了这步操作后,ticket的值就为0了,再轮到Thread-1执行它剩下的代码

System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");

这时Thread-1就打印出了一张不存在的票。

以上情况会引发线程安全问题,根源是因为存在多个线程同时对共享数据在做更改。为了避免,就使共享数据只能同时被一个线程访问。

(2).对共享数据加锁:

给代码块加锁(同步代码块)
public class demo01_SellTicket {
	public static void main(String[] args) {
		new SellWindow().start();
		new SellWindow().start();
	}
}

class SellWindow extends Thread {
	static Object key = new Object();
	static int ticket = 10;
	@Override
	public void run() {
		while(true) {
			synchronized(key) {
				if(ticket>0) {
				System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");
				// 卖票要办手续,需要时间,让线程睡1秒
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				ticket--;
			}
		}
	}
	}
}
输出
Thread-0:正在售卖第10张票
Thread-0:正在售卖第9张票
Thread-0:正在售卖第8张票
Thread-1:正在售卖第7张票
Thread-1:正在售卖第6张票
Thread-1:正在售卖第5张票
Thread-1:正在售卖第4张票
Thread-1:正在售卖第3张票
Thread-1:正在售卖第2张票
Thread-1:正在售卖第1张票

给方法加锁(同步方法)

public class demo01_SellTicket {
	public static void main(String[] args) {
		Runnable task = new RunnableImpl();
		new Thread(task).start();
		new Thread(task).start();
	}
}

class RunnableImpl implements Runnable {
	static int ticket = 10;
	@Override
	public void run() {
		while(true) {
			sell();
		}
	}
	private synchronized void sell() {
			if(ticket>0) {
			System.out.println(Thread.currentThread().getName()+":正在售卖第"+ticket+"张票");
			// 卖票要办手续,需要时间,让线程睡1秒
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
		}
	}
}
输出
Thread-0:正在售卖第10张票
Thread-0:正在售卖第9张票
Thread-1:正在售卖第8张票
Thread-1:正在售卖第7张票
Thread-1:正在售卖第6张票
Thread-1:正在售卖第5张票
Thread-1:正在售卖第4张票
Thread-0:正在售卖第3张票
Thread-0:正在售卖第2张票
Thread-0:正在售卖第1张票

注:同步方法的锁对象就是调用该方法的对象。如上面的两个线程的锁对象都是task。

  1. 线程通信案例:包子铺与吃货
    (1).案例分析:
    包子铺:没有包子时就做包子,有包子时就唤醒吃货吃包子,然后进入阻塞状态
    吃货:有包子时就吃,没有包子时就唤醒包子铺做包子,然后进入阻塞状态
    包子:共享资源
    (2).代码实现:
class BaoZi {
	String pi;
	String xian;
	boolean flag = false;
}
class BaoZiPu extends Thread {
	BaoZi bz;
	public BaoZiPu(BaoZi bz) {
		this.bz = bz;
	}
	public void run() {
		int count = 0;
		while(true) {
			synchronized(bz) {
				if(bz.flag==true) {
					try {
						bz.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				if(count%2==0) {
					bz.pi = "薄皮";
					bz.xian = "韭菜猪肉馅";
				} else {
					bz.pi = "冰皮";
					bz.xian = "牛肉大葱馅";
				}
				System.out.println("包子铺正在生产"+bz.pi+bz.xian+"包子。");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				bz.flag = true;
				count++;
				System.out.println("唤醒吃货。");
				bz.notify();
			}
		}	
	}
}
class ChiHuo extends Thread{
	BaoZi bz;
	public ChiHuo(BaoZi bz) {
		this.bz = bz;
	}
	public void run() {
		while(true) {
			synchronized(bz) {
				if(bz.flag==false) {
					try {
						bz.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("吃货正在吃"+bz.pi+bz.xian+"包子。");
				bz.flag = false;
				System.out.println("包子吃完了,唤醒包子铺。");
				bz.notify();
			}
		}
	}
}
public class Main {
	public static void main(String[] args) {
		BaoZi bz = new BaoZi();
		new BaoZiPu(bz).start();
		new ChiHuo(bz).start();
	}
}
/*输出
包子铺正在生产薄皮韭菜猪肉馅包子。
唤醒吃货。
吃货正在吃薄皮韭菜猪肉馅包子。
包子吃完了,唤醒包子铺。
包子铺正在生产冰皮牛肉大葱馅包子。
唤醒吃货。
吃货正在吃冰皮牛肉大葱馅包子。
包子吃完了,唤醒包子铺。
包子铺正在生产薄皮韭菜猪肉馅包子。
唤醒吃货。
*/

四、线程池

public class demo05_ThreadPool {
	public static void main(String[] args) {
	// newCachedThreadPool根据需要,每提交一个任务就创建一个新的线程
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.submit(new RunnableImpl_0());
		exec.submit(new RunnableImpl_0());
		exec.submit(new RunnableImpl_0());
		// 线程池使用完要将它关掉,否则程序不会停止
		exec.shutdown();
	}
}

class RunnableImpl_0 implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"正在执行。");
	}
}

五、Future模式

计算从1到n项的Fibonacci函数值之和:

public class demo041_Fib {
	public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        List<CalculateTask> taskList = new ArrayList<CalculateTask>();
        List<Future<Integer>> results = new ArrayList<Future<Integer>>();
        int sum = 0;
        for(int i = 1; i<=n; i++) {
        	CalculateTask task = new CalculateTask(i);
        	taskList.add(task);
        	results.add(exec.submit(task));
        }
        for(int j = 0; j<n; j++) {
        	try {
				sum += results.get(j).get().intValue();
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
        }
        System.out.println("sum="+sum);
        exec.shutdown();
	}
}
class CalculateTask implements Callable {
	
	int n;
	public CalculateTask(int n) {
		this.n = n;
	}
	public Integer call() throws Exception {
		int r = Fib(n);
		return r;
	}
	public int Fib(int n) {
		if(n==1||n==2) {
			return 1;
		} else {
			return Fib(n-1)+Fib(n-2);
		}
 	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值