Java线程

一,Java线程

1,区分进程和线程

电脑或者手机上处理计算任务实际上就是CPU,它时刻都在运行。
而进程,顾名思义就是一个正在进行的程序。它拥有独立的内存空间。两个进程在内存是隔离的,它们的内存地址值不同。
一个进程代表着一个任务,单个CPU只能执行一个任务。之所以有执行多个任务的错觉,是因为CPU切换的非常快。
那么线程是什么呢?
一个进程,可以把它比作一个车间。车间中有许多工人,协同完成这个任务。可以把工人理解为线程,一个车间可以有多个工人,同样一个进程可以有多个线程。
有句话这么说,线程是进程的执行顺序。或许可以把这个车间比作一个执行流水线任务的车间。

比如:java.exe会启动JVM执行class文件,就会产生一个java.exe的进程,此时就一定有一个线程负责java程序的执行
,这个线程会找到main方法作为入口,而这个线程是主线程。在主线程中又可以创建线程。

2,如何自定义一个线程

创建线程有两种方法:
①一种是把类声明为Thread类的子类,该子类应该重写Thread类的run方法。

写好线程类:
class FirstThread extends Thread{
    @Override
    public void run() {
        super.run();
        System.out.println("FirstThread");
    }
}
//建立线程对象(此时并不有开启线程)
FirstThread ft=new FirstThread();
//两个作用:开启线程和调用该线程的run方法
ft.start();
注意的是--->ft.run();//调用run,并没有开启线程。

所以,线程的run方法负责存放子线程需要执行的代码。

②实现Runnable接口,复写run方法。在创建Thread时把该类的实例作为一个构造参数传递进去。然后调用该Thread对象的start方法,就会创建一个线程。

class SecondThread implements Runnable{
    @Override
    public void run() {
        System.out.println("SecondThread");
    }
}
//Runnable实现类作为Thread的构造参数
Thread thread=new Thread(new SecondThread());
//开启线程,调用Runnable实现类的run方法
thread.start();

不禁要问,为什么要提供两种创建线程的方法?

之前说过,start的方法会去调用该线程的run方法。
对于第一种创建线程的方法,Thread子类的句柄指向Thread子类的实例,调用的当然是Thread子类的run方法。

对于第二种创建线程的方法,查看Thread的构造方法:
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}
而init方法中有这么一段代码:
this.target = target;//target是Runnable类型的变量

Thread类的run方法,如下:
public void run() {
    if (target != null) {
        target.run();
    }
}
如果是通过第二种创建线程,传递的Runnable就不为null。将会调用Runnable接口的run方法。接口引用指向子类对象,那么也就会调用Runnable实现类的run方法。

发现这两种创建线程实现的原理是一样的,之所以有两种创建线程的方法,是由于Java不支持多继承,如果一个类有继承,却又有需要在子线程运行的需求,那么就可以实现Runnable接口类创建线程。

3,线程的几种状态

new一个继承了Thread的类,表示该线程被创建

start方法,会开启这个线程,此时这个线程具备执行资格,不具备执行权。这种状态为“阻塞状态”

正在执行的线程,属于又有执行资格,也有执行权,称为“运行状态”

正在运行的线程被sleep或wait后,丧失了执行权和执行资格,称为“冻结状态”

stop掉或线程run方法执行完毕,该线程就死亡了。称为“消亡状态”

4,线程的安全问题
看一个卖票的例子:

public class Asule {
    public static void main(String[] args) {
        Ticket ticket2=new Ticket();
        Thread t1=new Thread(ticket2);
        Thread t2=new Thread(ticket2);
        Thread t3=new Thread(ticket2);
        Thread t4=new Thread(ticket2);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Ticket implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
        System.out.println(Thread.currentThread().getName()+"卖票:"+ticket--);
            }
        }
    }
}

开启四个线程去模拟卖票的四个窗口。为了模拟安全问题,在执行卖票途中,会让线程sleep。
运行后,就会发现有的线程会卖出错误的票。

Thread-2卖票:0
Thread-3卖票:-1

这种情况产生的原因很简单,一个线程执行run方法,被sleep处于冻结状态。另一个线程持有执行权也执行run方法,同样sleep处于冻结状态。那么,前一个线程会较早的苏醒,如果此时还有1张票,那么卖出这张,票数减少一张为0张。而后一个苏醒后,还会继续卖出一张。这就会导致安全问题。

解决的方法,使用同步代码块

同步代码块   
synchronized (任何对象){
    需要同步的代码
}

synchronized是怎么解决线程的安全问题的?
synchronized需要传进一个对象,可以把它理解为一个锁。
当线程0执行到同步代码块时,就把这把锁拿走了。
而不管线程0在执行代码时,出现什么情况。其他线程因为没有锁,都无法执行同步代码块的内容。
只有在线程0执行完同步的代码后,锁才会被释放。

哪些语句在操作共享数据,就是需要同步的代码。

加上同步后

class Ticket implements Runnable{
    private int ticket=100;
    Object obj=new Object();

    @Override
    public void run() {
        while(true){
            synchronized (obj) {
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
        System.out.println(Thread.currentThread().getName()+"卖票:"+ticket--);
                }
            }
        }
    }
}

同步代码块是封装代码的,而函数也可以封装代码。完全可以把同步加到函数上,那么整个函数就有了同步的特性。如:

class Ticket implements Runnable{
    private int ticket=100;
    Object obj=new Object();

    @Override
    public synchronized void run() {
        while(true){
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖票:"+ticket--);
            }
        }
    }
}

同步的前提:
1,必须要有两个或两个以上的线程
2,必须是多个线程使用一个锁
同步的优缺点:
优点,解决了多线程的安全问题。缺点,多了一个判断锁的动作,消耗了资源。

5,线程间通信
例子:

public class Asule {
    public static void main(String[] args) {
        Res r=new Res();
        In in=new In(r);
        Out out=new Out(r);
        Thread t1=new Thread(in);
        Thread t2=new Thread(out);
        t1.start();
        t2.start();
    }
}

class Res{
    public String name;
    public String sex;
}

class In implements Runnable{
    Res r;
    In(Res r){
        this.r=r;
    }
    public void run(){
        int x=0;
        while(true){
            synchronized(r){
                if(x==0){
                    r.name="ronaldo";
                    r.sex="man";
                }else{
                    r.name="anglebady";
                    r.sex="女";
                }
            }
            x=(x+1)%2;
        }   
    }
}

class Out implements Runnable{
    Res r;
    Out(Res r){
        this.r=r;
    }
    public void run(){
        while(true){    
            synchronized(r){
                System.out.println(r.name+":::"+r.sex);
            }
        }
    }
}

这里写图片描述
一个为Resource输入数据,一个线程输出Resource数据。打印的结果是这样的:

ronaldo:::man
ronaldo:::man
ronaldo:::man
.........
anglebady:::女
anglebady:::女
anglebady:::女
.........

出现这种情况的原因是,输入线程为Resource输入数据后,此时CPU的执行权落在了输出线程上,如果该线程长期握有执行权,就会重复的输出Resource数据。
最理想的状态,莫过于,输入线程输入一段数据,输出线程输出一段数据。交替的线程执行。
这可以使用线程的等待唤醒机制来达到。多线程的等待唤醒机制:

输入结束,就输出。
输出结束,就输入。

等待唤醒机制的思路是,定义一个标记布尔值flag,判断flag时会默认让一个线程wait。另一个执行的线程,执行完之后,会取反flag,自己wait,并notify唤醒被wait的线程。
如下:

class Res{
    public String name;
    public String age;
    public boolean flag;
}

class In implements Runnable{
    Res r;
    In(Res r){
        this.r=r;
    }
    public void run(){
        int x=0;
        while(true){
            synchronized(r){
                if(r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(x==0){
                    r.name="ronaldo";
                    r.age="man";
                }else{
                    r.name="anglebady";
                    r.age="女";
                }
                x=(x+1)%2;
                r.flag=!r.flag;
                r.notify();
            }
        }   
    }
}

class Out implements Runnable{
    Res r;
    Out(Res r){
        this.r=r;
    }
    public void run(){
        while(true){
            synchronized(r){
                if(!r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(r.name+":::"+r.age);
                r.flag=!r.flag;
                r.notify();
            }
        }
    }
}

wait,notify,notifyAll这三个方法,它们都是Object中的方法。
查看API,有这么一句,wait,notify,notifyAll处于的线程必须拥有此对象的监视器。
而这里的监视器,指的就是同步里面的锁。
而锁只会存在于同步中,所以wait,notify,notifyAll这些方法都必须定义在同步代码块或同步函数中。
也正是因为如此,锁可以是任意对象,那么任意对象都能调用的方法,也就被定义在Object中。

上面的写法比较繁琐,可以改写为:

class Res{
    public String name;
    public String sex;
    public boolean flag;

    public synchronized void out(){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(this.name+":::"+this.sex);
        flag=!flag;
        this.notify();
    }

    public synchronized void in(String name,String sex){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name=name;
        this.sex=sex;
        flag=!flag;
        this.notify();
    }
}

class In implements Runnable{
    Res r;
    In(Res r){
        this.r=r;
    }
    public void run(){
        int x=0;
        while(true){
            if(x==0){
                r.in("ronaldo","man");
            }else{
                r.in("anglebady","女");
            }
            x=(x+1)%2;
        }   
    }
}

class Out implements Runnable{
    Res r;
    Out(Res r){
        this.r=r;
    }
    public void run(){
        while(true){
            r.out();
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值