Java多线程笔记
WuCheng

多线程的创建

继承Thread类

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
class ThreadDemo extends Thread {
private String threadName = null;

ThreadDemo(String threadName) {
this.threadName = threadName;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(threadName + ": " + i);
}
}

@Override
public synchronized void start() {
System.out.println("线程:" + threadName + "启动");
super.start();
}
}

public class Multithreading {
public static void main(String[] args) {
ThreadDemo t1 = new Test.ThreadDemo("线程1");
t1.start();
}
}

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RunnableDemo implements Runnable {
private String threadName = null;

RunnableDemo(String threadName) {
this.threadName = threadName;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(threadName + ": " + i);
}
}
}

public class Multithreading {
public static void main(String[] args) {
RunnableDemo r1 = new Test.RunnableDemo("线程1");
Thread t1 = new Thread(r1);

t1.start();
}
}

实现Callable接口

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
class CallableDemo implements Callable<Integer>{ //此方法有带返回值
// Callable支持泛型
private int ticket = 100;

@Override
public Integer call() throws Exception {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "获得票 号码为:" + ticket--);
}
return ticket; //如果没有添加泛型讲以object类型返回
}
}

public class Multithreading {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new CallableDemo());
//FutrureTask是Future的唯一实现类
//Future类可以获取实现了Runnable和Callable的对象进行操作
new Thread(futureTask).start();
//Future类实现了Runnable方法所以也要作为参数被传递给Thread来执行多线程

try {
Integer ticket = futureTask.get();//用get方法来获取返回值
System.out.println("主线程获取的返回值:"+ticket);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}

}
}

线程池

线程池的好处是利于管理线程,在需要的时候调线程,不需要再一个一个创建线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Multithreading {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10); //返回的是接口值
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //ThreadPoolExecutor继承了ExecutorService方法

// service1.setCorePoolSize(10); //设置线程的核心
// service1.setMaximumPoolSize(); // 设置最大线程数量

service1.execute(new RunnableDemo()); //适用于Runnable
Future<Integer> submit = service1.submit(new CallableDemo()); //适用于Callable
//也可以使用Lambda表达式
service1.submit(()=>{
...
});

service1.shutdown(); //线程池不使用时关闭线程池
}
}

多线程的使用(抢票例子)

多个线程取一个票,票的数量是有限的,所以需要多个线程对一个数据进行操作,四种方式都可以实现

Thread方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ThreadDemo extends Thread{
private static int ticket = 10;

public ThreadDemo(String name) {
super(name);
}

@Override
public void run() {
while (ticket > 0){
System.out.println(getName()+"获得票 号码为:"+ticket--);
}
}
}

Runnable方式

1
2
3
4
5
6
7
8
9
10
class RunnableDemo implements Runnable {
private int ticket = 10;

@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "获得票 号码为:" + ticket--);
}
}
}

对比两种方式可以明显的发现,在继承Thread方式来实现多线程的时候我们需要在ticket前加static修饰,如果不修饰票数则修改的数据将不会是同一个数据。
而Runnable方式则不需要static修饰,若要对共同数据进行操作显而易见的Runnable会更加适合,而且在使用中也会更多的使用Runnable来实现多线程。

但是两个方法在线程上不安全的,当两个线程被阻塞后同时获取到数据的时候票数就会重复了。
特别是在println之前加入Thread.sleep(100)会更明显的观察到不安全的多线程所带来的bug。

线程同步锁

同步锁分为同步方法和同步代码块,还是以获取车票为例

同步代码块

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
class RunnableDemo implements Runnable {
private int ticket = 100;

@Override
public void run() {
while (true) {
synchronized (this) {
//也可以使用类作为对象,因为对象只会被加载一次(使用this需要谨慎)
//synchronized(RunnableDemo.class)
/* 同步监视器俗称锁,任何一个对象都可以充当锁
* 多个线程必须要用同一把锁,也就是对象要唯一
* 在同步监视器中对现行唯一所以在实现runnable方法中可以直接用this来用当前对象来作为监视器
* 值得注意的是继承Thread类中的this指向的对象地址并不唯一所以并不具有唯一属性
*/
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "获取票 号码为:" + ticket--);
} else {
break;
}
}
}
}
}

同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RunnableDemo implements Runnable {
private int ticket = 100;

@Override
public void run() {
while (ticket > 0){
show();
}
}

public synchronized void show() {
//同步监视器此时的参数为this
//注意在继承Thread类来进行多线程操作时由于方法被创建时并不唯一所以在方法钱要加static修饰,而同步监视器此时用的时RunnableDemo这一类
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "获取票 号码为:" + ticket--);
}
}
}

ReentrantLock(JDK 5.0+)

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
class Window3 implements Runnable{
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(true);
//构造的值为1时为公平lock,即为按顺序的线程执行,不会出现同一线程执行多次

@Override
public void run() {
while (ticket > 0) {
try {
lock.lock(); //同步锁

if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

System.out.println(Thread.currentThread().getName() + "窗口 票数为:" + ticket--);
}
}
finally {
lock.unlock(); //手动解锁
}
}
}
}

单例模式的安全问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Bank{
private static Bank instance = null;

public static synchronized Bank getInstance(){
//当多个线程获取instance时会有线程安全问题所以需要加锁
//此方式每次获取instance的时候其他线程都会被同步锁而无法快速判断是否值为null,所以效率较低
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
return instance;
}
//改进版:
if (instance == null){
synchronized (Bank.class){
if (instance == null){
instance = new Test.Bank();
}
}
}
return instance;
}
}
  • 本文标题:Java多线程笔记
  • 本文作者:WuCheng
  • 创建时间:2022-05-17 15:16:16
  • 本文链接:https://www.wucheng.work/2022/05/17/Java多线程/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!