Java多线程机制简单理解-01

个人对于多线程的理解

当我们运行多功能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();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值