Java多线程


前言

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

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

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

一、线程创建

我们可以通过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 {
    @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.线程的生命周期

二、线程的常用方法

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);
        }
    }
}

三、线程安全

线程安全问题就是多个线程争抢同1个资源,我们可以通过加锁、队列再重回同步,解决这种问题;

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();

    }
}

四、线程间通信

五、线程的生命周期

参考