一、多线程的创建方式
- 通过继承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(); // 开启了一个新线程
}
}
- 通过实现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(); // 开启了一个新线程
}
}
此时代码就变得越来越简洁。
- 通过实现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();
// 在主线程中插入子线程,主线程被阻塞,直到子线程执行完毕
// 主线程才继续执行
三、线程同步问题
-
什么是线程同步问题?
当多个线程需要访问同个共享数据时,为了避免混乱,就使数据同一时刻只能被一个线程访问。
可以通过给数据加锁来实现同步,即只有拿到锁钥匙的线程,才能够访问这个共享数据,可以有多把相同的锁,但只能有一把钥匙。 -
锁个名称都叫synchronized,每个对象都有属于它的无数把锁。
-
案例:火车站多窗口卖票
(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).案例分析:
包子铺:没有包子时就做包子,有包子时就唤醒吃货吃包子,然后进入阻塞状态
吃货:有包子时就吃,没有包子时就唤醒包子铺做包子,然后进入阻塞状态
包子:共享资源
(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);
}
}
}