Java --> 多线程1(线程3种创建方式、常用方法、线程安全、线程同步)
- 多线程的三种创建方式:
- 继承Thread类;
- 实现Runnab接口;
- JDK5.0新增:实现Callable接口
方式一:继承Thread类
1 //1、定义MyThread类继承自Thread类
2 public class MyThread extends Thread{
3 //2、重写run方法:定义线程以后要干啥
4 @Override
5 public void run() {
6 for (int i = 0; i < 5; i++) {
7 System.out.println("子线程执行输出:" + i);
8 }
9 }
10 }
1 //多线程的创建方式一 : 继承Thread类
2 public class ThreadDemo1 {
3 public static void main(String[] args) {
4 //3、创建一个新的线程对象
5 Thread myThread = new MyThread(); //多态写法
6 //4、(注意点一)调用start方法启动线程(最终执行的还是run方法) 【子线程执行】
7 //调用start方法的原因:如果写为 myThread.run()就会当成普通类调用了run方法,那么将会是永远都是子线程先跑【效果同单线程一样】
8 myThread.start();
9
10 //启动主线程
11 //(注意点二)主线程的位置不要写在子线程之前,不然会导致:子线程还没创建,先执行主线程【效果同单线程一样】
12 for (int i = 0; i < 5; i++) {
13 System.out.println("主线程启动:" + i);
14 }
15 }
16 }
方式二:实现Runnable接口
1 //1、定义MyRunnable类实现Runnable接口
2 public class MyRunnable implements Runnable{
3 //2、重写run方法:定义线程的执行任务
4 @Override
5 public void run() {
6 for (int i = 0; i < 10; i++) {
7 System.out.println("子线程启动:" + i);
8 }
9 }
10 }
1 //多线程创建方式二 : 实现Runnable接口
2 public class RunnableDemo1 {
3 public static void main(String[] args) {
4 //3、创建任务对象【非线程对象】
5 Runnable target = new MyRunnable(); //多态
6 //4、把任务对象交给Thread处理
7 Thread thread = new Thread(target);
8 thread.start();
9
10 //给主线程任务
11 for (int i = 0; i < 10; i++) {
12 System.out.println("主线程执行输出:" + i);
13 }
14 }
15 }
方式二的另一种实现方式:实现Runnable接口(匿名内部类的形式)
1 //线程的创建方式二:实现Runnable接口(匿名内部类的形式)
2 public class RunnableDemo2 {
3 public static void main(String[] args) {
4 //1、创建线程对象
5 Runnable target = new Runnable() {
6 //2、重写run方法
7 @Override
8 public void run() {
9 for (int i = 0; i < 10; i++) {
10 System.out.println("子线程1启动:" + i);
11 }
12 }
13 };
14
15 //第一层简化
16 Thread thread2 = new Thread(new Runnable() {
17 @Override
18 public void run() {
19 for (int i = 0; i < 10; i++) {
20 System.out.println("子线程2启动:" + i);
21 }
22 }
23 });
24 thread2.start();
25
26 //第二层简化【Lambda表达式】
27 Thread thread3 = new Thread(() ->{
28 for (int i = 0; i < 10; i++) {
29 System.out.println("子线程3启动:" + i);
30 }
31 });
32 thread3.start();
33
34 //3、把线程任务对象交给Thread处理
35 Thread thread = new Thread(target);
36 thread.start();
37 for (int i = 0; i < 10; i++) {
38 System.out.println("主线程启动输出:" + i);
39 }
40 }
41 }
(由于有30条输出语句,在此只列举出部分结果)
- 方式三:JDK5.0新增:实现Callable接口
1 import java.util.concurrent.Callable;
2
3 //1、定义任务类,实现Collable接口<泛型接口>,用于指定线程任务对象执行结果的返回值类型
4 public class MyCallable implements Callable<String> {
5 private int n;
6
7 public MyCallable(int n) {
8 this.n = n;
9 }
10
11 //2、重写run方法(线程的任务方法)【eg:计算1~n的和】
12 @Override
13 public String call() throws Exception {
14 int sum = 0;
15 //前n项和通项公式
16 sum = n * (1 + n) / 2;
17 return sum + "";
18 }
19 }
1 import java.util.concurrent.Callable;
2 import java.util.concurrent.FutureTask;
3
4 //线程的创建方式三:实现Collable接口,结合FutureTask完成
5 public class CallableDemo {
6 public static void main(String[] args) {
7 //3、创建Callable任务对象
8 Callable<String> callable = new MyCallable(100);
9 //4、把Cabbable对象交给FutureTask
10 //FutureTask的作用1:使Runnable对象(实现了Runnable接口),交给Thread了
11 //FutureTask的作用2:可以在线程执行完毕之后调用get方法,得到线程的执行结果
12 FutureTask<String> futureTask = new FutureTask<>(callable);
13 //5、交给Thread线程处理
14 Thread thread = new Thread(futureTask);
15 //6、启动线程
16 thread.start();
17
18 //线程2
19 Callable<String> callable2 = new MyCallable(200);
20 FutureTask<String> futureTask2 = new FutureTask<>(callable2);
21 Thread thread2 = new Thread(futureTask2);
22 thread2.start();
23
24 try {
25 //此处的代码实在任务futureTask没有执行完毕才执行
26 String str1 = futureTask.get();
27 System.out.println(str1);
28 }catch (Exception e){
29 e.printStackTrace();
30 }
31
32 try {
33 //同理:此处的代码实在任务futureTask2没有执行完毕才执行
34 String str2 = futureTask2.get();
35 System.out.println(str2);
36 }catch (Exception e){
37 e.printStackTrace();
38 }
39 }
40 }
- Thread类的常用方法:
1 public class MyThread extends Thread{
2 public MyThread() {
3 }
4
5 public MyThread(String name) {
6 super(name);
7 }
8
9 @Override
10 public void run() {
11 for (int i = 0; i < 5; i++) {
12 //System.out.println(this.getName() + "执行输出:" + i);
13 System.out.println(Thread.currentThread().getName() + "执行输出:" + i);
14 }
15 }
16 }
1 public class ThreadDemo1 {
2 public static void main(String[] args) {
3 Thread thread = new MyThread("1号");
4 //thread.setName("1号");
5 thread.start();
6 System.out.println(thread.getName());
7
8 Thread thread2 = new MyThread("2号");
9 //thread2.setName("2号");
10 thread2.start();
11 System.out.println(thread2.getName());
12
13 //那个线程执行它,它就拿到那个线程对象
14 //主线程的名称就叫main
15 Thread ct = Thread.currentThread();
16 ct.setName("主线程");
17 System.out.println(ct.getName());
18
19 for (int i = 0; i < 5; i++) {
20 System.out.println(ct.getName() + "线程启动:" + i);
21 }
22 }
23 }
- Thread类的线程休眠方法
- 线程安全问题:取钱模型演示
- 需求:小明和小红使一对夫妻,它们有一个共同的账户,余额是10W元;
- 如果小明和小红同时来取钱,而且2人都要取钱10W元,可能出现什么问题?
问题:两个线程都去取钱10W,银行亏钱10W。
分析:
- 提供一个账户类,创建一个账户对象代表2个人的共享账户;
- 定义一个线程类,线程类可以处理账户对象;
- 创建2个线程对象,传入同一个账户对象;
- 启动2个线程,去同一个账户对象中取钱10W元。
1 public class Account {
2 private String cardId;
3 private double money;
4
5 public Account() {
6 }
7
8 public void drawMoney(double money){
9 String threadName = Thread.currentThread().getName();
10 if (this.money >= money){
11 System.out.println(threadName + "来取钱,取出:" + money);
12 //更新余额
13 this.money -= money;
14 System.out.println("账户:" + this.cardId + "余额" + this.money);
15 }else {
16 System.out.println(threadName + "来取钱,余额不足");
17 }
18 }
19
20 public Account(String cardId, double money) {
21 this.cardId = cardId;
22 this.money = money;
23 }
24
25 public String getCardId() {
26 return cardId;
27 }
28
29 public void setCardId(String cardId) {
30 this.cardId = cardId;
31 }
32
33 public double getMoney() {
34 return money;
35 }
36
37 public void setMoney(double money) {
38 this.money = money;
39 }
40 }
1 //取钱线程类
2 public class DrawThread extends Thread{
3 //定义账户对象
4 private Account account;
5 public DrawThread(Account account,String name){
6 super(name);
7 this.account = account;
8 }
9 @Override
10 public void run() {
11 //取钱
12 account.drawMoney(100000);
13 }
14 }
1 //模拟取钱案例
2 public class ThreadDemo {
3 public static void main(String[] args) {
4 //1、定义线程类,定义一个账户对象
5 Account account = new Account("3539687420",100000);
6
7 new DrawThread(account,"小明").start();
8 new DrawThread(account,"小红").start();
9 }
10 }
这就是线程安全问题:即多个线程先后依次访问共享资源可能造成的问题。
- 解决方法一:线程同步【核心思想:加锁】
再看运行结果:
- 解决方法二:同步方法【在处添加关键字synchronized】
- 解决方法三:Lock锁