Java多线程

前言

在拥有多核CPU的情况下,Java的线程可以被同时调度到不同的CPU上,同时执行;这是Python线程和Java线程的区别;

Java默认会有3个线程分别为main方法对应的线程(主线程)、处理异常的线程(异常处理线程)和垃圾收集器线程(GC线程);

其中异常处理线程可以中断主线程的执行;

无论使用什么语言,在并行和并发编程模式下都会遇到同类线争抢同1个资源这样的内部矛盾,也会遇到不同类线程如何完成同步通信这样的外部矛盾;

在Golang中使用锁解决Gorutine争抢资源的问题,使用channel解决Gorutine之间数据通信,注意这是在通过2种方法解决2种不同的问题;

如果多个线程之间要想通信就需要有1个共同的通信介质,这就又回到多个线程争抢同1个资源的问题上,所以锁是实现线程间通信的基础。 

一、线程创建

在Java中我们可以通过3种方式创建线程。

1.继承Thread类 

我们可以通过继承java.lang.Thread类的方式开生成线程对象;

package Threads;

//1):定义一个类A继承于java.lang.Thread类.
class MusicThread extends Thread {
    //2):在A类中覆盖Thread类中的run方法.
    public void run() {
        //3):在run方法中编写需要执行的操作
        for (int i = 0; i < 50; i++) {
            System.out.println("播放音乐" + i);
        }
    }
}

class CreateThread01 {
    public static void main(String[] args) {
        for (int j = 0; j < 50; j++) {
            System.out.println("运行游戏" + j);
            if (j == 10) {
                //4):在main方法(线程)中,创建线程对象,并启动线程.
                MusicThread music = new MusicThread();
                music.start();
            }
        }
    }
}

1.2.设置和读取线程名称

(1)setName和getName

我们可以调用setName和getName方法,给线程设置名称以帮助我们区分线程。

package Threads;

//定义一个类A继承于java.lang.Thread类.
class MusicThread extends Thread {
    public void run() {
        for (int i = 0; i < 50; i++) {
            //4.获取子线程名称
            String Threadname = super.getName();
            System.out.println(Threadname + "播放音乐" + i);
        }
    }
}

class CreateThread01 {
    public static void main(String[] args) {
        for (int j = 0; j < 50; j++) {
            //1.主线程设置线程名称
            Thread.currentThread().setName("Game线程");
            //2.主线程获取线程名称
            System.out.println(Thread.currentThread().getName() + "运行游戏" + j);
            if (j == 10) {
                MusicThread music = new MusicThread();
                //3.设置子线程的名称
                music.setName("Muisc线程");
                music.start();
            }
        }
    }
}

(2)构造器设置线程名称

还可以在构造线程对象时,就给线程设置名称。

package Threads;

//定义一个类A继承于java.lang.Thread类.
class MusicThread extends Thread {
    public MusicThread(String name) {
        super(name); //2.调用父类的有参构造器
    }

    public void run() {
        for (int i = 0; i < 50; i++) {
            //3.获取子线程名称
            System.out.println(this.getName() + "播放音乐" + i);
        }
    }
}

class CreateThread01 {
    public static void main(String[] args) {
        for (int j = 0; j < 50; j++) {
            System.out.println("运行游戏" + j);
            if (j == 2) {
                //1.利用构造器设置子线程名称
                MusicThread music = new MusicThread("音乐线程" + j);
                music.start();
            }
        }
    }
}

 1.3.抢火车票

package Threads;

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

//购票窗口
class TicketWindow extends Thread {
    //一共10张车票:多个线程对象共享10张票;
    static int ticketNum = 10;
    //加锁
    Lock lock = new ReentrantLock();

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

    @Override
    public void run() {
        //100个人抢10张票
        for (int i = 0; i <= 100; i++) {
            //判断是否售罄?
            if (ticketNum > 0) {
                lock.lock();
                ticketNum--;
                System.out.printf("我在%s买到了从北京到哈尔滨的第%s张车票。\n", super.getName(), ticketNum);
                lock.unlock();
            }

        }
    }
}


public class BuyTicket02 extends Thread {
    public static void main(String[] args) {
        TicketWindow t1 = new TicketWindow("窗口1");
        t1.start();
        TicketWindow t2 = new TicketWindow("窗口2");
        t2.start();
        TicketWindow t3 = new TicketWindow("窗口3");
        t3.start();
    }

}

2.实现Runnable接口

 我们可以通过实现Runabke接口,制造线程对象。

package Threads;

//实现Runnable接口
class TestThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}


public class CreateThread02 {
    public static void main(String[] args) {
        //创建子线程对象
        TestThread tt = new TestThread();
        Thread t = new Thread(tt, "子线程");
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }


    }

}

2.1.抢火车票

package Threads;

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

//实现Runnable接口
class TestThread implements Runnable {
    int ticketNumber = 10;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            lock.lock();
            if (ticketNumber > 0) {
                ticketNumber--;
                System.out.printf("我在%s抢到了北京到保定的第%d张票\n", Thread.currentThread().getName(), ticketNumber);

            }
            lock.unlock();


        }
    }
}


public class CreateThread02 {
    public static void main(String[] args) {
        //创建1个线程对象共享票和锁
        TestThread tt = new TestThread();
        //开启线程1
        Thread t1 = new Thread(tt, "窗口1");
        t1.start();
        //开启线程2
        Thread t2 = new Thread(tt, "窗口2");
        t2.start();
        //开启线程3
        Thread t3 = new Thread(tt, "窗口3");
        t3.start();


    }

}

3.实现Calable接口

以上两种创建线程的方式,都需要有1个run(),run方法的不足之处就是无法有返回值也无法抛出异常

package Threads;

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

class TestThread03 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(100);
    }
}

public class CreateThread03 {
    public static void main(String[] args) throws Exception {
        //定义1个线程对象
        TestThread03 tr = new TestThread03();
        FutureTask ft = new FutureTask(tr);
        Thread t3 = new Thread(ft);
        t3.start();

        //获取线程得到的返回值
        Object obj = ft.get();
        System.out.println(obj);


    }
}

4.线程的生命周期

 

二、线程的常用方法

线程创建完成之后,我们可以调用线程对象中封装的一些API对线程进行操作;

1.setPriority()

我们可以通过setPriority设置线程被CPU调度的优先级。

    * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

正常优先级 为5,最小优先级为1,最大优先级为10;

package Threads;

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

class Task02 extends Thread {
    @Override
    public void run() {
        for (int i = 20; i < 30; i++) {
            System.out.println("task02--" + i);
        }
    }
}

public class ThreadMthods {
    public static void main(String[] args) {
        Task01 t1 = new Task01();
        //设置线程1的优先级别为1
        t1.setPriority(1);
        t1.start();

        //设置线程2的优先级别为10
        Task02 t2 = new Task02();
        t1.setPriority(10);
        t2.start();
    }
}

2.join()

当一个线程调用了join方法之后,这个线程就会优先被执行;

当它执行结束后,CPU才可以去执行其他线程;

注意:线程必须先执行start()再执行join(),才能生效;

package Threads;


class Task03 extends Thread {
    Task03() {
    }

    Task03(String name) {
        super.setName(name);
    }

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


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 100; i++) {
            if (i == 6) {
                Task03 t3 = new Task03("子线程");
                t3.start();
                t3.join(); //半路杀出个程咬金

            }
            Thread.currentThread().setName("main线程");
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

3.sleep()

我们可以通过sleep()方法阻塞当前线程;

package Threads;

public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        System.out.println("开始");
        Thread.sleep(6000); //线程阻塞6秒,再进入就绪状态,在被CPU调度
        System.out.println("结束");
    }
}

4.setDaemon(true)

在古代皇帝死了,他的妃子通常会哭的很惨,因为接下来她也要殉葬;

在Python中子线程默认就是主线程的守护线程,一旦主线程提前结束,子线程即使没有结束也要强制其结束; 

而Java中的线程是跑在不同的CPU上的,主线程和子线程的地位是平等的;

在Java中,默认情况下即使主线程结束了,子线程也可以继续执行;

但是只要我们把子线程设置为守护线程,一旦主线程结束,子线程会立即结束;

package Threads;

class Task extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程" + i);
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        Task t1 = new Task();
        t1.setDaemon(true);//注意先设置守护线程,在启动该线程
        t1.start();
        //在Python中子线程默认就是主线程的守护线程,一旦主线程提前与子线程结束,子线程即使没有结束也要强制其结束;
        //而Java的线程可以跑在不同的CPU上,主线程和子线程是平等的;默认情况下即使主线程结束了,子线程也可以继续执行;
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程" + i);
        }
    }
}

5.stop()

我们可以通过调用stop方法,来结束当前线程;

package Threads;


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        for (int i = 1; i < 11; i++) {
            if (i == 7) {
                Thread.currentThread().stop();
            }
            System.out.println("主线程" + i);
        }
    }
}

三、线程安全问题(内部矛盾)

我这里所说的线程安全问题即 N个执行相同任务的线程,争抢同1个资源;

例如:100个人在北京西站去抢10张北京到哈尔滨的火车票;

在Java中我们可以通过同步代码块、静态同步方法、显示锁解决这种问题;

1.synchronized ()同步代码块

package Threads;


class TicketAgency extends Thread {
    static int tickets = 10;

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        //确保每个线程对象,使用的都是同一把锁。不要使用this
        synchronized ("zhanggen") {
            for (int i = 0; i <= 100; i++) {
                if (tickets > 0) {
                    System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", super.getName(), tickets--);
                }
            }
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

2.静态同步方法

public static synchronized
package Threads;


class TicketAgency extends Thread {
    static int tickets = 10;

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        //必须确保多个线程对象,使用的都是同1把锁。不要使用this
        for (int i = 0; i <= 100; i++) {
            buyTicket();
        }

    }

    //同步方法:增加static修饰符,确保锁住的不是this
    public static synchronized void buyTicket() {
        if (tickets > 0) {
            System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--);
        }
    }
}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

3.Lock类

synchronized是Java中的关键字,这个关键字的识别是依靠JVM来识别完成的,是虚拟机级别的;

在JKD1.5之后我们可以通过API级别的Lock显示锁(自己lock+unlock)实现同步,这种方式更加灵活。

package Threads;


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

class TicketAgency extends Thread {
    static int tickets = 10;
    //拿来一把锁
    Lock lock = new ReentrantLock(); //多态 接口=实现类

    TicketAgency() {
    }

    TicketAgency(String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            lock.lock();
            if (tickets > 0) {
                System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--);
            }
            lock.unlock();

        }

    }

}


public class ThreadMthods {
    public static void main(String[] args) throws Exception {
        TicketAgency agency01 = new TicketAgency("窗口1");
        agency01.start();
        TicketAgency agency02 = new TicketAgency("窗口2");
        agency02.start();
        TicketAgency agency03 = new TicketAgency("窗口3");
        agency03.start();

    }
}

四、线程同步通信问题(外部矛盾)

我这里所说的线程通信问题 即 2个执行不同任务的线程之间如何完成通信,和以上的线程安全问题不是同1个问题;

例如:生产者和消费者模式 A生产10件产品,B消费者来消费这10见商品,并保证供需平衡;

所以A和B阵营主内除了保证自身阵营内部的线程安全,还需要在A和B两个阵营之间建立1种顺序的通信机制;

 A阵营生产完了通知B阵营来消费,B阵营消费完了通知A阵营继续生产;

1.使用同步代码块

package zhanggen.com;

//商品类
public class Product {
    //品牌
    private String band;
    //名字
    private String name;

    public Product() {
    }

    //set和get方法
    public Product(String band, String name) {
        this.band = band;
        this.name = name;
    }


    public String getBand() {
        return band;
    }

    public String getName() {
        return name;
    }

    public void setBand(String band) {
        this.band = band;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Product

---------

package zhanggen.com;

public class Producer extends Thread {
    //生产的商品
    private Product p;

    public Producer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        //生产10个产品
        for (int i = 1; i <= 10; i++) {
            //利用同步代码块解决供需争抢的问题
            synchronized (p) {
                if (i % 2 == 0) {
                    //生产费列罗巧克力
                    p.setBand("巧克力");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    p.setName("费列罗巧克力");


                } else {
                    //生产哈尔滨啤酒
                    p.setBand("啤酒");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    p.setName("哈尔滨啤酒");

                }
                //将生产信息做1个打印
                System.out.printf("生产者生产出: %s品牌 的 %s\r\n", p.getBand(), p.getName());
            }

        }
    }
}
Producer.java

--------------

package zhanggen.com;

//消费者线程
public class Cousumer extends Thread {
    //共享商品
    private Product p;

    Cousumer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {//消费者消费10次
        //利用同步代码块解决供需争抢的问题
        synchronized (p) {
            for (int i = 1; i <= 10; i++) {
                System.out.printf("消费者消费了:%s的%s\n", p.getBand(), p.getName());
            }
        }

    }
}
Consumer.java

-----------------

package zhanggen.com;

public class Market {
    public static void main(String[] args) {
        //共享商品
        Product p = new Product();
        //创建生产者线程
        Producer producer = new Producer(p);

        //创建消费者
        Cousumer cousumer = new Cousumer(p);

        producer.start();
        cousumer.start();

    }
}
Market.java

2.使用同步方法

package zhanggen.com;

//商品类
public class Product {
    //品牌
    private String band;
    //名字
    private String name;

    public Product() {
    }

    //set和get方法
    public Product(String band, String name) {
        this.band = band;
        this.name = name;
    }


    public String getBand() {
        return band;
    }

    public String getName() {
        return name;
    }

    public void setBand(String band) {
        this.band = band;
    }

    public void setName(String name) {
        this.name = name;
    }

    //利用同步方法解决供需争抢的问题:要么生产!要么消费! 此时调用make锁住的就是谁
    public synchronized void make(int i) {
        if (i % 2 == 0) {
            //生产费列罗巧克力
            this.setBand("巧克力");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }

            this.setName("费列罗巧克力");


        } else {
            //生产哈尔滨啤酒
            this.setBand("啤酒");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.setName("哈尔滨啤酒");

        }
        //将生产信息做1个打印
        System.out.printf("生产者生产出: %s品牌 的 %s\r\n", this.getBand(), this.getName());

    }


    //消费
    public synchronized void cost() {
        System.out.printf("消费者消费:%s的%s\n", this.getBand(), this.getName());

    }

}
Product.java

--------------

package zhanggen.com;

public class Producer extends Thread {
    private Product p;

    public Producer(Product p) {
        this.p = p;
    }


    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            p.make(i);
        }
    }
}
Producer.java

---------------------

package zhanggen.com;

//消费者线程
public class Cousumer extends Thread {
    //共享商品
    private Product p;

    Cousumer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {//消费者消费10次
        for (int i = 1; i <= 10; i++) {
            p.cost();
        }


    }
}
Consumer.java

------------------

package zhanggen.com;

public class Market {
    public static void main(String[] args) {
        //共享商品
        Product p = new Product();
        //创建生产者线程
        Producer producer = new Producer(p);

        //创建消费者
        Cousumer cousumer = new Cousumer(p);

        producer.start();
        cousumer.start();

    }
}
Market.java

3.wait()和notify()

在Java对象中有2种池:锁池(synchrnized)和等待池(wait()、notify()、notifyAll())

如果1个线程对象调用了某个对象的wait()方法,那么该线程进入到该对象的等待池中(并且将锁释放出来,也就是让别的线程先执行。);

如果未来的某一刻,另外一个线程调用了同一个对象的notify()或者notifyAll()方法,那么等待池中的线程将被唤起,然后进入到对象的锁池里面去获取该对象的锁;

如果获得锁成功之后,该线程就会沿着wait()方法之后的继续执行。注意是沿着wait()方法之后。

wait()和notify()必须放在synchronized代码块或者由synchronized修饰的方法内;

package zhanggen.com;


//商品类
public class Product {
    //品牌
    private String band;
    //名字
    private String name;

    //生产和消费指示灯:0正在生产,1可消费
    boolean flag = false;

    public Product() {
    }

    //set和get方法
    public Product(String band, String name) {
        this.band = band;
        this.name = name;
    }


    public String getBand() {
        return band;
    }

    public String getName() {
        return name;
    }

    public void setBand(String band) {
        this.band = band;
    }

    public void setName(String name) {
        this.name = name;
    }

    //利用wait和notice进行线程通信,实现多线程同步执行;
    public synchronized void make(int i) {
        //如果消费线程正在消费,生产线程进入等待池。释放锁让给消费线程;
        if (flag == true) {
            try {
                wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (i % 2 == 0) {
            //生产费列罗巧克力
            this.setBand("巧克力");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }

            this.setName("费列罗");


        } else {
            this.setBand("啤酒");
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.setName("哈尔滨");

        }
        System.out.printf("生产者生产出%s,品牌为%s\r\n", this.getBand(), this.getName());
        flag = true;
        // 唤醒等待池中的1个消费线程,来消费。
        notify();
    }


    //利用wait和notice进行线程通信,实现多线程同步执行;
    public synchronized void cost() {
        //如果生产线程正在生产,消费线程进入等待词,让出锁给生产线程;
        if (flag == false) {
            try {
                wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.printf("消费者消费了%s,品牌为%s\n", this.getBand(), this.getName());
        flag = false;
        //唤醒等待池中的1个生产线程,去生产。
        notify();

    }

}

4.await

以上我们只是完成1个生产者线程和1个消费者线程之间1对1的顺序通信;

在JDK1.5之后我们可以使用await()和signal(),实现多个消费者线程 和多个生产者线程之间,多对多通信的顺序通信;

package zhanggen.com;


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

//商品类
public class Product {
    //品牌
    private String band;
    //名字
    private String name;

    //生产和消费指示灯:0正在生产,1可消费
    boolean flag = false;

    //声明1把Lock锁
    Lock lock = new ReentrantLock();
    //生产者的等待队列
    Condition producersCondition = lock.newCondition();
    //消费者的等待队列
    Condition consumersCondition = lock.newCondition();

    public Product() {
    }

    //set和get方法
    public Product(String band, String name) {
        this.band = band;
        this.name = name;
    }


    public String getBand() {
        return band;
    }

    public String getName() {
        return name;
    }

    public void setBand(String band) {
        this.band = band;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void make(int i) {
        //如果消费线程正在消费,生产线程进入生产者的等待池等待
        lock.lock();
        try {
            if (flag == true) {
                try {
                    //生产线程进入生产者的等待队列
                    producersCondition.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (i % 2 == 0) {
                this.setBand("巧克力");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                this.setName("费列罗");


            } else {
                this.setBand("啤酒");
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                this.setName("哈尔滨");

            }
            System.out.printf("生产者生产出%s,品牌为%s\r\n", this.getBand(), this.getName());
            flag = true;
            // 唤醒消费队列中1个消费线程,来消费。
            consumersCondition.signal();
        } finally {
            lock.unlock();
        }
    }


    public void cost() {
        lock.lock();

        try {
            if (flag == false) {
                try {
                    //消费线程进入消费者的等待队列
                    consumersCondition.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("消费者消费了%s,品牌为%s\n", this.getBand(), this.getName());
            flag = false;
            //唤醒生产者队列中的1个生产线程,去生产
            producersCondition.signal();

        } finally {
            lock.unlock();
        }
    }

}

参考

原文地址:https://www.cnblogs.com/sss4/p/15598507.html