这一篇博文是【大数据技术●降龙十八掌】系列文章的其中一篇,点击查看目录:大数据技术●降龙十八掌
1、join() 方法
join()方法可以理解为线程插队。停止当前线程,先执行插入的线程,当插入的线程执行完毕后,再执行当前线程。看下面的例子:
package join;
/**
* Created by 鸣宇淳 on 2017/12/7.
*/
public class MyJoinRunner implements Runnable {
//子线程
public void run() {
for (int n = 0; n < 100; n++) {
System.out.println(Thread.currentThread().getName() + ":" + n);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MyJoin {
public static void main(String[] args) throws InterruptedException {
MyJoinRunner runner=new MyJoinRunner();
Thread t1=new Thread(runner,"子线程");
t1.start();
//将子线程插队,先执行子线程,然后再执行其他线程(主线程)
t1.join();
for (int n = 0; n < 100; n++) {
System.out.println(Thread.currentThread().getName() + ":" + n);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出结果:
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
子线程:5
子线程:6
.......
.......
main:0
main:1
main:2
main:3
main:4
main:5
.......
.......
2、一个多线程会出问题的例子
假设有个场景是自动卖票,一共有5张票,新建三个线程来同时卖票,往往就会出问题。如下实例所示。
/**
* Created by 鸣宇淳 on 2017/12/8.
*/
public class SellTicketRunner implements Runnable {
private int ticket = 5; //一共有n张票
//一个子线程执行的方法
public void run() {
while (true) {
if (this.ticket > 0) {
//判断如果票大于0,就先睡眠,以达到模拟的效果。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
this.ticket--;
//打印剩余票数
System.out.println(Thread.currentThread().getName() + "正在卖票;剩余=" + this.ticket);
} else {
break;
}
}
}
}
public class MySellTicker {
public static void main(String[] args) throws InterruptedException {
SellTicketRunner runner=new SellTicketRunner();
System.out.println("卖票开始.....");
Thread t1=new Thread(runner,"线程一");
Thread t2=new Thread(runner,"线程二");
Thread t3=new Thread(runner,"线程三");
t1.start();
t2.start();
t3.start();
System.out.println("买票结束!");
}
}
输出为:
线程一正在卖票;剩余=4
线程二正在卖票;剩余=2
线程三正在卖票;剩余=2
线程一正在卖票;剩余=1
线程三正在卖票;剩余=-1
线程二正在卖票;剩余=-1
线程一正在卖票;剩余=-2
买票结束!
会发现,出现票超卖的情况,这是因为在判断余票数量后、卖票操作前的阶段,有可能有多个线程进入,然进行卖票操作。这就需要我们使用锁和同步进行限制。
3、同步和锁定
(1) 对象锁
java中每个对象都有一个内置锁。可以使用任意一个对象上的锁,来实现线程的同步。看下面的实例,是将上面有问题的卖票代码,添加上对象锁,来控制线程同步,以解决超卖的问题。
package sellticket;
/**
* Created by 鸣宇淳 on 2017/12/8.
*/
public class SellTicketRunner implements Runnable {
private int ticket = 5; //一共有n张票
private String lock=""; //创建一个对象,使用这个对象上的锁来实现线程同步
//一个子线程执行的方法
public void run() {
while (true) {
synchronized (lock) {
//将需要保护的代码块,锁住,防止多个线程同时进入这个代码块
if (this.ticket > 0) {
//判断如果票大于0,就先睡眠,以达到模拟的效果。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
this.ticket--;
//打印剩余票数
System.out.println(Thread.currentThread().getName() + "正在卖票;剩余=" + this.ticket);
} else {
break;
}
}
}
}
}
public class MySellTicker {
public static void main(String[] args) throws InterruptedException {
SellTicketRunner runner=new SellTicketRunner();
Thread t1=new Thread(runner,"线程一");
Thread t2=new Thread(runner,"线程二");
Thread t3=new Thread(runner,"线程三");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
System.out.println("买票结束!");
}
}
(2) 方法锁
可以在方法上添加上一个synchronized关键字,表明这个一个同步方法,通过锁定一个方法,同时只让一个线程执行这个方法,
package sellticket;
/**
* Created by 鸣宇淳 on 2017/12/8.
*/
public class SellTicketRunner2 implements Runnable {
private int ticket = 5; //一共有n张票
public void run() {
while (true) {
boolean isQuit = sell();
if (!isQuit) {
break;
}
}
}
//在方法上添加一个synchronized关键字,表名是同步方法
//将需要保护的方法,锁住,防止多个线程同时进入这个方法
private synchronized boolean sell() {
System.out.println(Thread.currentThread().getName() + "进入");
boolean isHav;
if (this.ticket > 0) {
isHav = true;
//判断如果票大于0,就先睡眠,以达到模拟的效果。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票
this.ticket--;
//打印剩余票数
System.out.println(Thread.currentThread().getName() + "正在卖票;剩余=" + this.ticket);
} else {
isHav = false;
}
System.out.println(Thread.currentThread().getName() + "退出\n");
return isHav;
}
}
public class MySellTicker {
public static void main(String[] args) throws InterruptedException {
SellTicketRunner2 runner=new SellTicketRunner2();
System.out.println("卖票开始.....");
Thread t1=new Thread(runner,"线程一");
Thread t2=new Thread(runner,"线程二");
Thread t3=new Thread(runner,"线程三");
t1.start();
t2.start();
t3.start();
}
}
当sell方法不加synchronized关键字时输出,看上去售票过程比较乱:
卖票开始.....
线程二进入
线程一进入
线程三进入
线程三正在卖票;剩余=3
线程一正在卖票;剩余=3
线程二正在卖票;剩余=3
线程一退出
线程一进入
线程三退出
线程二退出
线程二进入
线程三进入
线程二正在卖票;剩余=2
线程二退出
线程二进入
线程三正在卖票;剩余=2
线程三退出
线程三进入
线程一正在卖票;剩余=2
线程一退出
线程一进入
线程一正在卖票;剩余=1
线程一退出
线程一进入
线程一退出
线程三正在卖票;剩余=-1
线程三退出
线程三进入
线程三退出
线程二正在卖票;剩余=-1
线程二退出
线程二进入
线程二退出
如果加上synchronized,就能保证同时只有一个线程在执行sell方法,输出为:
卖票开始.....
线程一进入
线程一正在卖票;剩余=4
线程一退出
线程一进入
线程一正在卖票;剩余=3
线程一退出
线程一进入
线程一正在卖票;剩余=2
线程一退出
线程一进入
线程一正在卖票;剩余=1
线程一退出
线程一进入
线程一正在卖票;剩余=0
线程一退出
线程一进入
线程一退出
线程二进入
线程二退出
线程三进入
线程三退出
(3) static 方法的同步
在非static方法上添加synchronized,相当于对当前类的对象this加锁。
private synchronized void fun() throws InterruptedException {
Thread.sleep(100);
}
等同于:
private void fun() throws InterruptedException {
synchronized (this) {
Thread.sleep(100);
}
}
但是在static方法是先于类的对象而存在的,当没有实例化对象的时候,static方法已经可以调用了,那么在static方法上添加synchronized ,是对什么加锁呢?其实是对类的class对象加锁。
public class MyStaticDemo {
private synchronized static void fun() throws InterruptedException {
Thread.sleep(100);
}
}
等同于:
public class MyStaticDemo {
private static void fun() throws InterruptedException {
synchronized (MyStaticDemo.class) {
Thread.sleep(100);
}
}
}
4.死锁
一个死锁的实例:
/**
* Created by 鸣宇淳 on 2017/12/11.
*/
public class DeadLockRunner implements Runnable {
String[] source;
String[] target;
public DeadLockRunner(String[] source,String[] target)
{
this.source=source;
this.target=target;
}
public void run() {
synchronized (source) {
System.out.println(Thread.currentThread().getName() + ":进入source到target拷贝");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (target) {
for (int n = 0; n < source.length; n++) {
target[n] = source[0];
}
}
System.out.println(Thread.currentThread().getName() + ":完成source到target拷贝");
}
}
}
public class DeadLock {
public static void main(String[] args) {
String[] source = new String[]{"a", "b", "c", "d"};
String[] target = new String[]{"1", "2", "3", "4"};
DeadLockRunner runner1 = new DeadLockRunner(source,target);
DeadLockRunner runner2 = new DeadLockRunner(target,source);
Thread t1=new Thread(runner1,"线程1");
Thread t2=new Thread(runner2,"线程2");
t1.start();
t2.start();
}
}
运行时可以发现输出:
线程2:进入source到target拷贝
线程1:进入source到target拷贝
然后就卡在这里不继续运行了,根据输出结果和分析代码可得知,线程2是先执行的,线程2先锁定source后,就sleep了,然后线程1进入后锁定了target,也sleep了,当线程2醒来后要求target,但是被线程1锁定了,线程1醒来要求source,但是被线程2锁定了,两个线程就造成了死锁。
避免死锁的方法:
要确定获得锁的顺序,然后整个程序要遵守该顺序,按相反的顺序释放锁。
5. 线程等待
必须在同步环境内调用wait()、notify()、notfiyAll()方法,也就是说在在同步代码块或者同步方法内才能进行等待或者唤醒。wait()、notify()、notfiyAll()方法是Object的实例方法,所以每个对象上都可以有一个线程列表。
实例1:
package wait;
/**
* Created by 鸣宇淳 on 2017/12/11.
*/
public class MyWaitRunner implements Runnable {
private int total;
public MyWaitRunner(int n) {
this.total = n;
}
public void run() {
System.out.println(Thread.currentThread().getName() + "进入执行");
synchronized (this) {
for (int i = 0; i < total; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒对象监视器上的单个线程,当前是唤醒线程1
notify();
}
}
System.out.println(Thread.currentThread().getName() + "执行完毕");
}
}
public class MyWait {
public static void main(String[] args) throws InterruptedException {
MyWaitRunner runner1=new MyWaitRunner(100);
Thread t1=new Thread(runner1,"线程1");
t1.start();
synchronized (t1)
{
System.out.println("主线程做一些事情......");
System.out.println("等待子线程完成");
//等待线程1完成
t1.wait();
System.out.println("子线程完成");
}
}
}
实例2:
下面这个例子是模拟一个场景,司机开车带着乘客去北京游览故宫,乘客线程上车后,通知司机开车,然后乘客睡觉,让司机在到达后叫醒自己,通过这个例子看一下两个线程同步和通知功能的使用方法。
/**
* Created by 鸣宇淳 on 2017/12/12.
*/
public class Passenger implements Runnable {
//乘客线程方法
public void run() {
synchronized (ToBeiJing.lock) {
System.out.println("[乘客]已经上车");
try {
//通知司机开车
System.out.println("[乘客]通知司机开车");
ToBeiJing.driverThread.start();
System.out.println("[乘客]开始睡觉,到北京叫我");
ToBeiJing.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("[乘客]醒了,开始游览故宫");
}
}
}
public class Driver implements Runnable {
//司机线程方法
public void run() {
for (int n = 0; n < 5; n++) {
System.out.println("[司机]正在开车");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[司机]已经到北京了");
synchronized (ToBeiJing.lock) {
System.out.println("[司机]叫醒乘客");
//叫醒乘客
lock.notify();
}
}
}
public class ToBeiJing {
//定义一个锁,线程依据这个锁实现同步
public static String lock = "";
//司机线程
public static Thread driverThread;
//乘客线程
public static Thread passengerThread;
public static void main(String[] args) throws InterruptedException {
Driver driver = new Driver();
Passenger passenger = new Passenger();
driverThread = new Thread(driver, "司机线程");
passengerThread = new Thread(passenger, "乘客线程");
//乘客线程启动
passengerThread.start();
}
}
第三个实例:
这是个经典的生产者消费者例子,有多个生产者和多个消费者同时在生产和消费,有一个仓库,最大存储量是固定的,所以在生产和消费的时候要收到仓库量的限制,就需要到线程的同步和锁。看以下代码。
import java.util.ArrayList;
import java.util.List;
/**
* Created by 鸣宇淳 on 2017/12/12.
*/
public class Demo {
public static void main(String[] args) {
//各个线程中,要生产或者消费的个数
Integer[] producerNum = new Integer[]{10, 20, 30, 25, 40, 60};
Integer[] consumerNum = new Integer[]{20, 25, 35, 15, 50};
Godown godown = new Godown();
List<Thread> threads = new ArrayList<Thread>();
//创建生产者线程
for (Integer p : producerNum) {
Thread t = new Thread(new Producer(p, godown));
threads.add(t);
}
//创建消费者线程
for (Integer c : consumerNum) {
Thread t = new Thread(new Consumer(c, godown));
threads.add(t);
}
System.out.println("当前仓库中数量:" + godown.currNum);
//启动生产者和消费者线程
for (Thread t : threads) {
t.start();
}
}
}
//仓库类
public class Godown {
//最大库存量
public static final int MAX_SIZE = 100;
public int currNum;//当前库存量
/*
生产方法
*/
public synchronized void produce(int addNum) throws InterruptedException {
while (addNum + currNum > MAX_SIZE) {
System.out.println("要生产" + addNum + ",仓库空位数为" + (MAX_SIZE - currNum) + ",暂停生产");
this.wait();
}
//可以生产
currNum = currNum + addNum;
System.out.println("生产了" + addNum + ",当前库存量为:" + currNum);
//唤醒
this.notifyAll();
}
public synchronized void consume(int needNum) throws InterruptedException {
while (currNum < needNum) {
System.out.println("要求消费" + needNum + "个,剩余" + currNum + "个,不能消费,等待生产");
this.wait();
}
currNum = currNum - needNum;
System.out.println("消费了" + needNum + "个,当前库存量为:" + currNum);
//唤醒
notifyAll();
}
}
/**
* Created by 鸣宇淳 on 2017/12/12.
* <p>
* 生产者类
*/
public class Producer implements Runnable {
private int addNum;
private Godown godown;
public Producer(int addNum, Godown godown) {
this.addNum = addNum;
this.godown = godown;
}
public void run() {
try {
this.godown.produce(this.addNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Created by 鸣宇淳 on 2017/12/12.
* 消费者
*/
public class Consumer implements Runnable {
private int needNum;
private Godown godown;
public Consumer(int needNum, Godown godown) {
this.needNum = needNum;
this.godown = godown;
}
public void run() {
try {
this.godown.consume(needNum);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}