27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)

Posted shawnyue-08

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)相关的知识,希望对你有一定的参考价值。

多线程

多线程实现方式一

Thread

Java虚拟机允许应用程序同时执行多个线程。

每个线程都有优先级,具有较高优先级的线程优先于优先级较低的线程执行。

当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为创建线程的优先级。

每个线程都有一个用于识别的名称,多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。

继承Thread类,重写Thread类的run方法

public class Thread implements Runnable { 
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
//继承Thread,重写run方法的逻辑为:不再调用target.run()方法,而是调用子类自定义的[线程要执行的方法]
//所以Thread() / Thread(String name)的构造方式,target为null,执行的是子类MyThread的run()
//Thread类继承Runnable接口,实现run方法,target代表Runnable实例对象,即创建Thread对象时传递的Runnable类型的参数
//Thread(Runnable target)的构造方式,就不需要重写run方法,target.run()调用Runnable实现类的run逻辑

Thread类的常用方法

方法名 描述
public final String getName()【final修饰方法,子类不允许重写】 获取当前线程的名称
public final int getPriority() 获取当前线程的优先级
public final void setName(String name) 设置当前线程的名称
public final void setPriority(int newPriority) 设置当前线程优先级
public static native Thread currentThread(); 获取当前执行的线程
public final static int MIN_PRIORITY = 1; 最低优先级
public final static int NORM_PRIORITY = 5; 默认优先级
public final static int MAX_PRIORITY = 10; 最高优先级
package org.westos.demo;


/**
 * @author lwj
 * @date 2020/5/31 13:53
 */
public class MyTest {
    public static void main(String[] args) {
        System.out.println("主线程的main方法开始执行");
        System.out.println(Thread.currentThread().getName());
        //获取当前正在执行的线程对象Thread.currentThread()
        //main
        System.out.println(Thread.currentThread().getPriority());
        //获取当前正在执行的线程的优先级
        //5

        //创建一个线程,1、继承Thread类,重写run方法
        MyThread myThread = new MyThread();
        myThread.setName("myThread-0");
        //设置线程的名称
        System.out.println(myThread.getName());
        //获取线程的名称
        //myThread-0
        System.out.println(myThread.getPriority());
        //获取线程的优先级
        //5
        myThread.setPriority(10);
        //设置线程的优先级
        System.out.println(myThread.getPriority());
        //10
        myThread.start();
        //启动线程,同一线程不要重复启动


        //创建另一个线程
        MyThread myThread1 = new MyThread();
        myThread1.setName("myThread-1");
        System.out.println(myThread1.getName());
        //myThread-1
        System.out.println(myThread1.getPriority());
        //5
        myThread1.start();


        /*
        两个线程轮流被操作系统调度,cpu执行,具有随机性
         */
        
        System.out.println("主线程的main方法执行完毕");
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        //run方法:该线程执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("线程" + this.getName() + ":" + i);
        }
    }
}

线程并不一定立即执行,由操作系统进行调度。

package org.westos.demo;

/**
 * @author lwj
 * @date 2020/6/1 16:33
 */
public class MyTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("myThread-0");
        myThread.setPriority(Thread.MAX_PRIORITY);
        myThread.start();
    }
}

class MyThread extends Thread {
    public MyThread() {}

    public MyThread(String name) {
        super(name);
        //MyThread子类不能继承父类Thread的构造方法,但是可以使用super关键字调用父类的构造方法
    }

    @Override
    public void run() {
        System.out.println("线程" + this.getName() + this.getPriority());
        //MyThread继承Thread,自动继承Thread类的getName()和getPriority()
        //线程myThread-010
    }
}

缺陷:单继承具有局限性。

多线程实现方式二

实现Runnable接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
package org.westos.demo2;

/**
 * 实现Runnable接口
 * @author lwj
 * @date 2020/6/1 20:01
 */
public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable, "A线程");
        //构造方法:Thread(Runnable runnableImpl)
        //Thread(Runnable target, String name)

        Thread thread1 = new Thread(myRunnable, "B线程");
		
        //虽然是一个相同的任务(对象),但是两个线程在执行run方法时,都会有自己的内存空间来执行for循环,来存储变量i,所以两个变量i的内存地址不同,那么就是两次的0-19
        
        thread.start();
        //开启线程
        thread1.start();
    }
    
    /*
    B线程---0
    B线程---1
    B线程---2
    B线程---3
    B线程---4
    A线程---0
    A线程---1
    A线程---2
    B线程---5
    B线程---6
    B线程---7
    B线程---8
    B线程---9
    A线程---3
    A线程---4
    A线程---5
    A线程---6
    A线程---7
    A线程---8
    A线程---9
    
    每个线程都有自己的PC寄存器/程序计数器,还有自己的虚拟机栈内存,存储自己线程的方法调用
     */
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

优点:避免了单继承的局限性,方便同一个对象被多个线程使用。

多线程实现方式三

实现Callable接口

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

相较于实现Runnable接口的方式,方法有返回值,并且可以抛出异常。

这种方式需要 FutureTask 实现类的支持(包装Callable),用于接收运算结果。FutureTask 是Runnable接口的实现类,Thread(Runnable target)来接收FutureTask实例。

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}
package org.westos.demo2;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现Callable接口
 * @author lwj
 * @date 2020/6/1 20:18
 */
public class MyTest2 {
    public static void main(String[] args) {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable(100));
        Thread thread = new Thread(futureTask);
        //Thread(Runnable target)
        thread.start();

        try {
            Integer integer = futureTask.get();
            //FutureTask<V>用于接收运算结果
            System.out.println(integer);
            //5050
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyCallable implements Callable<Integer> {
    private int num;

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
        }
        return sum;
    }
}

Runnable和Callable的区别

  • Runnable,让线程来执行run方法,没有返回值,如果run方法内部有异常,那么不能抛出异常,因为父类接口方法声明中没有抛出,所以只能try-catch;
  • Callable,让线程来执行call()方法,有返回值,而且call方法可以抛出异常。

Thread常见方法

线程休眠

Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),休眠结束后,由阻塞态进入就绪态;如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。

注意:在哪个线程里面调用sleep()方法就阻塞哪个线程。

package org.westos.demo;

/**
 * 线程休眠Thread.sleep,让当前线程休眠
 * @author lwj
 * @date 2020/6/1 16:49
 */
public class MyTest2 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行了");
        Thread.sleep(2000);
        //主线程休眠

        System.out.println("主线程休眠后的代码");

        MyThread3 myThread = new MyThread3("A线程");
        MyThread3 myThread1 = new MyThread3("B线程");

        myThread.start();
        myThread1.start();
    }
}

class MyThread3 extends Thread {
    public MyThread3() {}

    public MyThread3(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(1000);
                //实现Runnable接口的方式创建线程,在run方法中如果存在异常,那么不能向外抛出,只能在内部try-catch
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String name = Thread.currentThread().getName();
            System.out.println(name + "---" + i);
        }
    }
}

join

在A线程中调用B线程的join()方法,那么A线程必须等待B线程执行完毕后,才可以继续执行。

package org.westos.demo;

/**
 * join
 * @author lwj
 * @date 2020/6/1 18:52
 */
public class MyTest3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 myThread = new MyThread1("刘备");
        MyThread1 myThread1 = new MyThread1("关羽");
        MyThread1 myThread2 = new MyThread1("张飞");
        myThread.start();
        myThread.join();
        //此时进程中只有main线程和myThread线程,在main线程中调用myThread.join()方法,那么直到myThread线程执行完毕后,main线程才会继续执行
        myThread1.start();
        myThread1.join();
        //此时只有main线程和myThread1线程,当myThread1线程执行完毕后,main线程才会继续执行
        myThread2.start();
        myThread2.join();
        //同理,当myThread2线程执行完毕后,main线程才会继续执行

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

class MyThread1 extends Thread {
    public MyThread1() {}

    public MyThread1(String name) {
        super(name);
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println(name + "---" + i);
        }
    }
}

案例2

package org.westos.demo;

/**
 * @author lwj
 * @date 2020/6/4 15:53
 */
public class JoinTest implements Runnable{
    public static int a = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            a += 1;
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new JoinTest());
        thread.start();
        //线程启动,需要准备资源,等待CPU的调度,而且和main线程是处于并发执行的状态,所以存在一种情况,main线程执行完毕了,thread线程才开始执行
        System.out.println(a);
        //0
    }
}
package org.westos.demo;

/**
 * @author lwj
 * @date 2020/6/4 15:53
 */
public class JoinTest implements Runnable{
    public static int a = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            a += 1;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinTest());
        thread.start();
        thread.join();
        //在main线程中调用thread.join方法,那么main线程必须等待thread线程执行完毕后才可以继续执行
        System.out.println(a);
        //5
    }
}

yield

线程让步

yield()的作用是让步。它能让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到"运行状态"继续运行!

package org.westos.demo;

/**
 * 线程礼让yield
 * @author lwj
 * @date 2020/6/4 16:07
 */
public class MyTest5 {
    public static void main(String[] args) {
        MyThread4 thread = new MyThread4("A线程");
        MyThread4 thread1 = new MyThread4("B线程");
        thread.start();
        thread1.start();
    }
    
    /*
    ...
    A线程---0
    B线程---3
    A线程---1
    B线程---4
    A线程---2
    B线程---5
    A线程---3
    B线程---6
    A线程---4
    B线程---7
    A线程---5
    B线程---8
    A线程---6
    B线程---9
    A线程---7
    B线程---10
    A线程---8
    B线程---11
    A线程---9
    B线程---12
    A线程---10
    B线程---13
    A线程---11
    B线程---14
    A线程---12
    B线程---15
    A线程---13
    B线程---16
    A线程---14
    B线程---17
    A线程---15
    B线程---18
    A线程---16
    B线程---19
     */
}

class MyThread4 extends Thread {
    public MyThread4() {}

    public MyThread4(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
            Thread.yield();
        }
    }
}

守护线程

Java用户线程和守护线程

  • 用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束;守护线程是用来辅助用户线程的,如公司的员工和保安,各司其职,当员工都离开后,保安自然下班了。
  • 必须在线程启动之前调用对象的setDaemon(true)方法。
  • 还有就是经典坦克游戏,当你要守护的老鹰4掉了,坦克(守护线程)也就没有存在的必要了。
package org.westos.demo;

/**
 * 守护线程
 * @author lwj
 * @date 2020/6/1 19:14
 */
public class MyTest4 {
    public static void main(String[] args) {
        //更改主线程的name
        Thread.currentThread().setName("刘备");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }

        MyThread2 myThread = new MyThread2("关羽");
        MyThread2 myThread1 = new MyThread2("张飞");
        myThread.setDaemon(true);
        myThread1.setDaemon(true);
        //设置为守护线程
        //当主线程刘备消亡后,守护线程也要消亡
        myThread.start();
        myThread1.start();


        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
        
        
        /*
        刘备---0
        刘备---1
        刘备---2
        刘备---3
        刘备---4
        刘备---5
        刘备---6
        刘备---7
        刘备---8
        刘备---9
        刘备线程执行完毕
        张飞---0
        关羽---0
        关羽---1
        关羽---2
        关羽---3
        关羽---4
        关羽---5
         */
    }
}

class MyThread2 extends Thread {
    public MyThread2() {}

    public MyThread2(String name) {
        super(name);
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println(name + "---" + i);
        }
    }
}

创建守护线程

  • 在线程启动之前调用Thread对象的setDaemon(true),设置该线程为守护线程
  • 在守护线程中创建的新线程也是守护线程

线程停止

package org.westos.demo;

/**
 * stop
 * @author lwj
 * @date 2020/6/4 16:29
 */
public class MyTest6 {
    public static void main(String[] args) throws InterruptedException {
        MyThread5 thread = new MyThread5("A线程");
        thread.start();
        //让主线程休眠2ms,给thread线程一些执行时间
        Thread.sleep(2);
        thread.stop();
        //中断线程,已过时的方法
    }

    /*
    A线程---0
    ...
    A线程---90左右
     */
}

class MyThread5 extends Thread {
    public MyThread5() {}

    public MyThread5(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

interrupt():清除阻塞,打断线程的阻塞状态,当线程调用wait()、sleep(long mills)方法后,由运行态变为阻塞态。

package org.westos.demo2;

/**
 * interrupt
 * @author lwj
 * @date 2020/6/4 16:48
 */
public class MyTest3 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.setName("A线程");
        myThread.start();

        Thread.sleep(2);
        //主线程休眠保证myThread已经启动

        myThread.interrupt();
        //清除myThread线程的阻塞状态
        //触发了sleep方法的InterruptedException异常,进入catch块打印异常的堆栈信息

        /*
        java.lang.InterruptedException: sleep interrupted
            at java.lang.Thread.sleep(Native Method)
            at org.westos.demo2.MyThread.run(MyTest3.java:26)
        A线程---0
        A线程---1
        A线程---2
        A线程---3
        A线程---4
        A线程---5
        A线程---6
        A线程---7
        A线程---8
        A线程---9
         */
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //手动让线程由运行态变为阻塞状态
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

线程安全问题

继承Thread类实现

package org.westos.demo3;

/**
 * 售票案例
 * @author lwj
 * @date 2020/6/4 17:16
 */
public class MyTest {
    public static void main(String[] args) {
        CellThread a = new CellThread("A");
        CellThread b = new CellThread("B");
        CellThread c = new CellThread("C");
        a.start();
        b.start();
        c.start();

        /*
        在增加static修饰后,售票不是按照tickets--的顺序输出的,所以是需要在run方法里加同步,每个线程执行完run方法后再轮到下一个线程
         */
    }
}

class CellThread extends Thread {
    //private int tickets = 100;
    private static int tickets = 100;

    /*
    B窗口出售了第100张票
    C窗口出售了第100张票
    C窗口出售了第99张票
    C窗口出售了第98张票
    C窗口出售了第97张票
    C窗口出售了第96张票
    A窗口出售了第100张票

    第一次是由于在main方法中创建了三个线程,每个线程对象都拥有tickets属性为100,即三个窗口各售100张票
    所以需要将tickets属性作为三个线程共享的变量,添加static修饰
     */

    public CellThread() {}

    public CellThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (true) {
            if (tickets >= 1) {
                //只有在余票大于等于1张时才可以卖票
                System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
            } else {
                break;
            }
        }
    }
}

实现Runnable接口实现

package org.westos.demo3;

/**
 * 售票案例
 * @author lwj
 * @date 2020/6/4 19:57
 */
public class MyTest2 {
    public static void main(String[] args) {
        /*Thread a = new Thread(new CellRunnable(), "a");
        Thread b = new Thread(new CellRunnable(), "b");
        Thread c = new Thread(new CellRunnable(), "c");*/


        /*
        同样的问题,三个线程,分别执行三个不同的任务Runnable对象,那么分别都拥有tickets=100的属性
         */

        CellRunnable cellRunnable = new CellRunnable();
        Thread a = new Thread(cellRunnable, "a");
        Thread b = new Thread(cellRunnable, "b");
        Thread c = new Thread(cellRunnable, "c");
        a.start();
        b.start();
        c.start();

        /*
        三个线程执行一个Runnable任务之后,那么只有一个对象,所以任何一个线程对tickets属性的修改都会影响其他线程
        而当继承Thread的方式时,是因为有三个对象,所以tickets变量必须设置为静态,多个对象共享数据
         */
    }
}

class CellRunnable implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets >= 1) {
                System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
            } else {
                break;
            }
        }
    }
}

线程安全问题

package org.westos.demo3;

/**
 * 模拟实际售票场景
 * @author lwj
 * @date 2020/6/4 20:07
 */
public class MyTest3 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread a = new Thread(myRunnable, "a");
        Thread b = new Thread(myRunnable, "b");
        Thread c = new Thread(myRunnable, "c");
        a.start();
        b.start();
        c.start();
    }
}

class MyRunnable implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets >= 1) {
                try {
                    Thread.sleep(50);
                    //判断有票之后
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "窗口出售了第" + tickets-- + "张票");
            } else {
                break;
            }
        }
    }
}
  • c窗口出售了第0张票
  • 卖出同一张票
    • b窗口出售了第1张票
    • a窗口出售了第1张票

以上是关于27多线程(多线程的三种实现方式Thread线程类的常见方法线程安全问题)的主要内容,如果未能解决你的问题,请参考以下文章

JAVA多线程实现的三种方式

JAVA多线程实现的三种方式

Java多线程实现的三种方式

JAVA多线程实现的三种方式

Java 多线程实现的三种方式

AJPFX关于JAVA多线程实现的三种方式