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(); } }
四、线程间通信
五、线程的生命周期
参考