第一章:多线程的创建方式 第二章:线程常用方法
第三章:多线程练习案例:模拟火车站多窗口售票 第四章:线程的安全问题
第五章:线程的同步机制 第六章:线程同步练习案例:多用户存款
第七章:线程的通信 第八章:线程的生命周期
第九章:经典案例:生产者/消费者
一:多线程的创建方式
1:将类声明为 Thread
的子类,该子类应重写 Thread
类的 run
方法
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程并启动
Thread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----"
+ i);
}
}
}
2:声明实现 Runnable
接口的类。该类然后实现 run
方法
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程并启动
Thread thread = new Thread(new MyThread());
thread.start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----"
+ i);
}
}
}
二:线程常用方法
start():启动线程并执行相应的run()方法
run():线程要执行的代码放入run方法中
currentThread():静态方法 获取当前线程
getName():获取当前线程的名字
setName():设置线程的名称
yield():静态方法 执行此方法的线程释放当前cpu执行权
join():在A线程中调用B线程的join方法,表示:当执行的该方法,A线程停止执行,直至B线程执行完毕,A线程在接着执行join之后的代码
sleep(long time):静态方法 显示的让当前线程睡眠
setPriority():设置线程执行的优先级,默认是5,最大是10,最小是1
getPriority():获取线程执行的优先级
测试一:主线程和子线程分别打印1-100
public class ThreadTest {
/**
* 使用到的方法:
* start():启动线程并执行相应的run()方法
* currentThread():静态 获取当前线程
* getName():获取当前线程的名字
* setName():设置线程的名称
* setPriority():设置线程执行的优先级,默认是5,最大是10,最小是1
* getPriority():获取线程执行的优先级
*/
public static void main(String[] args) {
Thread.currentThread().setName("mainThread"); // 设置主线程的名称
Thread.currentThread().getName(); // 获取主线程的名称
Thread.currentThread().setPriority(10);// 设置主线程的执行优先级为10,优先级高低没有决定性,也就是优先级高的也不一定就是先执行或者先执行结束。
Thread thread = new MyThread();// 创建一个子线程
thread.setName("子线程");// 设置子线程名称
thread.getName();// 获取子线程名称
thread.setPriority(1);
thread.start();// 启动子线程
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
测试二:主线程和子线程分别打印1-100(注意观察打印效果)
public class ThreadTest {
/**
*yield():执行此方法的线程释放当前cpu执行权
*join():在A线程中调用B线程的join方法,表示:当执行的该方法,A线程停止执行,直至B线程执行完毕,A线程在接着执行join之后的代码
*sleep(long time):显示的让当前线程睡眠
*/
public static void main(String[] args) throws Exception {
Thread thread = new MyThread();// 创建一个子线程
thread.start();// 启动子线程
for (int i = 0; i < 100; i++) {
// 在主线程里面分别使用yield()、join()、sleep()方法,观察结果
if (i == 20){
thread.join();//中断主线程,执行子线程,执行结果会很明显
//Thread.currentThread().sleep(1000);//让主线程休眠1000毫秒
//Thread.currentThread().yield(); //主线程释放这一次cpu执行权,但是下一次还是可能被主线程抢到,所以打印的结果并不是很明显
}
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
三:多线程模拟火车站窗口售票
/**
* 模拟火车站卖票:三个窗口一起出售100张车票
*/
public class ThreadTest {
public static void main(String[] args) throws Exception {
Window w = new Window();
//创建三个线程
Thread window1 = new Thread(w);
Thread window2 = new Thread(w);
Thread window3 = new Thread(w);
//命名
window1.setName("窗口1");;
window2.setName("窗口2");
window3.setName("窗口3");
//开启线程售票
window1.start();
window2.start();
window3.start();
}
}
class Window implements Runnable {
//定义一百张车票
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "--售出第" + ticket + "张票");
ticket--;
} else {
break;
}
}
}
}
此程序存在线程安全问题:根据打印数据,发现重复票和错票
四:线程的安全问题
线程安全问题出现的原因:
当一个线程在操作共享资源(ticket)的过程中,突然失去了cpu的执行权,此时,cpu调度了另外一个线程也来操作这个共享资源,就会导致共享数据出现线程安全问题
结合售票代码和代码执行步骤图分析错票原因:
while (true) {
if (ticket > 0) {
try {
Thread.currentThread().sleep(100); //线程睡眠,目的是让出现错票的概率增大
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--售出第"
+ ticket + "张票");
ticket--;
} else {
break;
}
}
当ticket剩下一张的时候,线程t1进来取票,判断条件ticket>0,满足,t1正准备做其他事情的时候,cpu突然放弃了t1,转过头去调度t2线程,t2线程这时候也进来取票,虽然t1取过了,但是并没有执行ticket--,所以t2来取票的时候,票数还是1,所以t2也通过了条件判断ticket>0,同理t3也进来取到票了,最后打印的时候就会出现0和-1的票。
五:线程的同步机制
如何来解决线程的安全问题?
>必须让一个线程操作共享数据完毕之后,其他线程才有机会进行共享数据的操作
java如何解决线程安全问题:线程的同步机制
>方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码(即操作共享数据的代码)
}
共享数据:多个线程共同操作的同一个变量
同步监视器:俗称“锁”,由一个对象来充当,线程必须先拿到锁才能执行大括号中的代码,线程执行完大括号中的代码后 会释放锁,供其他线程使用。因为所有线程都共用一把“锁”,所以用来作为“锁”的对象必须是唯一的,否则实现不了同步
案例:同步代码块解决售票问题
class Window implements Runnable {
// 定义一百张车票
private static int ticket = 100;
private static Object obj = new Object();// 可以new一个任何对象作为锁,因为是唯一,定义为静态
@Override
public void run() {
while (true) {
/**
* this表示使用自身对象作为锁,如果线程是实现Runnable接口可以使用 但是如果选择继承Thread对象,this需要慎用
* 具体原因在下面给出分析
*/
// synchronized (this) {
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()
+ "--售出第" + ticket + "张票");
ticket--;
} else {
break;
}
}
}
}
}
/**
* 模拟火车站卖票:三个窗口一起出售100张车票
*/
public class ThreadTest {
public static void main(String[] args) throws Exception {
/**
* 实现Runnable接口,创建多线程的时候就new了一个对象 所以可以使用this作为锁
*/
Window w = new Window();
Thread window1 = new Thread(w);
Thread window2 = new Thread(w);
Thread window3 = new Thread(w);
/**
* 继承Thread对象,创建多线程的时候需要new多个对象 如果使用this,那就是多把锁
*/
/*
* Window window1 = new Window(); Window window2 = new Window(); Window
* window3 = new Window();
*/
// 命名
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
// 开启线程售票
window1.start();
window2.start();
window3.start();
}
}
>方式二:同步方法
将操作共享资源的方法申明为synchronized,即此方法为同步方法,能够保证线程操作同步方法的时候,其他线程必须等 待该线程执行完此方法
同步方法默认的锁:this(自身对象)
案例:同步方法解决售票问题
class Window implements Runnable {
// 定义一百张车票
private static int ticket = 100;
@Override
public void run() {
while (true) {
sail();
if (ticket <= 0)
break;
}
}
public synchronized void sail() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "--售出第"
+ ticket + "张票");
ticket--;
}
}
}
/**
* 模拟火车站卖票:三个窗口一起出售100张车票
*/
public class ThreadTest {
public static void main(String[] args) throws Exception {
/**
* 因为同步方法默认用的锁是this,如果继承Thread实现多线程会导致同步失效
*/
Window w = new Window();
Thread window1 = new Thread(w);
Thread window2 = new Thread(w);
Thread window3 = new Thread(w);
// 命名
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
// 开启线程售票
window1.start();
window2.start();
window3.start();
}
}
六:线程同步练习案例:多用户存款
银行有一个账户。有三个储户分别向同一个账户存3000块钱,每次存1000,存三次,存完打印余额
//账户
class Account {
//账户余额
private int balance;
//转账,因为balance是共享资源,如果这个方法不加同步会导致线程问题
public synchronized void deposit(int money){
balance += money;
try {
Thread.currentThread().sleep(100);//线程睡眠,为了使错误更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存款,账号余额为"+balance);
}
}
//储户
class Customer extends Thread{
//储户要转账的账户
private Account account;
//构造函数赋值
public Customer(Account account) {
super();
this.account = account;
}
@Override
public void run() {
//向账户存3000块钱,每次存一千
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class Transfer {
public static void main(String[] args) {
Account account = new Account();
//创建两个线程
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
//设置两个线程的名称
customer1.setName("用户1");
customer2.setName("用户2");
//开启线程
customer1.start();
customer2.start();
}
}
运行结果:
问题扩展:实现两个储户交替存钱的操作
七:线程的通信
wait():令一个线程挂起并放弃当前cpu执行权、同步锁,使别的线程可以访问并修改共享资源,而当前线程排队等候再次访问资源。
notify():唤醒正在等待排队等待同步资源的线程中优先级最高的线程
notifyAll():唤醒正在排队等待资源的所有线程结束等待,
注意:这三个方法是 java.lang.Object 提供的,只有在synchronized方法或者synchronized代码块中才可以使用,否则会报java.lang.IllegalMonitorStateException异常
利用线程通信机制解决上一章的拓展问题:实现两个储户交替存钱
//账户
class Account {
// 账户余额
private int balance;
// 转账,因为balance是共享资源,如果这个方法不加同步会导致线程问题
public synchronized void deposit(int money){
balance += money;
notify();//当前线程进来之后,需要唤醒上一个进入等待的线程,否则会导致多个线程都在等待
try {
Thread.currentThread().sleep(100);// 线程睡眠,为了不加synchronized时候,使错误更明显
System.out.println(Thread.currentThread().getName() + "存款,账号余额为"
+ balance);
wait();// 当前线程完成一次转账之后,进入等待,释放锁,让另一个线程进来执行转账
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 储户
class Customer extends Thread {
// 储户要转账的账户
private Account account;
// 构造函数赋值
public Customer(Account account) {
super();
this.account = account;
}
@Override
public void run() {
// 向账户存3000块钱,每次存一千
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class Transfer {
public static void main(String[] args) {
Account account = new Account();
// 创建两个线程
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
// 设置两个线程的名称
customer1.setName("用户1");
customer2.setName("用户2");
// 开启线程
customer1.start();
customer2.start();
}
}
运行结果:
八:线程的声明周期
新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
线程状态抓换图解:
注意sleep()和wait()方法的区别
sleep() 释放CPU的执行权,不释放锁
wait() 释放CPU的执行权,释放锁
九:多线程经典案例:生产者/消费者
生产者(producter)将产品交给店员(clerk),而消费者(customer)从店员这取走产品;店员一次只能持有固定数量的产品(比如20);如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店里有空位放置产品了再通知生产者生产;如果店里没有产品了,店员会告诉消费者等一下,如果店里有产品了再通知消费者来取走产品。
案例分析:
是否涉及多线程?: 涉及,生产者和消费者都是一个单独的线程
是否存在线程安全问题?:存在,因为生产者和消费者都需要操作共享资源“产品”
是否涉及线程通信?:涉及,产品多到一定数量后,生产者需要等待,暂时停止生产;产品空了,消费者需要等待,暂时停止消费;它们彼此也需要去唤醒对方进行生产或消费
//定义一个店员
class Clerk {
private int product;// 产品数量
public synchronized void addProduct() throws Exception {// 生产产品
if (product >= 20) {
System.out.println("产品已到上限,停止生产----------生产线程进入等待");
wait();
} else {
product++;
Thread.currentThread().sleep(1000); //放慢生产速度
System.out.println("产品+1,数量为" + product);
notify();
}
}
public synchronized void subProduct() throws Exception {// 消费产品
if (product <= 0) {
System.out.println("产品已达下限,停止出售----------出售线程进入等待");
wait();
} else {
product--;
System.out.println("产品-1,数量为" + product);
Thread.currentThread().sleep(1000); //放慢消费速度
notify();
}
}
}
// 定义生产者
class Producer extends Thread {
private Clerk clerk;
// 构造器注入变量
public Producer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
// 生产者调用生产方法
while (true) {
try {
clerk.addProduct();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 定义消费者
class Consumer extends Thread {
private Clerk clerk;
// 构造器注入变量
public Consumer(Clerk clerk) {
super();
this.clerk = clerk;
}
@Override
public void run() {
// 消费者开始消费
while (true) {
try {
clerk.subProduct();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//测试
public class ThreadTest {
public static void main(String[] args) {
//创建生产者和消费者
Clerk clerk = new Clerk();
Consumer consumer = new Consumer(clerk);
Producer producer = new Producer(clerk);
//开始生产者和消费者线程
consumer.start();
producer.start();
}
}