初识Java多线程

Posted NamenessLess

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识Java多线程相关的知识,希望对你有一定的参考价值。

创建多线程

多线程的作用:一个时间点,同时做多个事情
(一个时间点,同时执行多行代码)

Thread

new 一个Thread类的子类

  1. 使用匿名内部类
public class Main {
    public static void main(String[] args) {
    
        Thread thread = new Thread() {
            @Override
            public void run () {
                System.out.println("这是一个子线程");
            }
        };
        
    }
}
  1. 使用静态内部类,该类继承自Thread类
static class A extends Thread {
        @Override
        public void run () {
            System.out.println("这是一个子线程");
        }
}

Runnable

new 一个 Runnable 子类,传入Thread构造函数中执行

public class Main {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个子线程");
            }
        };
        Thread thread = new Thread(runnable, "线程1");

    }

}

Thread thread = new Thread(runnable, “线程1”);

这里的第二个参数,是给该子线程起一个名字

启动一个线程

启动线程使用 start() 方法

public class Main {
    public static void main(String[] args) {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个子线程");
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        //启动线程
        thread.start();
    }

}

线程的执行顺序

Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个子线程");
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        thread.start();
        System.out.println("main线程执行");

上面这段代码,执行顺序是:

main线程的打印语句执行的概率大于子线程打印语句执行的概率

因为创建子线程比较耗时

Thread类的部分方法

1.public static Thread currentThread()
返回当前正在执行的线程对象的引用

2.public static void yield()
线程让步
即让当前线程由运行状态转变为就绪状态

3.public static void sleep​(long millis) throws InterruptedException
使当前正在执行的线程在指定的毫秒数内休眠(暂时停止执行)

中断一个线程

  1. 使用自定义标志位中断
public class Main {
    //标志位
    private static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    while (!flag) {
                        System.out.println("这是一个子线程");
                        Thread.sleep(5000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        thread.start();
        Thread.sleep(3000);
        flag = true;
        System.out.println("main线程执行");
    }

}
  1. 调用 interrupt() 函数直接中断线程
public class Main {

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (Thread.currentThread().isInterrupted() == false) {
                    System.out.println("这是一个子线程");
                }
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        System.out.println("main线程执行");
    }

}

Thread类中有一个中断标志位,通过调用 interrupt() 函数就会让中断标志位变成 true,此时调用 isInterrupted() 方法获取中断标志位的值,通过条件判断使线程中断

等待一个线程

通过调用 join 方法

public class Main {

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(i);
                }
            }
        };
        Thread thread = new Thread(runnable, "线程1");
        thread.start();
        thread.join();
        System.out.println("main线程执行");
    }

}

执行结果

0
1
2
3
4
main线程执行

在等待子线程运行时,main线程,会卡在join()方法的调用处,直到子线程结束后,main线程才会向下运行

当然join也有参数
public final void join​(long millis) throws InterruptedException

等待线程多少毫秒,到了时间后就不等待继续执行了,如果在时间之内子线程运行结束,那么就直接向下运行

线程状态

public static enum State {
        NEW,		//创建
        RUNNABLE,   //可运行
        BLOCKED,	//阻塞
        WAITING,	//等待
        TIMED_WAITING,//超时等待
        TERMINATED;	//终止

        private State() {
        }
}

上面是 Thread.State 枚举类

里面是线程的六个状态

线程安全

由于多线程之间可以相互共享资源(变量),所以会出现一些问题,这些问题就是线程安全问题

不安全的原因:多个线程对同一个变量的操作

如:两个线程同时修改同一个变量的值,这就会导致变量的值不会出现我们预期的结果

public class Main {

    private static int x = 0;

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    x++;
                }
            }
        };
        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        thread1.start();
        thread2.start();
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(x);
    }

}

上面代码的结果每次都不一样,有的时候是20000,有的时候不是20000,这就是线程不安全引起的原因

不安全的原因之一是:不具有原子性
原子性是指:多行代码执行时,是不可再分的

举个例子,就好比你打开你的笔记本电脑,先翻开笔记本,再按电源键,这两个事情必须连在一起执行,不能分开,原子性也类似,对变量进行操作,必须一次性完成,不可分

解决方法

加锁共享变量

举个简单的例子:

共享变量就好比打印机,线程好比人,两个人同时使用一个打印机,肯定不行,会导致两个人要打印的内容打印到同一张纸上,为了防止这个现象的发生,那就要规定一个打印机在被一个人使用的时候,另一个人不能使用,这就相当于使用打印机的人给打印机加了锁,自己在使用时,其他人是不能使用的,对于线程和共享变量,也是这个道理

多个线程访问共享变量,最终表现为:

一个线程加锁,操作变量,释放锁,其他线程加锁失败,需要等待(等待可以是一直等着,也可以是执行其他代码,过一会再回来再尝试加锁)

这就好比一个人正在用打印机,你也想用,但是已经被占了,要么你就排队,排在他后面,等着他用完你用,此时你只能等着,做不了其他事;要么你先做其他事情,过一会来看一次,看一看打印机有人用没,如果有人用,就继续做其他事,没人用就使用打印机

锁,在java层面是一个对象,多个线程加锁,是对同一个对象加锁,解锁也是

加锁

synchronized 关键字

使用方法:

  1. 静态方法:加锁整个方法,锁对象为类对象
public class Main {

    private static int x = 0;

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    add();
                }
            }
        };
        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        thread1.start();
        thread2.start();
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(x);
    }

    private synchronized static void add() {
        x++;
    }

}

add() 方法是静态方法

  1. 实例方法:加锁整个方法,锁对象为this
public class Main {

    private static int x = 0;

    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    increase();
                }
            }

            private synchronized void increase() {
                x++;
            }
        };
        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        thread1.start();
        thread2.start();
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(x);
    }

}

increase() 方法是实例方法

  1. 同步代码块
public class Main {

    private static int x = 0;
    
    public static void main(String[] args) throws InterruptedException {

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (o) {
                        x++;
                    }
                }
            }
            Object o = new Object();
        };
        Thread thread1 = new Thread(runnable, "线程1");
        Thread thread2 = new Thread(runnable, "线程2");
        thread1.start();
        thread2.start();
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(x);
    }

}

具体用法:

synchronized (锁对象) {
共享变量操作;
}

当然也可以对一个锁反复加锁

Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (o) {
                        synchronized (o) {
                            synchronized (o) {
                                synchronized (o) {
                                    x++;
                                }
                            }
                        }
                    }
                }
            }
            Object o = new Object();
};

volatile 关键字

不需要加锁即可保证线程安全性
用于保证了原子性的变量

读取变量值的操作:原子性保证了
修改变量值的操作:变量值不依赖共享变量,才保证了原子性
如:用常量给共享变量赋值,算保证了原子性

保证了线程的安全情况下,要尽可能的提高效率,这就需要我们加锁的时候要注意了,只加锁访问共享变量的代码,不访问共享变量的代码不要加锁

Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    synchronized (o) {
                        System.out.println("子线程操作变量");
                        x++;
                    }
                }
            }
            Object o = new Object();
};

上面这段代码,实际上
System.out.println(“子线程操作变量”);
不需要加锁

举个例子,就好比打印机在一个房间内,房间内有饮水机等其他设备,你在使用打印机的时候,就不需要对整个房间加锁,你使用的是打印机,并不是房间内的其他设备,万一有人想喝水了,还得等你用完打印机,这就很影响其他人,线程之间也是

所以加锁一定要锁对代码

以上是关于初识Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

Java核心知识---初识线程

java多线程系列1-初识多线程多线程4种实现方式

java多线程系列1-初识多线程多线程4种实现方式

Java多线程-初识并发问题

Java-多线程之初识线程

java学习笔记之初识多线程