多线程

146 阅读4分钟

一、导入:什么是进程?

图片.png

Tip: 进程是由程序、数据及系统资源组成的一次程序运行活动的实体

图片.png

Tip: 一个程序可以包含多个进程,一个进程可以包含多个线程

二、导入:为什么要使用多线程

图片.png

图片.png

多任务的处理方式

图片.png

Tips: 多线程是实现多个线程并发执行的技术。

三、线程的创建方式

方式一(继承Thread)

1.创建一个线程类
    建个类
    继承Thread
    重写run方法
2.创建子线程
    创建线程类对象
    调用start()方法启动子线程
package test;
​
public class MyThread1 extends Thread{
​
    /**
     * run方法是子线程执行任务的单元
     */
    @Override
    public void run() {
        while(true) {
            //查询线程的名称
            String name = getName();
            System.out.println(name);
        }
    }
    
}
​
package test;
​
public class T1 {
​
    public static void main(String[] args) {
        /**
         * 创建子线程一
         */
        //创建线程类对象-- 相当于创建了一个子线程
        MyThread1 myThread1 = new MyThread1();
        //给线程取名称
        myThread1.setName("---子线程1---");
        //启动子线程
        myThread1.start();
        
        /**
         * 创建子线程二
         */
        //创建线程类对象-- 相当于创建了一个子线程
        MyThread1 myThread2 = new MyThread1();
        myThread2.setName("@@@子线程2@@@");
        myThread2.start();
        
        
        while(true) {
            System.out.println("***主线程***");
        }
    }
}
​

图片.png

方式二(实现Runnable接口)

1.创建一个线程类
    建个类
    实现Runnable
    重写run方法
2.创建子线程
    创建自定义线程类对象
    创建Thread类对象
    调用Thread类对象的start()方法启动子线程
package test;
​
public class MyThread2 implements Runnable {
​
    @Override
    public void run() {
        //获取线程名称
        String name = Thread.currentThread().getName();
        System.out.println(name);
    }
​
}
package test;
​
public class T2 {
    public static void main(String[] args) {
        //创建自定义线程类对象
        MyThread2 myThread2 = new MyThread2();
        
        /**
         * 创建子线程一
         */
        //创建Thread类对象
        Thread t1 = new Thread(myThread2,"###子线程一###");
        //调用Thread类对象的start方法启动线程
        t1.start();
        
        /**
         * 创建子线程二
         */
        Thread t2 = new Thread(myThread2,"***子线程二***");
        t2.start();
        
    }
}
​

方式三(匿名内部类)

package test;
​
public class T3 {
​
    public static void main(String[] args) {
        //创建Thread匿名对象
        new Thread(new Runnable() {
​
            @Override
            public void run() {
                while(true) {
                    System.out.println("我是一个子线程");
                }
            }
            
        }).start();
    }
}
​

四、线程的抢占和生命周期

图片.png

    线程是在cpu上执行,因为cpu单位时间里面能够执行的线程数量是有线的,所以电脑里面所有的线程不是同时执行的,而是交替执行的
    线程抢占机制: 多个线程去抢占cpu资源,谁抢占到谁执行
线程生命周期的五大状态
新生(创建线程对象)
就绪(调用线程对象的start方法,线程不会立马执行,线程需要去抢占cpu资源)
运行(抢占到cpu资源)
阻塞(线程运行的过程中释放cpu资源)
死亡(线程的任务执行完毕后,进入死亡状态)

图片.png

五、线程的优先级

    线程的优先级分十个等级,用1-10这样的10个数字表示,数字越小优先级越低,数字越大优先级越高
    优先级代表线程抢占到cpu资源的概率,不代表线程就会先执行   
    所有的线程,默认的优先级都是5
    
    setPriority(1);设置线程优先级
    getPriority();获取线程优先级
public class MyThread implements Runnable {
​
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(name);
    }
​
}
package test;
​
public class T4 {
​
    public static void main(String[] args) {
        
        //创建自定义线程类对象
        MyThread myThread = new MyThread();
        
        //创建线程对象
        Thread t1 = new Thread(myThread,"线程一");
        Thread t2 = new Thread(myThread,"线程二");
        Thread t3 = new Thread(myThread,"线程三");
        
        //修改线程优先级
        t1.setPriority(1);
        t2.setPriority(10);
        
        
        //获取线程的优先级
        int t1Priority = t1.getPriority();
        int t2Priority = t2.getPriority();
        int t3Priority = t3.getPriority();
        System.out.println("数字越小优先级越低");
        System.out.println("线程一的优先级:"+t1Priority);
        System.out.println("线程二的优先级:"+t2Priority);
        System.out.println("线程三的优先级:"+t3Priority);
        
        t1.start();
        t2.start();
        t3.start();
        
    }
​
}

六.线程的常用方法

    getName()--获取线程的名称
    run()--线程运行代码的方法
    start()--线程的启动方法
    getId()--获取线程的唯一标识
    sleep()--线程的休眠
    yield()--线程让步,释放cup资源,重新抢占cpu资源,谁抢到谁执行
    join()--线程抢占,抢占cpu资源,指的是在哪个线程里面调用该方法,其他线程就需要让出cpu资源,等待该线程执行完毕后再重新抢占cpu资源

线程休眠

package test;
​
public class MyThread3 implements Runnable {
​
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        while(true) {
            try {
                //现场睡眠1秒钟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name);
        }
        
    }
​
}
package test;
​
public class T5 {
​
    public static void main(String[] args) {
        //创建自定义线程类对象
        MyThread3 myThread = new MyThread3();
        //创建线程对象
        Thread t = new Thread(myThread,"线程一");
        t.start();
    }
​
}

线程让步

强行让线程释放掉占用的CPU资源,再重新抢占cpu资源
public class MyThread implements Runnable {
​
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 5; i++) {
            //线程的让步
            Thread.yield();
            System.out.println(name);
        }
​
    }
​
}
public class Test {
	
	
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"线程一");
		
		Thread t2 = new Thread(myThread,"线程二");
		
		t1.start();
		t2.start();
	}
}

线程抢占

线程A抢占线程B,线程A抢占的是线程B的cpu资源,线程B必须要等到线程A的代码全部执行完了才能继续执行
public class MyThread implements Runnable {

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		for (int i = 0; i < 10; i++) {
			System.out.println(name);
		}
	}
}
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		Thread t1 = new Thread(myThread,"线程一");
		t1.start();

		//线程的抢占(子线程t1抢占主线程的CPU资源,主线程需要等待子线程全部执行完后,再继续执行)
		try {
			t1.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		for (int i = 0; i < 10; i++) {
			System.out.println("主线程@@@@@@@@@@@@@@@@@@@");
		}
	}

七、线程安全[重点]

多个线程同时操作一个变量的时候,会有线程安全问题(例如票被重复卖出,票被超卖)

案例(有线程安全问题的代码)

图片.png

解决线程安全问题

原理:让某个线程把一段代码全部执行完,有一个线程在执行,其它线程都不允许执行

方式1:同步代码块

把可能出现线程安全的代码放到同步代码块中

package sale;

public class SaleTicket implements Runnable {

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		while(true) {
			/**
			 * 同步代码块
			 * 	只有抢到同步锁的线程才能执行同步代码块中的代码,
			 * 	同步代码块中的代码执行完后会释放同步锁,
			 * 	多个线程再同时抢同步锁,谁抢到谁执行
			 */
			synchronized (Test.lock) {
				//当票卖完后,就不再卖票,结束循环
				if(Test.ticketNum<=0) {
					break;
				}
				
				
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				//卖票
				Test.ticketNum--;
				System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
			}
			
		}
	}
}
package sale;

public class Test {
	//定义变量存储票的总数量
	public static int ticketNum = 100;
		
	//创建同步锁对象
	public static Object lock = new Object();
		
		
	public static void main(String[] args) {
		//创建线程类对象
		SaleTicket saleTicket = new SaleTicket();

		//创建子线程
		Thread t1 = new Thread(saleTicket, "窗口一");
		Thread t2 = new Thread(saleTicket, "窗口二");
		Thread t3 = new Thread(saleTicket, "窗口三");
			
		//启动子线程
		t1.start();
		t2.start();
		t3.start();
	}
}

方式2:同步方法

package sale2;

public class SaleTicket implements Runnable {
	boolean f = true;
	
	@Override
	public void run() {
		
		while(f) {
			sale();
		}
	}
	
	/**
	 * 同步方法:使用当前类对象(this)作为同步锁,保证线程安全	
	 */
	public synchronized void sale() {
		//当票卖完后,就不再卖票,结束循环
		if(Test.ticketNum<=0) {
			f = false;
			return;
		}
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		String name = Thread.currentThread().getName();
		//卖票
		Test.ticketNum--;
		System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
	}

}
package sale2;

public class Test {
	//定义变量存储票的总数量
	public static int ticketNum = 100;
	
	public static void main(String[] args) {
		//创建线程类对象
		SaleTicket saleTicket = new SaleTicket();

		//创建子线程
		Thread t1 = new Thread(saleTicket, "窗口一");
		Thread t2 = new Thread(saleTicket, "窗口二");
		Thread t3 = new Thread(saleTicket, "窗口三");
		
		//启动子线程
		t1.start();
		t2.start();
		t3.start();
	}

}

方式3:ReentrantLock

使用ReentrantLock对象加锁和解锁

package sale3;

public class SaleTicket implements Runnable {

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		while(true) {
			//上锁
			Test.lock.lock();
		
			//当票卖完后,就不再卖票,结束循环
			if(Test.ticketNum<=0) {
				//开锁
				Test.lock.unlock();
				break;
			}
			
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			//卖票
			Test.ticketNum--;
			System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
			
			//开锁
			Test.lock.unlock();
		}

	}
}
package sale3;

import java.util.concurrent.locks.ReentrantLock;

public class Test {
	//定义变量存储票的总数量
	public static int ticketNum = 100;
	
	//创建同步锁
	public static ReentrantLock lock = new ReentrantLock();
	
	
	public static void main(String[] args) {
		//创建线程类对象
		SaleTicket saleTicket = new SaleTicket();

		//创建子线程
		Thread t1 = new Thread(saleTicket, "窗口一");
		Thread t2 = new Thread(saleTicket, "窗口二");
		Thread t3 = new Thread(saleTicket, "窗口三");
		
		//启动子线程
		t1.start();
		t2.start();
		t3.start();
	}

}
结论:
	线程不安全的代码,效率高
	线程安全的代码,效率低

八、知识点扩展

8.1、死锁

同步锁使用的弊端:当线程任务中出现了多个同步代码块(多个锁)时,如果同步代码块中嵌套了其他的同步代码块。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

案例:编写死锁实例

创建2个线程类

public class DeadLock1 implements Runnable {
    @Override
    public void run(){
        try{
            System.out.println("Lock1 running");
            while(true){
                synchronized(Test.obj1){
                    System.out.println("Lock1 lock obj1");
                    Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
                    synchronized(Test.obj2){
                        System.out.println("Lock1 lock obj2");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
public class DeadLock2 implements Runnable{
	@Override
    public void run(){
        try{
            System.out.println("Lock2 running");
            while(true){
                synchronized(Test.obj2){
                    System.out.println("Lock2 lock obj2");
                    Thread.sleep(3000);
                    synchronized(Test.obj1){
                        System.out.println("Lock2 lock obj1");
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

创建测试类

public class Test {
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    public static void main(String[] args){
        Thread a = new Thread(new DeadLock1());
        Thread b = new Thread(new DeadLock2());
        a.start();
        b.start();
    }  
}

运行的结果如图所示:

图片.png

可以看到,Lock1获取obj1,Lock2获取obj2,但是它们都没有办法再获取另外一个obj,因为它们都在等待对方先释放锁,这时就是死锁。

8.2、线程的互斥与同步,异步

互斥:一个公共资源同一时刻只能被一个线程使用,多个线程不能同时使用公共资源。
同步:两个或两个以上的线程在运行过程中协同步调,按预定的先后次序运行
异步:多个线程的运行过程互不相干,它们之间关系为异步。

九、综合案例

FeiYuqing.java

/**
 * 费玉清
 *
 * @author qin
 */
public class FeiYuqing extends Thread {
    @Override
    public void run() {
        int race = 100;
        while(race > 0){
            try {
                int x = (int) (Math.random()*20);
                if(race - x >= 0){
                    race = race - x;
                    sleep(1*1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("¥费玉清还有"+race+"米就逃跑成功啦");
        }
        System.out.println("**********费玉清逃跑成功");
    }
}

Chimpanzee.java

/**
 * 大猩猩
 *
 * @author qin
 */
public class Chimpanzee extends Thread {
 
    @Override
    public void run() {
        int race = 100;
        while(race > 0){
            try {
                int x = (int) (Math.random()*20);
                if(race - x >= 0){
                    race = race - x;
                    sleep(1*1000);
               }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("!大猩猩还有"+race+"米就抓到费玉清啦");
        }
        System.out.println("**********大猩猩抓到了费玉清,祝你们幸福");
    }
}

Gym.java

/**
 * 健身房
 *
 * @author qin
 */
public class Gym {
 
    public static void main(String[] args) {
        FeiYuqing fei = new FeiYuqing();
        fei.start(); //费玉清跑
        Chimpanzee chi = new Chimpanzee();
        chi.start(); //大猩猩追
    }
}