Java线程

Java 线程

这玩意绝对很重要,因为经常听到多线程这个词,而且也听说很难

创建线程

创建线程有两种方法,一种是创建继承Thread的子类,一种是创建实现Runnable接口的实现类

方法一:

步骤:

  1. 创建一个 Thread 子类
  2. 在Thread类的子类中重写Thread的run方法,设置线程任务
  3. 创建Thread子类对象
  4. 调用Thread类中的start方法,开启新的线程执行run方法

实例:

1
2
3
4
5
6
7
8
public class myThread extends Thread{ //继承Thread类,重写里面的run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run:" + i);
}
}
}

启动线程:

1
2
3
4
5
6
7
myThread mt = new myThread(); //创建实现类的对象
mt.start(); //启动线程
myThread mt1 = new myThread(); //创建第二个线程
mt1.start(); //可以开启多个线程
for (int i = 0; i < 10; i++) { //主线程,这些线程都是是同时执行的
System.out.println("润:" + i);
}

使用对象的start方法就是在调用对象的run方法,于是执行了重写的run方法

方法二:

步骤:

  1. 创建Runnable接口的实现类
  2. 重写接口的run方法
  3. 创建实现类对象
  4. 创建Thread类对象,构造方法中传入Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法

实例:

1
2
3
4
5
6
7
8
public class RunnableImpl implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()); //输出线程名称
}
}
}

启动线程:

1
2
3
RunnableImpl run = new RunnableImpl();
Thread th = new Thread(run);
th.start();
  • 使用Runnable接口的优点就是一个类只能继承自一个父类,但能继承多个接口,如果使用继承Thread的方法,则无法继承其他的类了

获取线程名称的方法:

currentThread()方法返回当前正在执行的进程对象的引用,获取主线程的线程名可以通过 Thread.currentThread().getName() 来获取
线程获取线程名的方法可以直接调用 getName() 方法,返回字符串

1
2
3
4
5
6
7
8
9
public class myThread extends Thread{
@Override
public void run() {
String name = getName();
System.out.println(name);
Thread name1 = currentThread();
System.out.println(name1);
}
}

启动线程:

1
2
3
4
5
myThread mt = new myThread();
mt.start();
myThread mt1 = new myThread();
mt1.start();
System.out.println(Thread.currentThread().getName()); //获取主线程名称

通过currentThread()方法可以看出,调用这个线程的是主线程

线程安全

多个线程访问共享数据的时候,会出现线程安全问题,所以需要让线程进行某些操作的时候,同一时间只能有一个进程在工作
有两种方法实现:第一种使用synchronized同步代码块,第二种使用Lock锁

方法一:

格式:

1
2
3
synchronized(锁对象){
//可能出现线程安全的代码
}

原理: 抢夺到了同步块的执行权,会把锁对象拿走,然后执行同步代码块,执行完了就释放锁对象,其他线程执行到此处发现没有锁对象,没有执行权,会一直等待锁对象归还
锁对象我认为只要 Object 的对象都可以,此处锁对象可以使用一个有意义的对象来控制进程的调用

synchronized可以实现同步代码块,也可以实现同步方法

同步代码块实例:

1
2
3
4
Object obj = new Object(); //实例化一个 Object 对象来当锁对象
synchronized (obj){
System.out.println(Thread.currentThread());
}

同步方法实例:

1
2
3
4
5
6
7
8
9
10
11
public class RunnableImpl implements Runnable{
@Override
public void run() {
while (ticket>0){
payTicket();
}
}
public synchronized void payTicket(){
System.out.println(Thread.currentThread());
}
}

方法二:

Lock锁解决线程安全,比synchronized更好用
Lock接口的方法:
void lock()获取锁
void unlock()释放锁

步骤:
步骤

  1. 在类的成员位置创建一个ReentrantLock对象
  2. 在可能出现安全问题的代码前调用Lock接口的lock方法
  3. 在可能出现安全问题的代码后调用Lock接口的unlock方法
1
2
3
4
5
6
7
8
9
10
11
12
public class RunnableImpl implements Runnable{
Lock l = new ReentrantLock();

@Override
public void run() {
l.lock();
while (true){
System.out.println(Thread.currentThread().getName());
}
l.unlock();
}
}

同步和锁都是让这部分代码一个时间只能有一个线程去执行,这样就不会出现线程问题了

等待(wait)和唤醒(notify)

这里就可以用到同步锁对象来做到让线程等待和唤醒其他线程了

调用wait方法会使该线程进入等待状态,并且会释放被同步对象的锁
notify操作可以唤醒一个因执行wait而处于阻塞状态的线程,使其进入就绪状态,被唤醒的线程等待锁对象被释放后,可以尝试获得锁对象,如果获得了,则执行wait之后的代码


多线程实例,包子铺和吃货

1
2
3
4
5
public class BaoZi { //定义包子类,拿来当锁对象
String pi; //包子皮
String xian; //包子馅
boolean flag = false; //包子是否被做好的标记
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class BaoZiPu extends Thread{ //包子铺类
private final BaoZi baozi; //用来接收对象锁的变量
private int count = 0; //用来控制包子铺做什么包子
public BaoZiPu(BaoZi bz){ //本类的构造方法,需要一个BaoZi对象
this.baozi = bz;
}

@Override
public void run() { //重写run方法
while (true){ //一直做包子
synchronized (baozi){ //传入锁对象
if (baozi.flag){ //判断包子是否做好了,true表示包子是做好了,然后包子铺线程进入等待状态
try {
baozi.wait(); //线程进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒后
if (count % 2==0){ //判断做什么包子
baozi.pi="薄皮";
baozi.xian="三鲜";
}
else {
baozi.pi="冰皮";
baozi.xian="牛肉";
}
count++;
System.out.println("包子铺正在生产:" + baozi.pi + baozi.xian + "包子");
System.out.println("生产包子需要3s");
try {
sleep(3000); //设置生产包子的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
baozi.flag=true; //设定包子已经被生产好了
baozi.notify(); //唤醒其他线程,也就是吃货线程
System.out.println("包子铺已经生产好了" + baozi.pi + baozi.xian + "包子,可以吃了");
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ChiHuo extends Thread{ //吃货类
private BaoZi baozi; //用来接收对象锁的变量
public ChiHuo(BaoZi bz){ //本类的构造方法,需要传入一个BaoZi类对象
this.baozi=bz;
}
@Override
public void run() { //重写run方法
while (true){ //一直吃下去
synchronized (baozi){ //传入锁对象
if (!baozi.flag){ //判断包子是否被做好,没做好是false,取反就是true,执行下面的代码
try {
baozi.wait(); //吃货线程进入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒后
System.out.println("吃货正在吃" + baozi.pi + baozi.xian + "的包子");
System.out.println("吃包子需要3s");
try {
sleep(3000); //设置吃包子的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
baozi.flag=false; //把包子的完成设置为false,这样包子铺那边就可以执行做包子的代码了
//唤醒包子铺
baozi.notify();
System.out.println("吃货已经吃掉了" + baozi.pi + baozi.xian + "的包子");
System.out.println("==============================");
}
}
}
}
1
2
3
4
5
6
7
public class demo {
public static void main(String[] args) {
BaoZi baozi = new BaoZi(); //实例化BaoZi对象
new BaoZiPu(baozi).start(); //开启包子铺线程
new ChiHuo(baozi).start(); //开启吃货线程
}
}

多线程真的让人头秃,看了课之后又多看了几遍代码,然后就加上了注释,也就是自己的理解,途中还翻了许多大佬的博客来验证自己的理解是不是正确的,现在来说,理解算是接近正确的了吧 ٩(๑❛ᴗ❛๑)۶