Java中的多线程

首先,我们引出进程的概念:

1.进程:

任何一个程序在运行起来之后就是进程,生命周期为程序开始到程序结束。

每个进程在运行周期内,有自己一个PCB、虚拟地址空间、页表、以及页表映射的物理内存空间。PCB中存放着进程的数据 上下文、进程状态、PID、优先级、内存指针。由操作系统通过调度PCB来管理进程,操作系统对PCB的管理就是对进程的管理。

2.线程:

上面我们从操作系统角度浅谈了一下进程的概念;

我们有很多时候希望可以同时推进多个任务,但是,我们看到每个进程在创建时有许多的东西被创建。

因此,太麻烦,我们这个时候引出了线程的概念,在一个程序里同时执行多个任务;

创建一个进程,然后再这一个进程里创建多个任务流,一个任务流就称之为一个线程;因此,线程一套就是共享数据,因此线程就相比进程显得更加轻便简单。

在一个进程里最少都有一个线程,因此线程叫做轻量级进程。

如何创建线程呢?

大体上有三种:

1.继承Thread类;

2.实现Runnable接口;

3.实现Callable接口。

1.继承Thread类:

Java中java.lang.Thread这个类表示线程,一个类可以继承Thread类并覆写其run方法来实现一个线程;

public class HelloThread extends Thread{
    public void run(){
        System.out.println("Hello");
    }
    
}

run方法是固定的public权限,没有参数,没有返回值,不抛出受查异常。

类似于单线程中的main方法,线程run方法的第一条语句开始执行直到结束。

public static void main(String[] args){
    Thread thread = new HelloThread();
    thread.start();
}

上述代码为:在main方法中创建了一个线程对象,并调用其start方法,当调用start方法后,HelloThread中的run方法开始执行。

start表示启动线程,使其成为一条单独的执行流,操作系统会分配线程相关的资源,每个线程会有单独的程序计数器和栈(后面会单独将栈跟程序计数器),操作系统会将此线程作为一个独立的个体进行调度, 执行的起点就是run方法。

当直接在main方法中调用run方法的话,只是单纯的调用了一个方法,并没有体现线程的概念,这个时候可以使用currentThread()来查看当前线程。

2.实现Runnable接口

通过继承Thread类实现线程比较简单,但是Java里面只支持单继承,如果已经有了父类,就不可以在继承Thread类,于是就有了Runnable接口。

Runnable接口里只有一个run方法。

public class HelloRunnable implements Runnable{

    public void run(){
        System.out.println("hello");
    }

}

想要启动线程,还要创建一个Thread对象,在传递一个Runnable对象:

public static void main(String[] args){
    Thread helloThread = new Thread(new HelloRunnable());
    helloThrea.start();
}

还可以使用匿名内部类和Lambda表达式来实现接口:

1.匿名内部类:

   public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
               System.out.println("hello"); 
            }
        });
        thread.start();
    }

2.Lambda表达式:

 public static void main(String[] args) {
       Runnable runnable = ()-> System.out.println("hello");
       new Thread(runnable).start();
    }

使用Runnable接口可以避免单继承局限以及更好的体现程序共享的概念。

3.实现Callable接口

Runnable接口中run方法没有返回值,但有些情况需要有返回值,此时就需要实现Callable接口。该接口中没有run方法,就只有一个call方法。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

call方法的意义跟run方法一样,里面是线程实现的主体代码:

class MyThread implements Callable<String> {
    private int ticket = 10 ; // 一共10张票
    public String call() throws Exception {
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket -- );
        }
    return "票卖完了,下次吧。。。" ;
    }
}

然后再调用start方法来启动线程,但是Thread类中不可以接受Callable类型的参数;

但是有一个类FutureTask可接收Callable类型的参数;

然后FutureTask实现了RunnableFuture接口;

RunnableFuture接口继承了Runnable接口;

同样,Thread类也继承了Runnable接口。

   public static void main(String[] args) {
      FutureTask<String> futureTask = new FutureTask<String>(new Mythread);
      new Thread(futureTask).start();
    }

这样就启动了覆写了Callable接口的线程。

4.线程有一些基本属性及方法。

属性:id、名称、优先级、状态

方法:sleep方法、yield方法、join方法、stop方法、interrupt方法、

每一个线程都有id、名称,id是一个递增的整数,每创建一个线程就加一。名称的默认值是Thread-后加编号,也可以自己指定,在Thread构造方法中,也可通过setName方法来设置。

优先级,可能会优先执行的线程,在程序中设置优先级可能会映射到操作系统中。

设置优先级:public final void setPriority(int newPriority)

取得优先级:public final int getPriority()

状态:表示当前线程的状态。

1.public  static  native  void  sleep(long  millis) throws InterruptedException;

调用该方法会让当前线程睡眠指定的时间,单位是毫秒:

睡眠期间,该线程会让出CPU,但是不会释放对象锁。

2.public static native void yield();

yield方法会让出CPU让其他线程先执行,但是不会释放对象锁;yield方法会让相同优先级的线程获得CPU的时间,yield方法是将线程从运行状态调整至准备状态,并不是变为阻塞状态。

3.public  final  void  join()  throws InterruptedException;

一个线程等待另一个线程的退出,然后再往下执行,在等待的过程中,这个等待可能被中断,如果被中断,会抛出异常。

 4. public final void stop();

stop方法会解除线程获取的所有锁定,当一个线程对象调用stop方法时候,该对象所运行的线程会立即停止。

因为stop方法会造成线程不安全的情况,因此,已经被标记为过时方法。

5. public void interrupt();

interrupt只是对线程进行中断,中断并不是强迫终止一个线程,它给线程传递一个取消信号,由线程来决定如何以及何时退出。

a.对于正在执行的线程,interrupt方法只是设置线程的中断标志位,没有任何其他的作用。

b.当线程调用join/wait/sleep方法后,线程对象调用interrupt方法会抛出异常。抛出异常后,中断标志位会被清空,而不是被设置:

  public static void main(String[] args) {
     Thread t = new Thread(){
         @Override
         public void run() {
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 System.out.println(isInterrupted());
             }
         }
     };
      t.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {

        }
        t.interrupt();
    }

上述的输出为false。

c.如果线程正在等待锁,调用interrupt方法只会设置线程的中断标志位,线程依旧处于等待的状态:

public class Main {
    private static Object lock = new Object();
    private static class A extends  Thread{
        @Override
        public void run() {
            synchronized (lock){
                while (!Thread.currentThread().isInterrupted()){
                    System.out.println("哈哈哈");
                }
            }
            System.out.println("exit");
        }
    }
    public static void test() throws InterruptedException {
        synchronized (lock){
            A a = new A();
            a.start();
            Thread.sleep(1000);
            a.interrupt();
            a.join();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        test();
    }
}

上面的代码,主线程持有了lock锁,然后启动线程a,然而线程a也尝试获得lock锁,所以就进入等待锁队列,随后调用interrupt方法和join方法,来等待a线程结束。

然而运行的结果是线程并没有结束。

5.守护线程

守护线程是一种特殊线程,线程分为两种,“用户线程”,“守护线程”。

只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后 一个非守护线程结束时,守护线程才会随着JVM一同停止工作。

将当前线程标记为守护线程或用户线程:public final void setDaemon(boolean on),on为true为守护线程

判断守护线程:public final boolean isDaemon(),返回值为true为守护线程。

垃圾回收机制就是典型的守护线程。

6.synchronized与同步:

public class Main implements Runnable {
    private static int counter = 0;
    @Override
    public void run() {
        for (int i = 0;i<1000;i++){
            counter++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;
        Thread [] threads = new Thread[num];
        for(int i=0;i<num;i++){
            threads[i]=new Thread(new Main());
            threads[i].start();
        }
        for(int i=0;i<num;i++){
            threads[i].join();
        }
        System.out.println(counter);
    }
}

上述代码创造了1000个线程,每个线程对counter循环加了1000次,主线程会等待所有线程结束后打印counter的值,期望的结果是100万,然而打印出来却是90多万。因为上述代码的counter++操作不是原子性操作。

counter++分为三个步骤:

       1.取得counter的值;

       2.在当前基础上加1;

       3.将新值赋给counter。

两个线程可能同时执行第一步,然后重复写入相同的值。

因为上述的情况就需要引入锁的概念。

synchronized是为了防止多个线程同时执行同个对象的同步代码块

synchronized可用于修饰类的实例方法、静态方法和代码块;

    1.实例方法:

     

class Counter{
    private  int counter = 0;
    public synchronized void increaseNum(){
        counter++;
    }
    public synchronized int getCounter(){
        return counter;
    }
}
public class Main implements Runnable {
    Counter counter;

    public Main(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0;i<1000;i++){
            counter.increaseNum();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;
        Counter counter = new Counter();
        Thread [] threads = new Thread[num];
        for(int i=0;i<num;i++){
            threads[i]=new Thread(new Main(counter));
            threads[i].start();
        }
        for(int i=0;i<num;i++){
            threads[i].join();
        }
        System.out.println(counter.getCounter());
    }
}

将代码中加入synchronized关键字来修饰,这样方法内的代码就变成了原子操作,这次无论怎样结果都为100万;

在上述代码中传入的对象只有一个counter,是同一个对象;

因此,synchronized实例方法实际保护的是同一个对象的方法调用,确保只能有一个线程来执行。

synchronized保护的是对象而非代码,只要访问同一个对象的synchronized方法,即使不同的代码,也会同步顺序访问。

2.静态方法

class Counter{
    private  static int counter = 0;
    public static synchronized void increaseNum(){
        counter++;
    }
    public static synchronized int getCounter(){
        return counter;
    }
}
public class Main implements Runnable {


    @Override
    public void run() {
        for (int i = 0;i<1000;i++){
            Counter.increaseNum();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;

        Thread [] threads = new Thread[num];
        for(int i=0;i<num;i++){
            threads[i]=new Thread(new Main());
            threads[i].start();
        }
        for(int i=0;i<num;i++){
            threads[i].join();
        }
        System.out.println(Counter.getCounter());
    }
}

上述实例方法中保护的是实例对象是this,对于静态方法保护的是类对象。

3.代码块

class Counter{
    private  int counter = 0;
    public void increaseNum(){
        synchronized (this){
            counter++;
        }

    }
    public  int getCounter(){
        synchronized (this){
            return counter;
        }

    }
}

public class Main implements Runnable {
    Counter counter;

    public Main(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0;i<1000;i++){
            counter.increaseNum();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        int num=1000;
        Counter counter = new Counter();
        Thread [] threads = new Thread[num];
        for(int i=0;i<num;i++){
            threads[i]=new Thread(new Main(counter));
            threads[i].start();
        }
        for(int i=0;i<num;i++){
            threads[i].join();
        }
        System.out.println(counter.getCounter());
    }
}

synchronized的特征:

          1.可重入性;

          2.内存可见性。

可重入性:对同一个执行线程,它获得了锁之后,在调用其它需要同样锁的代码的时候,就可以直接调用;

内存可见性:在释放锁的时候,所有写入都会写回内存,而获得锁后,都会从内存中读取最新的数据;而若是只需要保证内存可见性,可用volatile修饰变量。

死锁:

使用synchronized或者其他锁,应注意死锁;

public class Main  {
    private static Object locka = new Object();
    private static Object lockb = new Object();
    private static void startThreadA(){
        Thread athread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (locka){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        
                    }
                    synchronized (lockb){
                        
                    }
                }
            }
        });
        athread.start();
    }
    private static void startThreadB(){
        Thread bthread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lockb){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {

                    }
                    synchronized (locka){

                    }
                }
            }
        });
        bthread.start();
    }

    public static void main(String[] args) throws InterruptedException {
        startThreadA();
        startThreadB();
    }
}

这样athread与bthread陷入了互相等待的过程。

以上就是我对线程的一些浅薄的理解,好了,不说了,继续敲代码了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值