黑马Java进阶教程,全面剖析Java多线程编程,含抢红包、抽奖实战案例 ,现对学习到的内容做一个总结.
一.多线程的三种方式
1.继承Thread类
继承Thread类,重写run方法.这里重写构造方法是为了方便在初始化的时候快速地设置线程的名称,当然也可以在创建线程后使用setName方法.
1package mythread1;
2
3public class MyThread extends Thread{
4 public MyThread(String name) {
5 super(name);
6 }
7
8 public MyThread() {
9 super();
10 }
11
12 @Override
13 public void run() {
14 //写线程执行的代码
15 for (int i = 0; i < 100; i++) {
16 System.out.println(getName() + ": Hello World");
17 }
18 }
19}
20
使用线程的start方法启动线程.
1package mythread1;
2
3public class Main {
4 public static void main(String[] args) throws InterruptedException {
5 /*
6 多线程第一种启动方式
7 1.定义类继承Thread类
8 2.重写run方法
9 3.创建子类对象,并启动线程
10 */
11 //利用构造方法设置名字
12 MyThread t1 = new MyThread("one");
13 MyThread t2 = new MyThread("two");
14 //通过setName显式设置名字
15 //不显示地设置名字,它自己也有名字。
16// t1.setName("Thread 1");
17// t2.setName("Thread 2");
18 //开启线程
19 t1.start();
20 //本线程,就是执行这个方法的线程,也就是main
21// Thread.sleep(5000);
22// Thread main = Thread.currentThread();
23 t2.start();
24
25 }
26}
2.实现Runnable接口
自定义类实现Runnable接口,重写run方法
1package mythread2;
2
3public class MyThread implements Runnable {
4 @Override
5 public void run() {
6 for (int i = 0; i < 100; i++) {
7 //当前线程的对象
8// Thread t = Thread.currentThread();
9// System.out.println(Thread.currentThread().getName() + " :Hello World");
10 System.out.println(Thread.currentThread().getName() + i);
11 }
12 }
13}
14
创建好自定义类后,放入Thread类构造线程
setPriority是设置线程优先级,数目越高优先级越高
1package mythread2;
2
3public class Main {
4 public static void main(String[] args) {
5 /*
6 多线程第二种启动方式:
7 1.自己定义类实现runable接口
8 2.重写run方法
9 3.创建自己的类对象
10 4.创建Thread类对象,开启线程
11 */
12
13 //自己的类对象,其实就是多线程的任务
14 MyThread myThread = new MyThread();
15 //Thread
16 Thread t1 = new Thread(myThread,"线程1");
17 Thread t2 = new Thread(myThread,"线程2");
18 //设置名字以区分,这是Thread类的方法
19// t1.setName("Thread 1");
20// t2.setName("Thread 2");
21 System.out.println(t1.getPriority());
22 System.out.println(t2.getPriority());
23 //设置优先级,越高越能抢到cpu,这只是概率.
24 t1.setPriority(1);
25 t2.setPriority(10);
26
27 //开启
28 t1.start();
29 t2.start();
30 }
31}
32
3.实现Callable接口
自定义类实现Callable接口,重写Call方法.这个Call方法是有返回值的.
1package mythread3;
2
3import java.util.concurrent.Callable;
4
5public class MyCallable implements Callable<Integer> {
6 @Override
7 public Integer call() throws Exception {
8 int sum = 0;
9 for (int i = 0; i <= 100; i++) {
10 sum += i;
11 }
12 return sum;
13 }
14}
15
下面注释已经写得很清楚了,实例化实现Callable接口的类,然后放入FutureTask对象进行管理,再将FutureTask放入Thread进行初始化.
这种方式可以获取线程执行的返回值.
1package mythread3;
2
3import java.util.concurrent.ExecutionException;
4import java.util.concurrent.FutureTask;
5
6public class Main {
7 public static void main(String[] args) throws ExecutionException, InterruptedException {
8 /*
9 多线程第三种实现方式:
10 特点:可以获取到多线程运行的结果
11 1.创建类实现Callable接口
12 2.重写call,可以获取结果
13 3.创建实现接口的对象
14 4.创建FutureTask接口对象,管理多线程运行的结果
15 5.创建Thread对象
16 */
17 MyCallable mc = new MyCallable();
18 FutureTask<Integer> ft = new FutureTask<>(mc);
19 Thread t1 = new Thread(ft);
20 t1.start();
21
22 //获取结果
23 System.out.println(ft.get());
24
25 }
26}
27
二.守护线程,出让线程,插入线程
不太重要
1package mythread4;
2
3
4public class Main {
5 public static void main(String[] args) throws InterruptedException {
6// MyThread1 myThread1 = new MyThread1();
7// MyThread2 myThread2 = new MyThread2();
8// myThread1.setName("hime");
9// myThread2.setName("prince");
10//
11// //设置守护线程,当其他非守护线程结束后,守护线程陆续结束
12// //就是它自己不会运行完,慢慢地结束了.
13// myThread2.setDaemon(true);
14// myThread1.start();
15// myThread2.start();
16
17// MyThread1 t1 = new MyThread1();
18// MyThread1 t2 = new MyThread1();
19// t1.setName("线程1");
20// t2.setName("线程2");
21// t1.start();
22// t2.start();
23
24 MyThread1 t1 = new MyThread1();
25 t1.setName("另一个线程");
26 t1.start();
27 //插入线程
28 //把t线程,插入到当前线程(main线程)之前
29 //t1全都执行完,才该main执行
30 t1.join();
31
32 for (int i = 0; i < 10; i++) {
33 System.out.println("main线程" + "@" + i);
34 }
35
36 }
37}
38
1package mythread4;
2
3public class MyThread1 extends Thread{
4 @Override
5 public void run() {
6 for (int i = 0; i < 100; i++) {
7 System.out.println(getName() + "@" + i);
8 //出让线程
9 //出让cpu执行权,就是不会一直抢着,重写和其他线程继续抢夺,这样执行结果可能会均匀一点.
10// Thread.yield();
11 }
12 }
13}
14
三.线程安全(线程同步)
同步代码块
同步代码块用synchronized包裹,再括号之后需要放入一个唯一的锁对象,可以使用static修饰,也可以放入当前类的字节码文件
1package threadsafe;
2
3public class MyThread extends Thread{
4 static int ticket = 0;
5
6 //锁对象,一定是唯一的
7// static Object object = new Object();
8
9 public MyThread() {
10 super();
11 }
12
13 public MyThread(String name) {
14 super(name);
15 }
16
17 @Override
18 public void run() {
19 while (true){
20 //同步代码块
21 synchronized (MyThread.class){
22 if (ticket <= 100){
23 try {
24 Thread.sleep(100);
25 } catch (InterruptedException e) {
26 throw new RuntimeException(e);
27 }
28 ticket++;
29 System.out.println(getName() + "selling ticket " + ticket);
30 }else {
31 break;
32 }
33 }
34 }
35 }
36
37
38
39}
40
同步方法
也可以把同步代码块抽取出来形成一个方法,称为同步方法,其实就是用synchronized修饰的方法
同步方法:非静态的时候锁对象是this;静态的时候锁对象是当前类字节码文件
1package threadsafe;
2
3public class MyRunnable implements Runnable{
4 //这里不需要设置静态变量,因为只有一个实例.
5 int ticket = 0;
6
7 @Override
8 public void run() {
9 while (true){
10 if (method()) break;
11 }
12 }
13
14 //同步方法. 非静态的时候锁对象是this;静态的时候锁对象是当前类字节码文件
15 private synchronized boolean method() {
16 if (ticket < 100){
17 try {
18 Thread.sleep(100);
19 } catch (InterruptedException e) {
20 throw new RuntimeException(e);
21 }
22 ticket++;
23 System.out.println(Thread.currentThread().getName() + " selling ticket " + ticket);
24 }else {
25 return true;
26 }
27 return false;
28 }
29}
30
lock锁
创建一把静态锁, 然后在需要同步的代码块上使用lock方法, 在代码块结束时使用unlock方法.
1package threadsafe;
2
3import java.util.concurrent.locks.Lock;
4import java.util.concurrent.locks.ReentrantLock;
5
6public class MyLock extends Thread{
7 static int ticket = 0;
8
9 //多个对象共享同一把锁,加static
10 static Lock lock = new ReentrantLock();
11
12 //锁对象,一定是唯一的
13// static Object object = new Object();
14
15 public MyLock() {
16 super();
17 }
18
19 public MyLock(String name) {
20 super(name);
21 }
22
23 @Override
24 public void run() {
25 while (true){
26 //同步代码块
27 //synchronized (MyLock.class){
28 lock.lock();
29 try {
30 if (ticket <= 100){
31 Thread.sleep(100);
32 ticket++;
33 System.out.println(getName() + "selling ticket " + ticket);
34 }else {
35 break;
36 }
37 } catch (InterruptedException e) {
38 throw new RuntimeException(e);
39 } finally {
40 //锁一定会被释放
41 lock.unlock();
42 }
43 //}
44 }
45 }
46}
四. 生产者-消费者机制
生产者消费者模式是一个经典的多线程协作模式.
等待唤醒机制
桌子Desk.class(控制生产者消费者的执行):
1package waitandnotify1;
2
3public class Desk {
4 /*
5 控制生产者和消费者状态
6 */
7
8 //桌子上是否有面条,0表示没有,1表示有
9 public static int foodFlag = 0;
10
11 //总个数
12 public static int count = 10;
13
14 //锁
15 public static Object lock = new Object();
16}
17
消费者Foodie.class:
1package waitandnotify1;
2
3public class Foodie extends Thread{
4 @Override
5 public void run() {
6 /*
7 1.循环
8 2.同步代码块
9 3.判断共享数据是否到了末尾,到了末尾
10 4.没有到末尾,执行核心逻辑
11 */
12 while (true){
13 synchronized (Desk.lock){
14 if (Desk.count == 0){
15 break;
16 }else {
17 /*
18 判断是否有面条
19 没有则等待
20 有,吃,然后唤醒厨师,吃的数目减一,修改桌子状态
21 */
22 if(Desk.foodFlag == 0){
23 try {
24 Desk.lock.wait(); //当前线程和锁绑定,方便唤醒
25 } catch (InterruptedException e) {
26 throw new RuntimeException(e);
27 }
28 }else {
29 Desk.count--;
30 System.out.println("eating, left " + Desk.count + " bowl to eat");
31 Desk.lock.notifyAll();
32 Desk.foodFlag = 0;
33 }
34 }
35 }
36 }
37 }
38}
生产者Cook.class:
1package waitandnotify1;
2
3public class Cook extends Thread{
4 @Override
5 public void run() {
6 while (true){
7 synchronized (Desk.lock){
8 if (Desk.count == 0){
9 break;
10 }else{
11 if (Desk.foodFlag == 1){
12 try {
13 Desk.lock.wait();
14 } catch (InterruptedException e) {
15 throw new RuntimeException(e);
16 }
17 }else {
18 System.out.println("Cooking, left " + Desk.count + " to cook");
19 Desk.lock.notifyAll();
20 Desk.foodFlag = 1;
21 }
22 }
23 }
24 }
25 }
26}
27
测试代码:
1package waitandnotify1;
2
3public class ThreadDemo {
4 public static void main(String[] args) {
5 //等待唤醒机制完成生产者-消费者
6 Cook c = new Cook();
7 Foodie f = new Foodie();
8 c.setName("Cooker");
9 f.setName("Foodie");
10 c.start();
11 f.start();
12 }
13}
14
阻塞队列实现等待唤醒机制
生产者:
1package waitandnotify2;
2
3import java.util.concurrent.ArrayBlockingQueue;
4
5public class Cook extends Thread{
6
7 //成员变量
8 ArrayBlockingQueue<String> queue;
9
10 //构造方法
11 public Cook(ArrayBlockingQueue<String> queue) {
12 this.queue = queue;
13 }
14
15 @Override
16 public void run() {
17 while(true){
18 try {
19 //自带锁,里面会判断当前队列是否是满的
20 queue.put("noodle");
21 System.out.println("put noodle in queue");
22 } catch (InterruptedException e) {
23 throw new RuntimeException(e);
24 }
25
26 }
27
28 }
29}
30
消费者:
1package waitandnotify2;
2
3import java.util.concurrent.ArrayBlockingQueue;
4
5public class Foodie extends Thread{
6
7 ArrayBlockingQueue<String> queue;
8
9 public Foodie(ArrayBlockingQueue<String> queue) {
10 this.queue = queue;
11 }
12
13 @Override
14 public void run() {
15 while(true){
16 try {
17 String food = queue.take();
18 System.out.println("eating a bowl of noodle");
19 } catch (InterruptedException e) {
20 throw new RuntimeException(e);
21 }
22 }
23 }
24}
25
测试(提供一个阻塞队列):
1package waitandnotify2;
2
3import java.util.concurrent.ArrayBlockingQueue;
4
5
6
7public class ThreadDemo {
8 public static void main(String[] args) {
9 //阻塞队列完成生产者-消费者
10
11 //创建阻塞队列
12 ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
13 //创建线程对象,将阻塞队列传入
14 Cook cook = new Cook(queue);
15 Foodie foodie = new Foodie(queue);
16 //开启线程
17 cook.start();
18 foodie.start();
19 }
20}
21
五.线程的六种状态
线程状态:
- 新建new,创建线程对象
- 就绪runnable,start方法
- 阻塞blocked,无法获得锁对象
- 等待waiting,wait方法
- 计时等待timed_waiting,sleep方法
- 结束terminated,全部代码执行完毕.
作业代码就不贴了, 以后需要复习时重新写.