前言
在拥有多核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; } }
---------
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()); } } } }
--------------
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()); } } } }
-----------------
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(); } }
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()); } }
--------------
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); } } }
---------------------
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(); } } }
------------------
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(); } }
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(); } } }
参考