JAVA学习–多线程

多线程基本概念
程序
对应于操作系统中的一个可执行文件,启动后将程序加载到内存,开始执行该程序,于是产生了“进程”。
进程
就是执行中的程序,是一个动态的概念。本质是一个在内存中独立运行的程序空间。

进程是程序的一次动态执行过程,占用特定的地址空间。
有三部分组成:cpu,data,code。每个进程是独立的,保有自己的cpu时间,代码和数据,缺点是:浪费内存,cpu的负担较重。
多任务操作系统将CPU时间动态划分给每个进程,同时执行多个进程,每个进程独立运行。

线程
一个进程产生多个线程。同一进程的多个线程也可以共享此进程的某些资源(如:代码、数据),所以线程又被称为轻量级进程。

一个进程内部的一个执行单元,是程序中的一个单一的顺序控制流程。
一个进程可拥有多个并行的线程。
一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且他们从同一堆中分配对象并进行通信、数据交换和同步操作。
线程间通信是在同一地址空间上进行的,不需要额外的通信机制,所以线程间传递速度更快。

线程和进程的区别

线程在进程中运行
一个进程包含多个线程
不同进程间数据很难共享,而同一进程下的不同线程间数据很易共享。
进程要比线程消耗更多的计算机资源。
进程间不会相互影响,因为空间完全隔离,而进程中的一个线程挂掉会导致整个进程挂掉。
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束才能使用这块内存。
一个进程如果只有一个线程则可以看作单线程,有多个线程,进程的执行过程不是一条线的,而是多条线共同完成。

什么是主线程已经子线程
主线程
当java程序启动时,一个线程会立刻运行,该线程叫做程序的主线程,即main方法对应的线程,是程序开始时执行的。
main方法是一个应用的入口,代表了应用的主线程。JVM执行main方法时,main方法会进入到栈内存,JVM会通过操作系统开辟一条main方法通向cpu执行路径,cpu就会通过这路径执行main方法,这个路径叫main(主)线程。
主线程特点:
是产生其他子线程的线程。
不一定是最后完成执行的线程,子线程可能在他接受后还在运行。

子线程
在主线程中创建并启动的线程一般称为子线程。

线程创建

1、通过继承Thread类实现多线程
java.lang.Thread类是负责实现多线程的类。

继承Thread类实现多线程的步骤:

继承Thread类定义线程类。
重写Thread类中的run()方法。run()方法也称为线程体。
实例化线程类并通过start()方法启动线程

public class TestThread extends Thread {
            
 public TestThread(){
            
	 System.out.println(this.getName());
 }
 /**
 * 线程的线程体
*/
 @Override
 public void run() {
            
	 System.out.println(this.getName()+"线程开始");
	 for(int i=0;i<20;i++){
            
		 System.out.println(this.getName()+" "+i);
  }
 System.out.println(this.getName()+"线程结束");
 }
 public static void main(String[] args) {
            
	 System.out.println("主线程开始");
	 TestThread t1 = new TestThread();
	 //启动线程
	 t1.start();
	 TestThread t2 = new TestThread();
	 //启动线程
	 t2.start();
	 System.out.println("主线程结束");
 }
 }

2、通过实现Runnable接口实现多线程

public class TestThread2 implements Runnable {
            
 public TestThread2(){
            
	 System.out.println(Thread.currentThread().getName());
 }
 /**
 *当前线程的线程体方法
*/
 @Override
 public void run() {
            
	System.out.println(Thread.currentThread().getName()+"线程开始");
 	for(int i=0;i<20;i++){
            
	 	System.out.println(Thread.currentThread().getName()+" "+i);
 	}
	 System.out.println(Thread.currentThread().getName()+" 线程结束");
 }
 public static void main(String[] args) {
            
	 System.out.println("主线程开始");
	 TestThread2 testThread2 = new TestThread2();
	 Thread t1 = new Thread(testThread2);
	 t1.start();
	 Thread t2 = new Thread(new TestThread2());
	 t2.start();
	 System.out.println("主线程结束");
 }
 }

线程执行流程
图片[1] - JAVA学习–多线程 - 宋马
线程生命周期
图片[2] - JAVA学习–多线程 - 宋马
一个线程对象在它的生命周期内,需要经历5个状态。

新生状态(New)
用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。

就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。,当系统选定一个等待执行
的Thread 对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4中原因会导致线程进入就绪状态:

新建线程:调用start()方法,进入就绪状态;
阻塞线程:阻塞解除,进入就绪状态;
运行线程:调用yield()方法,直接进入就绪状态;
运行线程:JVM将CPU资源从本线程切换到其他线程。

运行状态(Running)
执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而堵塞或完成任务而死亡。如果在时间片内没有执行结束,会被系统换下来到就绪状态。

阻塞状态(Blocked)
指的是暂停一个线程的执行以等待某个条件发生。4种原因导致阻塞:

执行sleep方法,使当前线程休眠,进入阻塞状态。指定时间到了后进入就绪状态。
执行wait()方法,进入阻塞状态。当使用nofity()方法唤醒线程后进入就绪状态。
线程运行时某个操作进入阻塞状态,比如执行IO流操作(read()/write()本身就是阻塞的方法)。只有引起阻塞的原因消失后线程才进入就绪状态。
join()线程联合:某个线程等待另一个线程执行结束后才能继续执行时,使用join()方法。

死亡状态(Terminated)
线程生命周期的最后一个阶段。死亡原因有两个:
1、正常运行的线程完成了run()方法内的全部工作;
2、线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(这两个方法已被JDK废弃,不推荐使用)
当一个线程进入死亡状态后就不能回到其他状态了。

线程使用
终止线程
由于stop()或destroy()方法已被JDK废弃,通常终止线程的方法是提供一个boolean型的终止变量,当这个变量为false时,终止线程的执行。

public class StopThread implements Runnable {
            
	private boolean flag = true;
	@Override
	public void run() {
            
		System.out.println(Thread.currentThread().getName()+" 线程开始");
		int i= 0;
		while(flag){
            
				System.out.println(Thread.currentThread().getName()+" "+i++);
			try {
            
				Thread.sleep(1000);
			} catch (InterruptedException e) {
            
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" 线程结束");
	}
	public void stop(){
            
		this.flag = false;
	}
	public static void main(String[] args)throws Exception {
            
		System.out.println("主线程开始");
		StopThread st = new StopThread();
		Thread t1 = new Thread(st);
		t1.start();
		System.in.read();
		st.stop();
		System.out.println("主线程结束");
	}
}

暂停当前线程执行sleep/yield
两个方法区别:

sleep()方法:让正在运行的线程进入阻塞状态,知道休眠时间满,进入就绪状态。
yield()方法:让正在运行的线程进入就绪状态,让出CPU使用权。

sleep方法使用

public class SleepThread implements Runnable {
            
	@Override
	public void run() {
            
		System.out.println(Thread.currentThread().getName()+" 线程开始");
		for(int i=0;i<20;i++){
            
			System.out.println(Thread.currentThread().getName()+" "+i);
			try {
            
				Thread.sleep(1000);
			} catch (InterruptedException e) {
            
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" 线程结束");
}
public static void main(String[] args) {
            
	System.out.println("主线程开始");
	Thread t = new Thread(new SleepThread());
	t.start();
	System.out.println("主线程结束");
	}
}	

yield方法使用
作用:暂停当前执行线程,并执行其他线程。
目的是让具有相同优先级的线程之间能够适当的轮换执行。实际中无法保证 yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
注意:

yield 是一个静态的方法。
调用 yield 后,yield 告诉当前线程把运行机会交给具有相同优先级的线程。
yield 不能保证,当前线程迅速从运行状态切换到就绪状态。
yield 只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。

public class YieldThread implements Runnable {
            
	@Override
	public void run() {
            
		for(int i=0;i<30;i++){
            
			if("Thread-0".equals(Thread.currentThread().getName())){
            
				if(i == 0){
            
					Thread.yield();
				}
			}
			System.out.println(Thread.currentThread().getName()+""+i);
		}
	}
	public static void main(String[] args) {
            
		Thread t1 = new Thread(new YieldThread());
		Thread t2 = new Thread(new YieldThread());
		t1.start();
		t2.start();
	}
}

线程的联合
线程A在中可以调用线程B的join()方法,让线程B和线程A联合,线程A必须等待线程B执行完毕后,才能继续执行。
join方法的使用
指调用该方法的线程在执行完run()方法后,在执行join 方法后面的代码,即将两个线程合并,用于实现同步控制。

class A implements Runnable{
            
    @Override
    public void run() {
            
        for(int i=0;i<10;i++){
            
            System.out.println(Thread.currentThread().getName()+""+i);
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}
class B implements Runnable{
            
    @Override
    public void run() {
            
        for(int i=0;i<20;i++){
            
            System.out.println(Thread.currentThread().getName()+""+i);
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}
public class JoinThread {
            
    public static void main(String[] args) {
            
        Thread t = new Thread(new A());
        Thread t1 = new Thread(new B());
        t.start();
        t1.start();
        for(int i=0;i<10;i++){
            
            System.out.println(Thread.currentThread().getName()+""+i);
            if(i ==2 ){
            
                try {
            
                    t.join();//先执行完线程A和线程B,在执行线程main
                } catch (InterruptedException e) {
            
                    e.printStackTrace();
                }
            }
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}

线程联合案例

/**
 * 儿子买烟线程
 */
class SonThread implements Runnable{
            
    @Override
    public void run() {
            
        System.out.println("儿子出门买烟");
        System.out.println("儿子买烟需要 10 分钟");
        for(int i=0;i<10;i++){
            
            System.out.println("第"+i+"分钟");
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
        System.out.println("儿子买烟回来了");
    }
}
/**
 * 爸爸抽烟线程
 */
class FatherThread implements Runnable{
            
    @Override
    public void run() {
            
        System.out.println("爸爸想抽烟,发现烟抽完了");
        System.out.println("爸爸让儿子去买一包利群");
        Thread t = new Thread(new SonThread());
        t.start();
        System.out.println("等待儿子买烟回来");
        try {
            
            t.join();
        } catch (InterruptedException e) {
            
            e.printStackTrace();
            System.out.println("爸爸出门找儿子");
            System.exit(1);
        }
        System.out.println("爸爸高兴的接过烟,并把零钱给了儿子");
    }
}
public class JoinDemo {
            
    public static void main(String[] args) {
            
        System.out.println("爸爸和儿子买烟的故事");
        Thread t = new Thread(new FatherThread());
        t.start();
    }
}

获取当前线程名称

方式一
this.getName()获取线程名称,该方法适用于继承 Thread 实现多线程方式

class GetName1 extends Thread{
            
    @Override
    public void run() {
            
        System.out.println(this.getName());
    }
}

方式二
Thread.currentThread().getName()获取线程名称,该方法适用于实现Runnable 接口实现多线程方式。

class GetName2 implements Runnable{
            
    @Override
    public void run() {
            
        System.out.println(Thread.currentThread().getName());
    }
}

设置线程名称

方式一
通过构造方法设置线程名称。

class SetName1 extends Thread{
            
    public SetName1(String name){
            
        super(name);
    }
    @Override
    public void run() {
            
        System.out.println(this.getName());
    }
}
public class SetNameThread {
            
    public static void main(String[] args) {
            
        SetName1 setName1 = new SetName1("SetName1");
        setName1.start();
    }
}

方式二
通过 setName()方法设置线程名称。

class SetName2 implements Runnable{
            
    @Override
    public void run() {
            
        System.out.println(Thread.currentThread().getName());
    }
}
public class SetNameThread {
            
    public static void main(String[] args) {
            
        Thread thread = new Thread(new SetName2());
        thread.setName("SetName2");
        thread.start();
    }
}

判断当前线程是否存活
isAlive()方法: 判断当前的线程是否处于活动状态。返回布尔类型的值。
活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。

class Alive implements Runnable{
            
    @Override
    public void run() {
            
        System.out.println(Thread.currentThread().isAlive()+" 2");
        try {
            
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            
            e.printStackTrace();
        }
    }
}
public class AliveThread {
            
    public static void main(String[] args) {
            
        Thread thread = new Thread(new Alive());
        System.out.println(thread.isAlive()+" 1");
        thread.start();
        System.out.println(thread.isAlive()+" 3");
        try {
            
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            
            e.printStackTrace();
        }
        System.out.println(thread.isAlive()+" 4");
    }
}

线程优先级
Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关。
注意:线程的优先级,不是说哪个线程优先执行,如果设置某个线程的优先级高。那就是有可能被执行的概率高。并不是优先执行。

线程优先级的使用
使用下列方法获得或设置线程对象的优先级:

int getPriority();
void setPriority(int newPriority);

class Priority implements Runnable{
            
    private int num = 0;
    private boolean flag = true;
    @Override
    public void run() {
            
        while(this.flag){
            
        System.out.println(Thread.currentThread().getName()+""+num++);
        }
    }
    public void stop(){
            
        this.flag = false;
    }
}
public class PriorityThread {
            
    public static void main(String[] args)throws Exception {
            
        Priority p1 = new Priority();
        Priority p2 = new Priority();
        Thread t1 = new Thread(p1,"线程 1");
        Thread t2 = new Thread(p2,"线程 2");
        System.out.println(t1.getPriority());
//Thread.MAX_PRIORITY = 10
        t1.setPriority(Thread.MAX_PRIORITY);
//Thread.MIN_PRIORITY = 1
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        p1.stop();
        p2.stop();
    }
}

守护线程
在 Java 中有两类线程:

User Thread(用户线程):就是应用程序里的自定义线程。
Daemon Thread(守护线程):比如垃圾回收线程,就是最典型的守护线程。
守护线程是一个服务线程,服务其他的线程,其他线程只有一种,就是用户线程。
特点:
随着用户线程死亡而死亡。

守护线程与用户线程区别
用户线程只有两种情况会死掉:

run中异常终止
run执行完毕,线程死亡

守护线程,随着用户线程的死亡而死亡。

守护线程使用

/**
 * 守护线程类
 */
class Daemon implements Runnable{
            
    @Override
    public void run() {
            
        for(int i=0;i<20;i++){
            
            System.out.println(Thread.currentThread().getName()+""+i);
            try {
            
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}
class UsersThread implements Runnable{
            
    @Override
    public void run() {
            
        Thread t = new Thread(new Daemon(),"Daemon");
		//将该线程设置为守护线程,两个线程一起执行,用户线程执行结束两个一起结束。
        t.setDaemon(true);
        t.start();
        for(int i=0;i<5;i++){
            
            System.out.println(Thread.currentThread().getName()+""+i);
            try {
            
                Thread.sleep(500);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}
public class DaemonThread {
            
    public static void main(String[] args)throws Exception {
            
        Thread t = new Thread(new UsersThread(),"UsersThread");
        t.start();
        Thread.sleep(1000);
        System.out.println("主线程结束");
    }
}

线程同步
线程冲突现象
图片[3] - JAVA学习–多线程 - 宋马
线程同步概念
多个线程访问同一对象,某些线程还想修改这个对象。此时需要线程同步。本质是一个等待机制,多个访问同一对象的线程进入等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

线程冲突案例演示
银行取钱的基本流程基本上可以分为如下几个步骤。
(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。
(2)用户输入取款金额
(3)系统判断账户余额是否大于取款金额
(4)如果余额大于取款金额,则取钱成功;如果余额小于取款金额,则取钱失败。

/**
 * 账户类
 */
class Account{
            
    //账号
    private String accountNo;
    //账户的余额
    private double balance;
    public Account() {
            
    }
    public Account(String accountNo, double balance) {
            
        this.accountNo = accountNo;
        this.balance = balance;
    }
    public String getAccountNo() {
            
        return accountNo;
    }
    public void setAccountNo(String accountNo) {
            
        this.accountNo = accountNo;
    }public double getBalance() {
            
        return balance;
    }
    public void setBalance(double balance) {
            
        this.balance = balance;
    }
}
/**
 * 取款线程
 */
class DrawThread extends Thread{
            
    //账户对象
    private Account account;
    //取款金额
    private double drawMoney;
    public DrawThread(String name,Account account,double drawMoney) {
            
   		super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }
    /**
     * 取款线程
     */
    @Override
    public void run() {
            
//判断当前账户余额是否大于或等于取款金额
        if(this.account.getBalance() >= this.drawMoney){
            
            System.out.println(this.getName()+" 取钱成功!吐出钞票:"+this.drawMoney);
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }//更新账户余额
            this.account.setBalance(this.account.getBalance()-
                    this.drawMoney);
            System.out.println("	 余额为: "+this.account.getBalance());
        }else{
            
            System.out.println(this.getName()+" 取钱失败,余额不足");}
    }
}
public class DrawMoneyThread {
            
    public static void main(String[] args) {
            
        Account account = new Account("1234",1000);
        new DrawThread("老公",account,800).start();
        new DrawThread("老婆",account,800).start();
    }
}

实现线程同步
使用synchronized 关键字避免同一个数据对象被多个线程同时访问造成的这种问题。
synchronized 语法结构:

synchronized(锁对象){
            
	同步代码
}

需要考虑的问题:

需要对那部分的代码在执行时具有线程互斥的能力(线程互斥:并行变串行)。
需要对哪些线程中的代码具有互斥能力(通过 synchronized 锁对象来决定)。

两种用法:

synchronized 方法
通过在方法声明中加入 synchronized 关键字来声明,语法如下:

public synchronized void accessVal(int newVal);

synchronized 在方法声明时使用:放在范围操作符(public)之后,返回类型声明(void)之前。这时同一个对象下 synchronized 方法在多线程中执行时,该方法是同步的,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,就算对象下有多个synchronized方法,也要等一个线程执行完指定的synchronized方法后别的线程才能调用当前对象下的synchronized方法。

synchronized 块
synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。

修改线程冲突案例演示

/**
 * 账户类
 */
class Account{
            
    //账号
    private String accountNo;
    //账户的余额
    private double balance;
    public Account() {
            
    }
    public Account(String accountNo, double balance) {
            
        this.accountNo = accountNo;
        this.balance = balance;
    }
    public String getAccountNo() {
            
        return accountNo;
    }
    public void setAccountNo(String accountNo) {
            
        this.accountNo = accountNo;
    }
    public double getBalance() {
            
        return balance;
    }
    public void setBalance(double balance) {
            
        this.balance = balance;
    }
}
/**
 * 取款线程
 */
class DrawThread extends Thread{
            
//账户对象
    private Account account;
    //取款金额
    private double drawMoney;
    public DrawThread(String name,Account account,double drawMoney){
            
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }
    /**
     * 取款线程
     */
    @Override
    public void run() {
            
        synchronized (this.account) {
            
            //判断当前账户余额是否大于或等于取款金额
            if (this.account.getBalance() >= this.drawMoney) {
            
                System.out.println(this.getName() + " 取钱成功!吐出钞票:" + this.drawMoney);
                try {
            
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
            
                    e.printStackTrace();
                }
                //更新账户余额
                this.account.setBalance(this.account.getBalance()-this.drawMoney);
                System.out.println("	 余额为:" +
                        this.account.getBalance());
            } else {
            
                System.out.println(this.getName() + " 取钱失败,余额不足");
            }
        }
    }
}
public class DrawMoneyThread {
            
    public static void main(String[] args) {
            
        Account account = new Account("1234",1000);
        new DrawThread("老公",account,800).start();
        new DrawThread("老婆",account,800).start();
    }
}

线程同步使用

使用this 作为线程对象锁
语法结构:

synchronized(this){
            
	//同步代码
}

public synchronized void accessVal(int newVal){
            
	//同步代码
}
/**
 * 定义程序员类
 */
class Programmer{
            
    private String name;
    public Programmer(String name){
            
        this.name = name;
    }
/**
 * 打开电脑
 */
 public synchronized void computer(){
            
    try {
            
        System.out.println(this.name + " 接通电源");
        Thread.sleep(500);
        System.out.println(this.name + " 按开机按键");
        Thread.sleep(500);
        System.out.println(this.name + " 系统启动中");
        Thread.sleep(500);
        System.out.println(this.name + " 系统启动成功");
    } catch (InterruptedException e) {
            
        e.printStackTrace();
    }
}
    /**
     * 编码
     */
     public synchronized void coding(){
            
        try {
            
            System.out.println(this.name + " 双击Idea");
            Thread.sleep(500);
            System.out.println(this.name + " Idea 启动完毕");
            Thread.sleep(500);
            System.out.println(this.name + " 开开心心的写代码");} catch (InterruptedException e) {
            
            e.printStackTrace();
        }
    }
}
/**
 * 打开电脑的工作线程
 */
class Working1 extends Thread{
            
    private Programmer p;
    public Working1(Programmer p){
            
        this.p = p;
    }
    @Override
    public void run() {
            
        this.p.computer();
    }
}
/**
 * 编写代码的工作线程
 */
class Working2 extends Thread{
            
    private Programmer p;
    public Working2(Programmer p){
            
        this.p = p;
    }
    @Override
    public void run() {
            
        this.p.coding();
    }
}
public class test {
             
    public static void main(String[] args) {
            
        Programmer p = new Programmer("张三");
        new Working1(p).start();
        new Working2(p).start();
    }
}

使用字符串作为线程对象锁
语法结构:

synchronized(“字符串”){
            
	//同步代码
}
/**
 * 定义程序员类
 */
class Programmer{
            
    private String name;
    public Programmer(String name){
            
        this.name = name;
    }
    /**
     * 打开电脑
     */
    synchronized public void computer(){
            
        try {
            
            System.out.println(this.name + " 接通电源");
            Thread.sleep(500);
            System.out.println(this.name + " 按开机按键");
            Thread.sleep(500);
            System.out.println(this.name + " 系统启动中");
            Thread.sleep(500);
            System.out.println(this.name + " 系统启动成功");
        } catch (InterruptedException e) {
            
            e.printStackTrace();
        }
    }
    /**
     * 编码
     */
    synchronized public void coding(){
            
        try {
            
            System.out.println(this.name + " 双击Idea");
            Thread.sleep(500);
            System.out.println(this.name + " Idea 启动完毕");
            Thread.sleep(500);
            System.out.println(this.name + " 开开心心的写代码");} catch (InterruptedException e) {
            
            e.printStackTrace();
        }
    }
    /**
     * 去卫生间
     */
    public void wc(){
            
        synchronized ("suibian") {
            
            try {
            
                System.out.println(this.name + " 打开卫生间门");
                Thread.sleep(500);
                System.out.println(this.name + " 开始排泄");
                Thread.sleep(500);
                System.out.println(this.name + " 冲水");
                Thread.sleep(500);
                System.out.println(this.name + " 离开卫生间");
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
    }
}
/**
 * 打开电脑的工作线程
 */
class Working1 extends Thread{
            
    private Programmer p;
    public Working1(Programmer p){
            
        this.p = p;
    }
    @Override
    public void run() {
            
        this.p.computer();
    }
}
/**
 * 编写代码的工作线程
 */
class Working2 extends Thread{
            
    private Programmer p;
    public Working2(Programmer p){
            
        this.p = p;
    }
    @Override
    public void run() {
            
        this.p.coding();
    }
}
/**
 * 去卫生间的线程
 */
class WC extends Thread{
            
    private Programmer p;
    public WC(Programmer p){
            
        this.p = p;
    }
    @Override
    public void run() {
            
        this.p.wc();
    }
}
public class TestSyncThread {
            
    public static void main(String[] args) {
            
/*Programmer p = new Programmer("张三");
new Working1(p).start();
new Working2(p).start();*/
        Programmer p = new Programmer("张三");
        Programmer p1 = new Programmer("李四");
        Programmer p2 = new Programmer("王五");
        new WC(p).start();
        new WC(p1).start();
        new WC(p2).start();
    }
}

死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

案例演示:

/**
 * 口红类
 */
class Lipstick{
            
}
/**
 * 镜子类
 */
class Mirror{
            
}
/**
 * 化妆线程类
 */
class Makeup extends Thread {
            
    private int flag; //flag=0:拿着口红。flag!=0:拿着镜子private String girlName;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    public void setFlag(int flag) {
            
        this.flag = flag;
    }

    public void setGirlName(String girlName) {
            
        this.girlName = girlName;
    }

    @Override
    public void run() {
            
        this.doMakeup();
    }

    /**
     * 开始化妆
     */
    public void doMakeup() {
            
        if (flag == 0) {
            
            synchronized (lipstick) {
            
                System.out.println(this.girlName + " 拿着口红");
            }
            try {
            
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
            synchronized (mirror) {
            
                System.out.println(this.girlName + " 拿着镜子");
            }
        } else {
            
            synchronized (mirror) {
            
                System.out.println(this.girlName + " 拿着镜子");
                try {
            
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
            
                    e.printStackTrace();
                }
                synchronized (lipstick) {
            
                    System.out.println(this.girlName + " 拿着口红");
                }
            }
        }
    }
}
public class DeadLockThread {
            
    public static void main(String[] args) {
            
        Makeup makeup = new Makeup();
        makeup.setFlag(0);
        makeup.setGirlName("大丫");
        Makeup makeup1 = new Makeup();
        makeup1.setFlag(1);
        makeup1.setGirlName("小丫");
        makeup.start();
        makeup1.start();
    }
}

死锁问题的解决
同一个代码块,不要同时持有两个对象锁。

/**
 * 口红类
 */
class Lipstick{
            
}
/**
 * 镜子类
 */
class Mirror{
            
}
/**
 * 化妆线程类
 */
class Makeup extends Thread{
            
    private int flag; //flag=0:拿着口红。flag!=0:拿着镜子private String girlName;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    public void setFlag(int flag) {
            
        this.flag = flag;
    }
    public void setGirlName(String girlName) {
            
        this.girlName = girlName;
    }
    @Override
    public void run() {
            
        this.doMakeup();
    }
    /**
     * 开始化妆
     */
    public void doMakeup(){
            
        if(flag == 0){
            
            synchronized (lipstick){
            
                System.out.println(this.girlName+" 拿着口红");
                try {
            
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
            
                    e.printStackTrace();
                }
            }
            synchronized (mirror){
            
                System.out.println(this.girlName+" 拿着镜子");
            }
        }else{
            
            synchronized (mirror){
            
                System.out.println(this.girlName+" 拿着镜子");
                try {
            
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
            
                    e.printStackTrace();
                }
            }
            synchronized (lipstick){
            
                System.out.println(this.girlName+" 拿着口红");
            }
        }
    }
}
public class DeadLockThread {
            
    public static void main(String[] args) {
            
        Makeup makeup = new Makeup();
        makeup.setFlag(0);
        makeup.setGirlName("大丫");
        Makeup makeup1 = new Makeup();
        makeup1.setFlag(1);
        makeup1.setGirlName("小丫");
        makeup.start();
        makeup1.start();
    }
}

线程并发协作(生产者/消费者模式)

角色介绍

生产者
生产者指的是负责生产数据的模块。

消费者
消费者指的是负责处理数据的模块。

缓冲区
消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
缓冲区是实现并发的核心,缓冲区的设置有两个好处:

实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离,解除了生产者与消费者之间的耦合。

解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。

实现生产者与消费者模式
创建缓冲区

/**
 * 定义馒头类
 */
class ManTou{
            
    private int id;
    public ManTou(int id){
            
        this.id = id;
    }
    public int getId(){
            
        return this.id;
    }
}
/**
 * 定义缓冲区类
 */
class SyncStack{
            
    //定义存放馒头的盒子
    private ManTou[] mt = new ManTou[10];
    //定义操作盒子的索引
    private int index;
    /**
     * 放馒头
     */
    public synchronized void push(ManTou manTou){
            
//判断盒子是否已满
        while(this.index == this.mt.length){
            
            try {
            
/**
 * 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
                this.wait();
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
//唤醒取馒头的线程
/**
 * 语法:该方法必须要在 synchronized 块中调用。
 * 该方法会唤醒处于等待状态队列中的一个线程。
 */
        this.notify();
        this.mt[this.index] = manTou;
        this.index++;
    }
    /**
     * 取馒头
     */
    public synchronized ManTou pop(){
            
        while(this.index == 0){
            
            try {
            
/**
 * 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
                this.wait();
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
        this.notify();
        this.index--;
        return this.mt[this.index];
    }
}

创建生产者消费者线程

/**
 * 定义馒头类
 */
class ManTou{
            
    private int id;
    public ManTou(int id){
            
        this.id = id;
    }
    public int getId(){
            
        return this.id;
    }
}
/**
 * 定义缓冲区类
 */
class SyncStack{
            
    //定义存放馒头的盒子
    private ManTou[] mt = new ManTou[10];
    //定义操作盒子的索引
    private int index;
    /**
     * 放馒头
     */
    public synchronized void push(ManTou manTou){
            
//判断盒子是否已满
        while(this.index == this.mt.length){
            
            try {
            
/**
 * 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
                this.wait();//wait 是等待另一个线程 notify 后, 才继续执行, 不用等另一个线程全部执行完
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
//唤醒取馒头的线程
/**
 * 语法:该方法必须要在 synchronized 块中调用。
 * 该方法会唤醒处于等待状态队列中的一个线程。
 */
        System.out.println("检测是否会继续执行一次");
        this.notify();//因为生产者进程有锁,所以在生产者线程未执行wait前消费者线程无法调用另一个加锁的方法,notify生效后消费者线程开始执行,在此处执行是为了确保index的值正确
        this.mt[this.index] = manTou;
        this.index++;
    }
    /**
     * 取馒头
     */
    public synchronized ManTou pop(){
            
        while(this.index == 0){
            
            try {
            
/**
 * 语法:wait(),该方法必须要在 synchronized 块中调用。* wait 执行后,线程会将持有的对象锁释放,并进入阻塞状态,* 其他需要该对象锁的线程就可以继续运行了。*/
                this.wait();
            } catch (InterruptedException e) {
            
                e.printStackTrace();
            }
        }
        this.notify();
        this.index--;
        return this.mt[this.index];
    }
}
/**
 * 定义生产者线程类
 */
class ShengChan extends Thread{
            
    private SyncStack ss;
    public ShengChan(SyncStack ss){
            
        this.ss = ss;
    }
    @Override
    public void run() {
            
        for(int i=0;i<10;i++){
            
            System.out.println("生产馒头:"+i);
            ManTou manTou = new ManTou(i);
            this.ss.push(manTou);
        }
    }
}
/**
 * 定义消费者线程类
 */
class XiaoFei extends Thread{
            
    private SyncStack ss;
    public XiaoFei(SyncStack ss){
            
        this.ss = ss;
    }
    @Override
    public void run() {
            
        for(int i=0;i<10;i++){
            
            ManTou manTou = this.ss.pop();
            System.out.println("消费馒头:"+i);
        }
    }
}
public class test {
            
    public static void main(String[] args) {
            
        SyncStack ss = new SyncStack();
        new ShengChan(ss).start();
        new XiaoFei(ss).start();
    }
}

线程并发协作总结
线程并发协作(也叫线程通信)。
生产者消费者模式:

生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
在生产者消费者问题中,仅有 synchronized 是不够的。synchronized 可阻止并发更新同一个共享资源,实现了同步但是synchronized不能用来实现不同线程之间的消息传递(通信)。
那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
图片[4] - JAVA学习–多线程 - 宋马
6.以上方法均是 java.lang.Object 类的方法;
都只能在同步方法或者同步代码块中使用,否则会抛出异常。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容