多线程相关内容总结(Java)

线程的创建方式(四个)

方式一

创建Thread类的子类并重写run方法,看demo

package Threads;

public class WayOne extends Thread {

    public static void main(String[] args) {
        Thread thread1=new WayOne("1");
        Thread thread2=new WayOne("2");
        thread1.start();
        thread2.start();
    }

    public WayOne(String name){
        this.setName(name);
    }

    @Override
    public void run() {
        while(true){
            System.out.println("线程"+getName()+"在执行");
        }
    }
}

输出:

D:\jdk8\bin\java.exe -javaagent:D:\IDEA2018\lib\idea_rt.jar=59877:D:\IDEA2018\bin -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\lanjie\out\production\lanjie Threads.WayOne
线程2在执行
线程1在执行
线程2在执行
线程1在执行
线程2在执行
线程1在执行
线程2在执行
线程1在执行
线程1在执行
线程2在执行
线程2在执行
线程1在执行

Process finished with exit code -1

方式一创建线程我们需要记住:

  1. Thread是lang包下的

方式二

实现Runnable接口,看demo

package Threads;

public class WayTwo implements Runnable {

    public static void main(String[] args) {
        Thread thread1=new Thread(new WayTwo(),"1");
        thread1.start();
        Thread thread2=new Thread(new WayTwo(),"2");
        thread2.start();
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"在执行");
        }
    }
}

方式二创建线程我们需要记住两个知识点

  1. Runnable里面只有一个抽象方法run()
  2. 使用实现Runnable方式创建线程,将设置线程任务与线程的启动进行了分离,达到了一定的解耦目的

方式三

通过Callable和FutureTask来创建,看demo

package Threads;

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

public class WayThree implements Callable {

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

        Runnable futureTask1=new FutureTask<>(new WayThree());
        Thread thread1=new Thread(futureTask1,"1");
        thread1.start();
        String res=(String)((FutureTask) futureTask1).get();
        System.out.println(res);
        Runnable futureTask2=new FutureTask<>(new WayThree());
        Thread thread2=new Thread(futureTask2,"2");
        thread2.start();
        String res2=(String)((FutureTask) futureTask2).get();
        System.out.println(res2);
    }

    @Override
    public Object call() throws Exception {
        return "线程"+Thread.currentThread().getName()+"返回值";
    }
}

实现Callable接口,重写call方法,并用FutureTask去接收(注:FutureTask是Runnable的子类)
方式三创建线程我们需要记住的知识点

  1. 可以有返回值
  2. 可以抛出异常
  3. Callable来自java.util.concurrent包(并发包,简称JUC)

方式四

通过线程池创建,看demo

package Threads;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WayFour implements Runnable {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for(int i=0;i<10;i++){
            Runnable runnable=new WayFour();
            executorService.execute(runnable);
        }
        //关闭线程池
        executorService.shutdown();
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程"+Thread.currentThread().getName()+"在执行");
        }
    }
}

输出:

D:\jdk8\bin\java.exe -javaagent:D:\IDEA2018\lib\idea_rt.jar=57826:D:\IDEA2018\bin -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\lanjie\out\production\lanjie Threads.WayFour
线程pool-1-thread-4在执行
线程pool-1-thread-3在执行
线程pool-1-thread-5在执行
线程pool-1-thread-1在执行
线程pool-1-thread-2在执行
线程pool-1-thread-5在执行
线程pool-1-thread-3在执行
线程pool-1-thread-4在执行
线程pool-1-thread-1在执行
线程pool-1-thread-2在执行
线程pool-1-thread-3在执行
...

Process finished with exit code -1

方式四我们需要记住的知识点:

  1. 通过线程池创建线程可降低资源消耗,提高响应速度
  2. 方便管理,可控最大并发

不能重复start一个线程类

看demo

package Threads;

public class WayOne extends Thread {

    public static void main(String[] args) {
        Thread thread1=new WayOne("1");
        Thread thread2=new WayOne("2");
        thread1.start();
        thread1.start();
    }

    public WayOne(String name){
        this.setName(name);
    }

    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程"+getName()+"在执行");
        }
    }
}

输出:

D:\jdk8\bin\java.exe -javaagent:D:\IDEA2018\lib\idea_rt.jar=54413:D:\IDEA2018\bin -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\lanjie\out\production\lanjie Threads.WayOne
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.start(Thread.java:708)
	at Threads.WayOne.main(WayOne.java:9)
线程1在执行
线程1在执行
...

Process finished with exit code -1

通过demo我们可以看到,重复启动一个线程类会抛出IllegalThreadStateException异常,我们从代码层面分析一下这是为什么
首先Thread类中维护着一个threadStatus 变量,代表着线程状态

//volatie关键字后面会详细写,莫急
private volatile int threadStatus = 0;

当我们新建一个线程类的时候,threadStatus 被初始化为0,代表NEW状态,当我们启动线程的时候,会首先检查threadStatus的状态,源码如下

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        //会首先判断是否为0(是否为NEW状态)
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
private native void start0();

注意:不光是start方法会检测threadStatus 的状态,start0方法也会检测threadStatus 的状态(可以通过反射来测试)

线程安全问题

线程安全问题的引出(卖票问题)

假设一个电影院有四个售票窗口同时售票,我们编程实现一下这个场景,看demo

package Threads;

public class Ticket implements Runnable {

    public int tickets;

    public static void main(String[] args) {
        Ticket ticket=new Ticket(100);
        Thread thread=new Thread(ticket,"1");
        Thread thread2=new Thread(ticket,"2");
        Thread thread3=new Thread(ticket,"3");
        Thread thread4=new Thread(ticket,"4");
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public Ticket(int tickets){
        this.tickets=tickets;
    }

    @Override
    public void run() {
        while(tickets>0){
            System.out.println("售票员"+Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tickets-=1;
        }
    }
}

D:\jdk8\bin\java.exe -javaagent:D:\IDEA2018\lib\idea_rt.jar=52696:D:\IDEA2018\bin -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\lanjie\out\production\lanjie Threads.Ticket
售票员1正在出售第100张票
售票员4正在出售第100张票
售票员3正在出售第100张票
售票员2正在出售第100张票
售票员3正在出售第99张票
售票员1正在出售第99张票
售票员4正在出售第99张票
售票员2正在出售第99张票
售票员3正在出售第97张票
售票员2正在出售第97张票
售票员4正在出售第97张票
售票员1正在出售第97张票
售票员1正在出售第96张票
售票员3正在出售第95张票
售票员2正在出售第96张票
售票员4正在出售第96张票
售票员2正在出售第93张票
售票员3正在出售第93张票
售票员1正在出售第93张票
售票员4正在出售第93张票
售票员3正在出售第92张票
售票员1正在出售第92张票
售票员2正在出售第92张票
售票员4正在出售第92张票
售票员2正在出售第91张票
售票员3正在出售第91张票
售票员1正在出售第91张票
售票员4正在出售第91张票
售票员1正在出售第90张票
售票员4正在出售第90张票
售票员3正在出售第90张票
售票员2正在出售第90张票
售票员3正在出售第89张票
售票员1正在出售第89张票
售票员4正在出售第89张票
售票员2正在出售第89张票
售票员4正在出售第88张票
售票员3正在出售第88张票
售票员2正在出售第88张票
售票员1正在出售第88张票
售票员2正在出售第87张票
售票员1正在出售第87张票
售票员3正在出售第87张票
售票员4正在出售第87张票
售票员2正在出售第86张票
售票员4正在出售第86张票
售票员1正在出售第86张票
售票员3正在出售第86张票
售票员3正在出售第84张票
售票员4正在出售第84张票
售票员1正在出售第84张票
售票员2正在出售第84张票
售票员3正在出售第82张票
售票员4正在出售第82张票
售票员2正在出售第82张票
售票员1正在出售第82张票
售票员3正在出售第80张票
售票员4正在出售第80张票
售票员2正在出售第80张票
售票员1正在出售第80张票
售票员4正在出售第78张票
售票员2正在出售第78张票
售票员3正在出售第78张票
售票员1正在出售第78张票
售票员4正在出售第77张票
售票员2正在出售第77张票
售票员3正在出售第77张票
售票员1正在出售第77张票
售票员3正在出售第76张票
售票员2正在出售第75张票
售票员4正在出售第75张票
售票员1正在出售第76张票
售票员1正在出售第72张票
售票员3正在出售第72张票
售票员4正在出售第72张票
售票员2正在出售第72张票
售票员1正在出售第68张票
售票员4正在出售第68张票
售票员3正在出售第68张票
售票员2正在出售第68张票
售票员2正在出售第66张票
售票员3正在出售第66张票
售票员1正在出售第66张票
售票员4正在出售第66张票
售票员1正在出售第64张票
售票员4正在出售第64张票
售票员2正在出售第64张票
售票员3正在出售第64张票
售票员3正在出售第62张票
售票员2正在出售第61张票
售票员1正在出售第61张票
售票员4正在出售第61张票
售票员3正在出售第58张票
售票员2正在出售第57张票
售票员4正在出售第58张票
售票员1正在出售第57张票
售票员1正在出售第56张票
售票员2正在出售第53张票
售票员3正在出售第53张票
售票员4正在出售第53张票
售票员2正在出售第51张票
售票员3正在出售第51张票
售票员1正在出售第51张票
售票员4正在出售第51张票
售票员1正在出售第49张票
售票员4正在出售第49张票
售票员3正在出售第49张票
售票员2正在出售第49张票
售票员3正在出售第47张票
售票员4正在出售第45张票
售票员1正在出售第45张票
售票员2正在出售第45张票
售票员1正在出售第43张票
售票员4正在出售第43张票
售票员3正在出售第43张票
售票员2正在出售第43张票
售票员1正在出售第42张票
售票员4正在出售第42张票
售票员2正在出售第42张票
售票员3正在出售第42张票
售票员4正在出售第40张票
售票员2正在出售第40张票
售票员1正在出售第40张票
售票员3正在出售第40张票
售票员2正在出售第39张票
售票员4正在出售第37张票
售票员1正在出售第37张票
售票员3正在出售第37张票
售票员3正在出售第35张票
售票员4正在出售第33张票
售票员1正在出售第35张票
售票员2正在出售第33张票
售票员1正在出售第29张票
售票员3正在出售第29张票
售票员2正在出售第29张票
售票员4正在出售第29张票
售票员3正在出售第26张票
售票员4正在出售第26张票
售票员2正在出售第25张票
售票员1正在出售第25张票
售票员2正在出售第24张票
售票员1正在出售第24张票
售票员4正在出售第24张票
售票员3正在出售第24张票
售票员1正在出售第22张票
售票员3正在出售第22张票
售票员4正在出售第22张票
售票员2正在出售第22张票
售票员2正在出售第19张票
售票员1正在出售第18张票
售票员4正在出售第19张票
售票员3正在出售第19张票
售票员3正在出售第15张票
售票员2正在出售第15张票
售票员4正在出售第15张票
售票员1正在出售第15张票
售票员3正在出售第13张票
售票员2正在出售第13张票
售票员4正在出售第13张票
售票员1正在出售第13张票
售票员2正在出售第10张票
售票员4正在出售第10张票
售票员3正在出售第10张票
售票员1正在出售第10张票
售票员4正在出售第7张票
售票员3正在出售第7张票
售票员1正在出售第7张票
售票员2正在出售第7张票
售票员2正在出售第5张票
售票员1正在出售第5张票
售票员4正在出售第5张票
售票员3正在出售第5张票
售票员4正在出售第3张票
售票员2正在出售第1张票
售票员3正在出售第3张票
售票员1正在出售第1张票

Process finished with exit code 0

我们从结果就可以看出,有些票被重复卖出去了,有些票没有被卖就跳过去了,这显然是不合理的!!!此时就产生了线程安全问题。

分析多线程产生安全问题的原因

以上述卖票为栗,比如说卖出第100张票四次,产生的原因为当线程1将第100张票卖出去后,还没来得及将票数减一,就被线程2夺去了CPU,线程2也卖了一次第100张票一次同样没来得及减去票数,就被其他线程抢占了CPU…再比如说第23张票没有被卖,产生的原因为当线程1卖掉第24张票,还没来的及减票数就被线程2抢去了CPU,线程2也卖了一次第24张票,没来的及减去票数就被抢去了CPU,当线程1拥有CPU后,从上次程序停止的位置接着执行,将票数减1,刚减完就被线程2抢了CPU,同理,线程2也将票数减1,此时第23张票就被跳过了…
线程安全问题是需要重视的。可能卖错票的影响十分小,但是如果支付宝,微信支付等等国民级应用出现线程安全问题时,它的影响就十分大了!
我们可以总结一下:线程安全问题的产生是有条件的,只有在多个线程操作同一个数据时才会产生线程安全问题

线程安全问题的解决方法

方法一

使用同步代码块,看demo

package Threads;

public class Ticket implements Runnable {

    public int tickets;

    public static void main(String[] args) {
        Ticket ticket = new Ticket(100);
        Thread thread = new Thread(ticket, "1");
        Thread thread2 = new Thread(ticket, "2");
        Thread thread3 = new Thread(ticket, "3");
        Thread thread4 = new Thread(ticket, "4");
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public Ticket(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        while (tickets>0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) {
                if (tickets > 0) {
                    System.out.println("售票员" + Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets -= 1;
                }
            }
        }
    }
}

输出:

D:\jdk8\bin\java.exe -javaagent:D:\IDEA2018\lib\idea_rt.jar=59877:D:\IDEA2018\bin -Dfile.encoding=UTF-8 -classpath D:\jdk8\jre\lib\charsets.jar;D:\jdk8\jre\lib\deploy.jar;D:\jdk8\jre\lib\ext\access-bridge-64.jar;D:\jdk8\jre\lib\ext\cldrdata.jar;D:\jdk8\jre\lib\ext\dnsns.jar;D:\jdk8\jre\lib\ext\jaccess.jar;D:\jdk8\jre\lib\ext\jfxrt.jar;D:\jdk8\jre\lib\ext\localedata.jar;D:\jdk8\jre\lib\ext\nashorn.jar;D:\jdk8\jre\lib\ext\sunec.jar;D:\jdk8\jre\lib\ext\sunjce_provider.jar;D:\jdk8\jre\lib\ext\sunmscapi.jar;D:\jdk8\jre\lib\ext\sunpkcs11.jar;D:\jdk8\jre\lib\ext\zipfs.jar;D:\jdk8\jre\lib\javaws.jar;D:\jdk8\jre\lib\jce.jar;D:\jdk8\jre\lib\jfr.jar;D:\jdk8\jre\lib\jfxswt.jar;D:\jdk8\jre\lib\jsse.jar;D:\jdk8\jre\lib\management-agent.jar;D:\jdk8\jre\lib\plugin.jar;D:\jdk8\jre\lib\resources.jar;D:\jdk8\jre\lib\rt.jar;D:\lanjie\out\production\lanjie Threads.Ticket
售票员1正在出售第100张票
售票员4正在出售第99张票
售票员2正在出售第98张票
售票员3正在出售第97张票
售票员2正在出售第96张票
售票员1正在出售第95张票
售票员4正在出售第94张票
售票员3正在出售第93张票
售票员1正在出售第92张票
售票员2正在出售第91张票
售票员3正在出售第90张票
售票员4正在出售第89张票
售票员1正在出售第88张票
售票员4正在出售第87张票
售票员3正在出售第86张票
售票员2正在出售第85张票
售票员2正在出售第84张票
售票员3正在出售第83张票
售票员4正在出售第82张票
售票员1正在出售第81张票
售票员1正在出售第80张票
售票员3正在出售第79张票
售票员2正在出售第78张票
售票员4正在出售第77张票
售票员2正在出售第76张票
售票员1正在出售第75张票
售票员3正在出售第74张票
售票员4正在出售第73张票
售票员2正在出售第72张票
售票员4正在出售第71张票
售票员1正在出售第70张票
售票员3正在出售第69张票
售票员1正在出售第68张票
售票员4正在出售第67张票
售票员2正在出售第66张票
售票员3正在出售第65张票
售票员2正在出售第64张票
售票员3正在出售第63张票
售票员1正在出售第62张票
售票员4正在出售第61张票
售票员3正在出售第60张票
售票员2正在出售第59张票
售票员1正在出售第58张票
售票员4正在出售第57张票
售票员1正在出售第56张票
售票员2正在出售第55张票
售票员4正在出售第54张票
售票员3正在出售第53张票
售票员1正在出售第52张票
售票员3正在出售第51张票
售票员2正在出售第50张票
售票员4正在出售第49张票
售票员1正在出售第48张票
售票员4正在出售第47张票
售票员3正在出售第46张票
售票员2正在出售第45张票
售票员3正在出售第44张票
售票员1正在出售第43张票
售票员4正在出售第42张票
售票员2正在出售第41张票
售票员3正在出售第40张票
售票员1正在出售第39张票
售票员4正在出售第38张票
售票员2正在出售第37张票
售票员4正在出售第36张票
售票员1正在出售第35张票
售票员2正在出售第34张票
售票员3正在出售第33张票
售票员3正在出售第32张票
售票员4正在出售第31张票
售票员2正在出售第30张票
售票员1正在出售第29张票
售票员2正在出售第28张票
售票员4正在出售第27张票
售票员3正在出售第26张票
售票员1正在出售第25张票
售票员4正在出售第24张票
售票员2正在出售第23张票
售票员3正在出售第22张票
售票员1正在出售第21张票
售票员1正在出售第20张票
售票员4正在出售第19张票
售票员2正在出售第18张票
售票员3正在出售第17张票
售票员3正在出售第16张票
售票员1正在出售第15张票
售票员2正在出售第14张票
售票员4正在出售第13张票
售票员1正在出售第12张票
售票员4正在出售第11张票
售票员2正在出售第10张票
售票员3正在出售第9张票
售票员1正在出售第8张票
售票员3正在出售第7张票
售票员2正在出售第6张票
售票员4正在出售第5张票
售票员4正在出售第4张票
售票员3正在出售第3张票
售票员2正在出售第2张票
售票员1正在出售第1张票

Process finished with exit code 0

由输出不难发现,卖票的线程安全问题被解决了
注意:同步代码块的锁对象必须是同一个对象(对象不能为空)

方法二

同步方法,看demo

package Threads;

public class Ticket implements Runnable {

    public int tickets;

    private String s="lock";

    public static void main(String[] args) {
        Ticket ticket = new Ticket(100);
        Thread thread = new Thread(ticket, "1");
        Thread thread2 = new Thread(ticket, "2");
        Thread thread3 = new Thread(ticket, "3");
        Thread thread4 = new Thread(ticket, "4");
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public Ticket(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        while (tickets>0){
            sale();
        }
    }

    public synchronized void sale(){
        if(tickets>0){
            System.out.println("售票员" + Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
            tickets -= 1;
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

注意:

  1. 同步方法锁对象是this
  2. 静态同步方法的锁对象是本类的Class属性,即Class对象,因为this是在对象被创建之后产生,而静态方法优先于对象产生

方式三

使用Lock锁,看demo

package Threads;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {

    public int tickets;

    Lock lock=new ReentrantLock();

    public static void main(String[] args) {
        Ticket ticket = new Ticket(100);
        Thread thread = new Thread(ticket, "1");
        Thread thread2 = new Thread(ticket, "2");
        Thread thread3 = new Thread(ticket, "3");
        Thread thread4 = new Thread(ticket, "4");
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public Ticket(int tickets) {
        this.tickets = tickets;
    }

    @Override
    public void run() {
        while(tickets>0){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                lock.lock();
                if(tickets>0){
                    System.out.println("售票员" + Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets -= 1;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}

注意:

  1. Lock是一个接口类,位于并发包,ReentrantLock是其实现类
  2. 在可能会出现安全问题的代码前获取锁,该代码后释放锁(释放锁一般写在finally块中)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值