个人对于多线程的理解
当我们运行多功能APP例如王者荣耀这个游戏时,游戏内并不是单一的功能操作,例如你打团疯狂操作时可能还打开装备栏秒换三装,同时还开着语音指挥全场,以上三个功能(操作,买装备,开语音)也就可以理解为一个进程中的三个线程【王者荣耀源码是不是这个机制我也不太清楚,但大概就是这么个意思】。显然,之前学习的简单的java程序,不可能实现如此复杂的功能,我们就需要将每个功能写成一个线程类,程序执行时,可以执行多个线程,进而实现复杂功能,且一个线程出现异常或错误,不会影响其他线程的功能(你操作时卡进了某个bug,也不影响你的语音瞎指挥)。
多线程任务的创建
方式一:通过继承Thread类
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
3.创建Thread类的子类对象
4.调用Thread类中的start方法,开启新的线程,执行run方法
public class MyThread extends Thread{//自己写的第一个线程
@Override
public void run() {//线程任务,这个线程要干啥
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//System.out.println(0/0);我加这一句话验证,一个线程出现异常,不会影响其他线程的执行
}
}
public class MyThread2 extends Thread {//自己写的第二个线程
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+i);//Thread.currentThread().getName()获取当前线程的名称,setName是设置线程名
}
}
}
public class demo01thread {
public static void main(String[] args) {
MyThread mt=new MyThread();//3
MyThread2 mt2=new MyThread2();//3
mt.start();//这是新线程,里面有错误,不会影响主线程的执行
mt2.start();
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
执行结果
main0
main1
main2
Thread-00
Thread-01
Thread-02
Thread-10
Thread-11
Thread-12
方式二:通过Runnable实现接口
1.创建Runnable实现类
2.重写run方法
3.创建runable接口的实现类对象
4.创建thread类对象,构造方法中传递runable实现类对象
4.调用Thread类中的start方法,开启新的线程,执行run方法
public class RunableImpl implements Runnable {//1.创建runable实现类
@Override
public void run() {//重写run方法
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class demo {
public static void main(String[] args) {
RunableImpl run=new RunableImpl();//3.创建runable接口的实现类对象
Thread t=new Thread(run);//4.创建thread类对象,构造方法中传递runable实现类对象
t.start();
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
实现Runnable好处
1.避免了单继承的局限性,但实现了Runnable接口,还可以继承其他的类
2.降低了程序的耦合性
尽量使用Runnable方式
一个简单例子及其优化
某景区有三个买票窗口,一共卖100张票,每个买票窗口模拟为一个线程,试用代码模拟卖票流程。
//实现卖票案例
public class RunableImpl implements Runnable {
//定义一个多线程共享的票源
private int tikect=100;
@Override
public void run() {
while (true) {
if (tikect > 0) {
//有票,卖票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tikect + "张票");
tikect--;
}
}
}
}
//创建三个线程,同时开启,对共享票进行出售
public class Tiket {
public static void main(String[] args) {
Runnable run=new RunableImpl();
Thread t=new Thread(run);
Thread t0=new Thread(run);
Thread t1=new Thread(run);
t.start();
t0.start();
t1.start();
}
}
执行结果太长:截取一部分
Thread-1正在卖第76张票
Thread-2正在卖第75张票
Thread-0正在卖第73张票
Thread-2正在卖第73张票
Thread-1正在卖第73张票
Thread-1正在卖第70张票
。
。
。
Thread-2正在卖第1张票
Thread-0正在卖第0张票
Thread-1正在卖第-1张票
我们可以看到卖票系统出现的紊乱,为啥,因为我们没有设置对共享资源的保护,一个线程在执行途中,可能另一个线程修改了票的个数,导致出现了上面这种运行结果,操作系统中我们解决这种问题用的是互斥访问临界区的方法,为临界资源设置一个mutex互斥访问变量,mutex为1时,说明没有线程使用该资源,此时某线程申请使用该资源,操作系统将该资源分配给该线程,同时将mutex设置为0,期间不允许其他线程访问该资源,线程结束后,释放该资源,mutex设置为1,别的线程即可访问,此方法确保了同一时刻最多只能有一个线程访问该资源,安全性高。Java中处理此类问题类似。
方法一:使用同步代码块
格式: Synchronized(锁对象){
可能出现问题的代码块;
}
以上代码我们可以优化为
public class RunableImpl implements Runnable {
//定义一个多线程共享的票源
private int tikect=100;
//创建一个锁对象
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj){
if (tikect > 0) {
//有票,卖票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tikect + "张票");
tikect--;
}
// }
}
}
}
方法二:使用同步方法
1.将访问了共享变量的代码提取出来,放到一个方法中
2.在方法上添加synchronized修饰符
public class demo implements Runnable{
private int tikect=100;
@Override
public void run() {
while (true) {
ticket();
}
}
// 定义一个同步方法
public synchronized void ticket(){
if (tikect > 0) {
//有票,卖票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tikect + "张票");
tikect--;
}
}
}
第三种方式:使用Lock锁
lock提供了比synchronized方法和语句可获得更广泛的锁定操作
lock接口中的方法
void lock();获取锁
void unlock();释放锁
使用步骤
1.在成员位置创建一个Reentrantlock对象
2.在可能出现安全问题的代码前调用lock接口中的方法lock获取锁
3.在可能出现安全问题的代码前调用lock接口中的方法unlock释放锁
public class tongbulock implements Runnable{
private int tikect=100;
Lock l = new ReentrantLock();//1
@Override
public void run() {
while(true) {
l.lock();
if (tikect > 0) {
//有票,卖票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + tikect + "张票");
tikect--;
}
l.unlock();
}
}
}