目录
1 线程安全
什么是线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
列:模拟买票,三个线程同时对共享的票进行售卖
此时线程不安全,因为有三个线程同时访问1个对象
public class RunnableImpl implements Runnable {
private int ticket = 20;//1 定义票数量
@Override
public void run() {
while (true){
if (ticket > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "票卖掉了\t" + Thread.currentThread().getName());
ticket--;
}
}
}
}
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
Thread thread = new Thread(runnable, "--a窗口");
Thread thread1 = new Thread(runnable, "--b窗口");
Thread thread2 = new Thread(runnable, "--b窗口");
thread.start();
thread1.start();
thread2.start();
}
结果:
2 线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。有三种方式完成同步操作: 1. 同步代码块。 2. 同步方法。 3. 锁机制。
2.1 同步代码块
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问
synchronized(同步锁){
需要同步操作的代码
}
同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.(锁对象: 可以是任意类型),多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
2.1.1 列:使用同步代码块实现买票
public class RunnableImpl implements Runnable{
private int ticket=100;
//创建一个锁对象
Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第"+ticket+"张票卖掉了\t"+Thread.currentThread().getName());
ticket--;
}
}
}
}
}
public class Demo01Ticket {
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
Thread thread = new Thread(r, "a窗口");
Thread thread1 = new Thread(r, "b窗口");
Thread thread2 = new Thread(r, "c窗口");
thread.start();
thread1.start();
thread2.start();
}
}
结果:部分代码图
2.2 同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
public synchronized void method(){
可能会产生线程安全问题的代码
}
2.2.1 列:使用同步方法块实现买票
public void payTicket() {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第" + ticket + "张票卖掉了\t" + Thread.currentThread().getName());
ticket--;
}
}
}
/*
静态的同步方法
锁对象是谁?
不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(t>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.err.println(Thread.currentThread().getName()+"-->正在卖第"+t+"张票");
t--;
}
}
}
}
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
Thread thread = new Thread(r,"a窗口");
Thread thread2 = new Thread(r,"b窗口");
Thread thread3 = new Thread(r,"c窗口");
thread.start();
thread2.start();
thread3.start();
}
2.2.2 列:某银行卡账号上有1000元现金。一个人拿着存折去取钱,同时另一个人拿着卡去ATM上取钱,各自取钱400元。要求取钱过程中不能出现资源竞争:比如400元被取出两次、银行卡的账目不能小于0等。
public class Bank {
private int money;// 存款金额
public Bank(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public int getmoney(double money2) throws Exception {
// System.out.println(this.money);
synchronized (this) {
if ((int) money2 <= 0) {
System.out.println("你输入的金额有误");
return -1;
} else if (money2 > this.money) {
System.out.println("你的余额不足,请重新输入");
return -2;
} else {
// 模拟取钱过程
Thread.sleep(2000);
this.money -= money2;
System.out.println("取钱成功,你的余额为:" + this.money);
return (int) money2;
}
}
}
}
public class BankThread extends Thread {
private Bank bank;
private double money = 400;// 取款金额
public BankThread(Bank bank, String name) {
super(name);
this.bank = bank;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("你的存款为:" + bank.getMoney());
System.err.println("取款" + money + "元");
System.err.println(this.getName() + "取款金额为:" + this.bank.getmoney(money));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class BankTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Bank bank = new Bank(1000);
BankThread bt1 = new BankThread(bank, "在ATM");
bt1.start();
System.out.println("--------------");
BankThread bt2 = new BankThread(bank, "银行柜台");
bt2.sleep(5000);
bt2.start();
}
}
2.3 Lock锁
java.util.concurrent.locks.Lock(接口) 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。Lock锁也称同步锁
Lock l=new ReentrantLock();
l.lock();//public void lock()//加同步锁。
l.unlock();public void unlock(); //释放同步锁。
2.3.1 列:Lock锁实现售票
public class RunnableImpl implements Runnable{
private int ticket=20;
//1 创建ReentrantLock对象
Lock l=new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();//2 在可能出现安全问题的代码前调用Lock接口中lock()方法获取锁
if(ticket>0){
try {
Thread.sleep(100);
System.out.println("第"+ticket+"张票卖出了\t---窗口:"+Thread.currentThread().getName());
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();//3 释放锁
}
}
}
}
}
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
Thread thread = new Thread(r, "a窗口");
Thread thread1 = new Thread(r, "b窗口");
Thread thread2 = new Thread(r, "c窗口");
thread.start();
thread1.start();
thread2.start();
}
3 线程状态
在API中 java.lang.Thread.State 这个枚举中给出了五种线程状态:
<1>.新建状态 (New Thread)
在 Java 语言中使用 new 操作符创建一个线程后,该线程仅仅是一个空对象,它具备了线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。
<2>.就绪状态 (Runnable)
使用 start() 方法启动一个线程后,系统为该线程分配了除 CPU 外的所需资源,使该线程处于就绪状态。
<3>.运行状态 (Running)
Java 运行系统通过调度选中一个处于就绪状态的线程,使其占有 CPU 并转为运行状态。此时,系统真正执行线程的 run() 方法。
<4>.阻塞状态 (Blocked) 一个正在运行的线程因某些原因不能继续运行时,就进入阻塞状态。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
<5>.死亡状态 (Dead) 线程在 run() 方法执行结束后进入死亡状态。
当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。
4 线程通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同
4.1 为什么要处理线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们 希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据
4.2 如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
4.3 什么是等待唤醒机制
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
Java提供了3个重要方法巧妙解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。
- 调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。
- 调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。
- 调用notifyAll()使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态
4.3.1 列:生产者消费者模式:
用生产者消费者模式,模拟存取苹果的过程,其中有Apple类,Basket类,每一秒放一个苹果,篮子中只能存放5个苹果,放满以后取出依次取出5个苹果,一共有20个苹果,模拟这个存取过程。
public class Apple {
private int id;
public Apple(int id) {
this.id = id;
}
... ...
@Override
public String toString() {
return "苹果 [id=" + id + "]";
}
}
import java.util.LinkedList;
import java.util.List;
public class Basket {
// list定义装苹果的容器 先摘的苹果 后卖 先卖
private LinkedList<Apple> list = new LinkedList<Apple>();
// 定义苹果容器的最大容量
private int maxCount = 5;
/**
* //1.生产苹果的方法 一次生产一个 1s
*
* @param apple
* @throws Exception
*/
public void addAppler(Apple apple) throws Exception {
if (list.size() == maxCount) {// 满了
wait(); // 停止生产的线程
}
// 提醒消费定义消费的时间
notify();// 唤醒消费的线程
Thread.sleep(1000);// 模拟消费的时间
// 消费了之后就可以生产了
((LinkedList<Apple>) list).addFirst(apple);
System.out.println("存放了:" + apple);
}
/**
* 2.消费苹果的方法 一次消费一个 1s
*
* @throws Exception
*/
public void reduceApple() throws Exception {
if (list.size() == 0) {
wait();
}
notify();
Thread.sleep(1000);
Apple apple = ((LinkedList<Apple>) list).removeFirst();
System.err.println("消费了:" + apple);
}
/**
* 放苹果的方法 20个 再生产的时候不允许消费
*/
public synchronized void putApple() throws Exception {
for (int i = 1; i <= 20; i++) {
Apple apple = new Apple(i);
addAppler(apple);
}
}
// 4.取苹果的方法 20个
public synchronized void outApple() throws Exception {
for (int i = 1; i <= 20; i++) {
reduceApple();
}
}
}
public class ProducterThread extends Thread{
private Basket basket;
public ProducterThread(Basket basket) {
this.basket=basket;
}
@Override
public void run() {
try {
this.basket.putApple();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ComsumerThread extends Thread{
private Basket basket;
public ComsumerThread(Basket basket) {
this.basket=basket;
}
@Override
public void run() {
try {
this.basket.outApple();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Basket basket=new Basket();
ProducterThread pt=new ProducterThread(basket);
ComsumerThread ct=new ComsumerThread(basket);
pt.start();
ct.start();
}
}
4.3.2 列:实现老板卖包子,顾客吃包子
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//1 创建锁对象,保证唯一
Object o = new Object();
//2 创建顾客线程,顾客吃包子
new Thread() {
@Override
public void run() {
while (true) {
synchronized (o) {
System.out.println("顾客要一个包子");
try {
o.wait();//顾客等待老板做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客吃包子");
}
}
}
}.start();
//3 老板做包子
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o) {
System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
System.out.println("------------------------");
o.notify();
}
}
}
}.start();
}
}
结果:
4.3.3 列:实现老板卖包子,2个顾客吃包子
void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02WaitAndNotify {
public static void main(String[] args) {
//1 创建同步锁对象
Object obj=new Object();
//2 创建顾客
new Thread(){
@Override
public void run() {
while (true){
synchronized (obj){
System.out.println("顾客1告知老板要的包子的种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客1吃包子");
}
}
}
}.start();
//2 创建顾客
new Thread(){
@Override
public void run() {
while (true){
synchronized (obj){
System.out.println("顾客2告知老板要的包子的种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客2吃包子");
}
}
}
}.start();
//3 创建老板线程
new Thread(){
@Override
public void run() {
while (true){
try {
Thread.sleep(5000);//老板做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老板做好包子,告知顾客\n----------------------");
synchronized (obj){
obj.notifyAll();//唤醒顾客线程
}
}
}
}.start();
}
}
结果:
5 线程调度常用方法
线程调度:按照特定机制为多个线程分配CPU的使用权
- setPriority(int newPriority) 更改线程的优先级
- static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
- void join() 等待该线程终止 使当前线程暂停执行,等待其他线程结束后再继续执行本线程
- static void yield() 暂停当前正在执行的线程对象,并执行其他线程,但是不能保证一定会实现礼让
- void interrupt() 中断线程
- boolean isAlive() 测试线程是否处于活动状态
5.1 列:某科室一天需看普通号50个,特需号10个 ,特需号看病时间是普通号的2倍,开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高,当普通号叫完第10号时,要求先看完全部特需号,再看普通号,使用多线程模拟这一过程
public class SpecialThread extends Thread {
int num = 10;// 设置每天特殊号人数有10人看病
public SpecialThread(String name) {
super(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= num; i++) {
System.err.println(this.getName() + i + "号这个在给人看病");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class GeneralThread extends Thread {
int num = 50;
public GeneralThread(String name) {
super(name);
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 1; i <= num; i++) {
System.out.println(this.getName() + i + "号正在看病");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
}
if (num == 10) {
try {
this.join(2000); // 礼让资源给其他线程使用
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
GeneralThread gt = new GeneralThread("普通号");
SpecialThread st = new SpecialThread("特殊号");
//特需号概率比 普通号大
gt.setPriority(Thread.MIN_PRIORITY);// 1
st.setPriority(Thread.MAX_PRIORITY);// 10
gt.start();
st.start();
}