java学习---多线程

多线程学习(一)

进程

进程是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程。一个进程有自己的方法区和堆。

线程

进程可以进一步细化为线程,是程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。

同一进程里的不同线程使用同一方法区和堆空间。所以一个进程中的多个线程共享相同的内存空间,从同一个堆中分配对象,可以访问相同的变量和对象,这虽然使得线程间通信更加简便高效,但是也同样使得多个线程共享的系统资源受到了安全隐患。

一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程,当然如果发生异常,会影响主线程。

并行:多个CPU同时执行多个任务
并发:一个CPU(采用时间片)看似同时执行多个任务

多线程的创建

Thread中的常用方法:
1.start():启动当前线程,调用当前线程的run()
2.run():通常需要重写Thread中的此方法,将创建线程需要执行的操作写在里面
3.currentThread():静态方法,返回当前执行代码的线程
4.getName():设置当前线程的名字
5.getName():获取当前线程的名字
6.yield():释放当前CPU的执行权,但是在多个线程的情况下,释放后得到执行权的可能是该CPU也可能是其他CPU
7.join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,等待b执行完后a才结束阻塞状态,等待CPU分配资源后执行

方式一:继承于Thread类
1.创建一个继承于Thread类的子类
2.重写Thread类的run(),–>将此线程执行的操作声明在run方法中
3.创建Thread类中的子类对象,‘
4.通过此对象调用start()方法
在这里插入图片描述
注意:这里调用的是start()方法,开启了另一个线程,如果直接调用run()方法就还是在主线程中。

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.start();
        MyThread2 t2 = new MyThread2();
        t2.start();
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            if(i%2==1){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

结果如下:可以看出是两个线程在运行
在这里插入图片描述

class Window extends Thread{
    private static int ticket=100;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(getName()+":卖票,票号为:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
        }

}
public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}

注意:这里的ticket有static

方法二:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
在这里插入图片描述

class Window1 implements Runnable{
    private  int ticket=100;
    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                ticket--;
            }else{
                break;
            }
        }
    }

}
public class WIndowTest1 {


    public static void main(String[] args) {
        Window1 w1 = new Window1();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

比较这两种方式:
优先选择实现Runnable接口的方式,原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合处理多个线程数据共享的情况

线程的生命周期

5个状态:新建 就绪 运行 死亡 阻塞
生命周期

线程的安全问题

出现的原因:在某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,对同一对象进行操作。
解决想法:当一个线程在操作共享数据时(即使该线程被阻塞),其他线程不能参与进来,等该线程执行结束后,其他线程才能开始对其进行操作。
措施:在java中通过同步机制,来解决线程的安全问题
优缺点:同步方式,解决了线程的安全问题,,但是操作同步代码时,只能有一个线程参与,其他线程等待,相当一个单线程过程,效率较低。
方式一:同步代码块
synchronized(同步监视器:锁(要求多个线程共用一把锁)){
//需要被同步的代码(对共享数据进行操作的代码)
}
说明:任何一个类的对象,都可以充当锁。

class Window2 implements Runnable{
    private int ticket=100;
    Object obj=new Object();
    @Override
    public void run() {
        while(true){
            synchronized(obj){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }else{
                    break;
                }
        }
    }

}
public static class WindowTest2 {

    public static void main(String[] args) {
        Window2 w2 = new Window2();
        Thread t1 = new Thread(w2);
        Thread t2 = new Thread(w2);
        Thread t3 = new Thread(w2);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }

}
}

这里使用的是创建多线程的第二种方法,这里里只创建了一个对象,所以只有一把锁:obj,所以不会出现错票和重票的情况。
下面给出一种错误写法:

package com.company;

class Window3 extends Thread {
    private static int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (ticket > 0) {
                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }

    }

    public static class WindowTest3 {
        public static void main(String[] args) {
            Window3 w1 = new Window3();
            Window w2 = new Window();
            Window w3 = new Window();
            w1.setName("窗口一");
            w2.setName("窗口二");
            w3.setName("窗口三");
            w1.start();
            w2.start();
            w3.start();
        }
    }
}



代码运行结果如下:
在这里插入图片描述
在这里插入图片描述
分析:出现了重票,这是因为创建了三个对象,有三个obj,共用的不是一把锁,如果想要正确的话,需要将obj设置为static或者synchronized(Window3.class)

方式二:同步方法
说明:1.同步方法任然涉及到同步监视器,只是不需要显式声明
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值