继承Thread类

首先要重写run方法,然后在使用的时候需要调用start方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for(int i=0;i<20;i++){
System.out.println("1");
}
}

public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程,线程启动需要一段时间,因此会先打印一会儿‘2’,是有cpu来进行调度的
testThread1.start();

for(int i=0;i<20;i++){
System.out.println("2");
}
}
}

文件下载的代码出问题了,原因尚不知道,先贴一下代码

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import java.io.*;
import java.net.URL;

public class TestThread2 extends Thread{
private final String url;
private final String name;

public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public void run() {
super.run();
WebDownloader webDownloader = new WebDownloader();
try {
webDownloader.downloader(url, name);
System.out.println("Succeed to download:"+name);
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO exception happen when you try to download the files");
}
}

public static void main(String[] args){
TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594993558795&di=e299a32a05127a6511851d8c3b431c5a&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180321%2Fc315358a45e04f7cb072e827a40e4379.jpeg", "1.jpeg");
TestThread2 t2 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602842887124&di=fde2e6863c115993abf1822605cbff0a&imgtype=0&src=http%3A%2F%2Fa3.att.hudong.com%2F14%2F75%2F01300000164186121366756803686.jpg", "2.jpg");
TestThread2 t3 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1602842887124&di=a038d5c4dd0accb9d2b694634db8596d&imgtype=0&src=http%3A%2F%2Fa0.att.hudong.com%2F70%2F91%2F01300000261284122542917592865.jpg", "3.jpg");

t1.start();
t2.start();
t3.start();


}
}

//下载器
class WebDownloader{
//下载方法
public void downloader(String url, String name) throws IOException {
URL downurl = new URL(url);
InputStream in = new BufferedInputStream(downurl.openStream());
// ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n = 0;
/*
while (-1!=(n=in.read(buf)))
{
out.write(buf, 0, n);
}
out.close();
in.close();
*/
// byte[] response = out.toByteArray();
FileOutputStream fos = new FileOutputStream(name);
while(-1!=(n=in.read(buf))){
fos.write(buf,0,n);
}
// fos.write(response);
fos.close();

}
}

实现Runnable接口

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("1");
}
}

public static void main(String[] args) {
TestRunnable testRunnable = new TestRunnable();
//创建线程对象,通过线程对象来开启我们的线程,代理
Thread thread = new Thread(testRunnable);
thread.start();
}
}

因为java是单继承,所以推荐使用Runnable接口,避免OOP单继承局限性,方便同一个对象被多个线程使用。

lambda表达式

函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class TestLambda {
//2.使用静态内部类
static class ILike2 implements Like{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}

public static void main(String[] args) {
//1.通过实现接口和类的方式来实现功能
ILike1 iLike1 = new ILike1();
iLike1.lambda();

//2.使用静态内部类
ILike2 iLike2 = new ILike2();
iLike2.lambda();

//3.使用局部内部类
class ILike3 implements Like{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
ILike3 iLike3 = new ILike3();
iLike3.lambda();

//4.使用匿名内部类
Like iLike4 = new Like() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
iLike4.lambda();

//5.使用lambda简化
//因为lambda表达式是对函数式接口使用的,这种接口只有一个方法,所以方法名可以省去,可以认为是保留lambda后面的所有内容,但是省去lambda前面的内容例如 public void lambda。因此在带参数的例子中和原来一样在括号中传入参数即可。
Like iLike5 = ()->{
System.out.println("I like lambda5");
};
//假如只有一行也可以写成下面的样子
//Like iLike5 = ()->System.out.println("I like lambda5");
//假如有参数,则可以用参数代替括号的位置,假如要传入多个参数的时候括号不能去掉
iLike5.lambda();

}
}

//1.通过实现接口和类的方式来实现功能
//定义一个函数式接口
interface Like{
//因为是函数式接口所以只能声明一个方法,这里声明一个lambda方法
void lambda();
}
//实现一个类来实现这个接口的功能
class ILike1 implements Like{
@Override
public void lambda() {
System.out.println("I like lambda1");
}
}

线程的停止

建议在线程中自己设置一个标志位,以及自定义的stop方法来设置这个标志位。

?这里i声明的位置不同会导致不同的输出结果,假如是方法中的局部变量则刚好会在main中循环到900之后停止,但是作为属性出现时则会有不同的结果。假如是使用属性中的i并且在使用的时候使用this.i去引用那么和直接使用方法中的i是一样的效果。

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
public class TestStop implements Runnable{
private boolean flag = true;
// private int i = 0;

@Override
public void run() {
int i=0;
while(flag){
System.out.println("thread print:"+ i++);
}
}

public void stop(){
this.flag = false;
}

public static void main(String[] args) {
TestStop testStop = new TestStop();
Thread thread = new Thread(testStop);
thread.start();

for(int i=0;i<1000;i++){
if(i == 900){
testStop.stop();
}
System.out.println("main print:"+i);
}
}
}

线程休眠

sleep方法中的参数指定当前线程阻塞的毫秒数,sleep存在异常InterruptedException,时间结束后将会进入就绪状态,sleep可以模拟网络延时(可以放大问题)以及倒计时。sleep不会释放锁。

1
Thread.sleep(1000);//休眠1s钟

线程礼让yield

可以让当前正在执行的线程暂停,但是不阻塞,线程会从运行状态转为就绪状态,之后cpu会重新调度,不一定会成功。

1
2
3
4
5
6
7
8
9
10
11
12
public class TestYield{
public static void main(String[] args) {
Runnable testYield = () -> {
System.out.println(Thread.currentThread()+"start");
Thread.yield();
System.out.println(Thread.currentThread()+"stop");
};

new Thread(testYield, "a").start();
new Thread(testYield, "b").start();
}
}

线程强制执行join

可以想象成插队

1
2
Thread.join(thread);
//之后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
28
29
30
31
public class TestState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread ends");
});

//线程未启动时查看状态
Thread.State state = thread.getState();
System.out.println(state);

//线程启动后观察状态
thread.start();
Thread.State tmp_state = state;
state = thread.getState();
System.out.println(state);

while(state != Thread.State.TERMINATED){
if(tmp_state != state)
System.out.println(state);
tmp_state = state;
state = thread.getState();
}
}
}

线程优先级

线程的优先级低只是被cpu调度的可能性比较低,并不是它不会被cpu调度

1
2
thread.getPriority();//获得线程的优先级,返回一个int  优先级低到高为1->10
thread.setPriority(5); //可以设置线程的优先级,默认为5

守护线程(daemon)

线程分为用户线程守护线程

虚拟机必须确保用户线程执行完毕,但是不需要等待守护线程执行完毕,守护线程有 后台记录操作日志,监控内存,垃圾回收等

1
2
3
4
5
6
Thread thread = new Thread(()->{
while(true){
System.out.println("This is Daemon thread");
}
});
thread.setDaemon(true);

此时程序能够正常的结束,因为该具有死循环的线程被设置为了守护线程。

线程安全

使用 队列+锁 的方式来保证线程的安全性。

同步方法与同步块

synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦开始执行就会独占该锁,知道该方法返回才会释放锁,后面被阻塞的线程才能获得这个锁,并继续执行。

不安全的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;

public class TestSynchronized {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()-> list.add(Thread.currentThread().getName())).start();
}
//这里需要等待一下,否则线程还没有执行完的时候就已经输出list.size()了
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

使用synchronized块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.swing.plaf.TableHeaderUI;
import java.util.ArrayList;

public class TestSynchronized {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()-> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

在方法中直接加上synchronized就是相当于synchronized(this){...}

我们需要上锁的内容应该是公共资源。

使用CopyOnWriteArrayList来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.CopyOnWriteArrayList;

public class TestSynchronized {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->list.add(Thread.currentThread().getName())).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

使用ReentrantLock来实现

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
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;

public class TestSynchronized {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
final ReentrantLock lock = new ReentrantLock();

for (int i = 0; i < 10000; i++) {
new Thread(()-> {
try{
lock.lock();
list.add(Thread.currentThread().getName());}
finally {
lock.unlock();
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

这里需要注意lock的作用域

线程通信

wait()可以让线程一直等待,直到其他线程通知,它和sleep不同,它会释放锁。

notify()可以唤醒一个处于等待状态的线程。

下面会使用生产者、消费者模型来演示

管程法

在生产者和消费者之间增加了一个缓冲区。生产者会将生产的东西放入缓冲区,同时消费者也会在这个缓冲区中拿。

缓存一致性

缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
public class TestPC {
public static void main(String[] args) {
Monitor monitor = new Monitor();

Producer producer = new Producer(monitor);
Consumer consumer = new Consumer(monitor);
producer.start();
consumer.start();

}
}

//需要的对象有生产者、消费者、产品、缓冲区(管程)

class Goods{
final int id;

public Goods(int id) {
this.id = id;
}
}

class Producer extends Thread{
Monitor monitor;
int i = 1;

public Producer(Monitor monitor) {
this.monitor = monitor;
}

@Override
public void run() {
super.run();
for (int j = 0; j < 100; j++) {
monitor.push(new Goods(i));
System.out.println("Producer:"+i++);
}
}
}

class Consumer extends Thread{
Monitor monitor;

public Consumer(Monitor monitor) {
this.monitor = monitor;
}

@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
//实际上输出语句并没有同步,因此输出数来的时候可能消费者在前
System.out.println("Consumer:"+monitor.pop().id);
}
}
}

class Monitor{
//创建一个缓冲区用来存放生产的商品
Goods[] buffer = new Goods[10];
//缓冲区中的商品数
int gCount = 0;


//生产者生产商品
//由于公共资源是this对象的属性,因此这里直接对this加锁即可
public synchronized void push(Goods goods){
//假如缓冲区满了就需要通知消费者进行消费
//不要用if,应该用while,否则当有多个消费者的时候,会出现脏判断,if语句中醒来的线程 不会再一次进行判断了 而while会重新再判断
while(gCount == buffer.length){
try {
//生产者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//通知消费者消费
this.notifyAll();
}

//假如没有满就放入缓冲区
buffer[gCount++] = goods;
this.notifyAll();
}

//消费者消费商品
public synchronized Goods pop(){
while (gCount == 0){
try {
//假如没有商品,那么消费者需要等待,并通知生产者进行生产
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notifyAll();
}
Goods goods = buffer[--gCount];
this.notifyAll();

return goods;
}
}

信号灯法

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/** * 協作模型:生產者消費者實現:信號燈法 */
public class Cooperation2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Fans(tv).start();
}
}
/** * 生產者:演員 */
class Actor extends Thread{
TV tv;
public Actor(TV tv){
this.tv = tv;
}

@Override public void run() {
for (int i=0; i<10; i++){
if (i%2 == 0){ this.tv.play("節目 " + i);
}else{
this.tv.play("廣告 " + i);
}
}
}
}
/** * 消費者:觀眾 */
class Fans extends Thread{
TV tv;
public Fans(TV tv){
this.tv = tv;
}

@Override public void run() {
for (int i=0; i<10; i++){
tv.watch();
}
}
}
/** * 共同資源:電視直播 */
class TV{
String voice;
//信號燈,如果為真則演員準備,觀眾等待
// 如果為假,則觀眾就位,演員等待
boolean flag = true;
// 表演方法:針對生產者
public synchronized void play(String voice){
//演員等待
if (!flag){
try { this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.voice = voice;
System.out.println("表演 "+voice +" ing");
//喚醒觀眾
this.notifyAll();
this.flag = !flag;
}
//觀看方法:針對消費者
public synchronized void watch(){
//觀眾等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("觀看 " + voice +" ing");
this.notifyAll();
this.flag = !flag;
}
}

线程池&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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestCallable implements Callable<Boolean> {

private String url;
private String name;

public TestCallable(String url,String name){
this.url = url;
this.name = name;
}

//下载图片线程的执行体
@Override
public Boolean call() throws IOException {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载的文件名为:"+name);
return true;
}

public static void main(String[] args) throws Exception{
TestCallable t1 = new TestCallable("https://www.baidu.com/img/dong_f6764cd1911fae7d460b25e31c7e342c.gif","百度劳动节1.gif");
TestCallable t2 = new TestCallable("http://a4.att.hudong.com/72/82/19300000009075130804824786610.jpg","恐龙2.jpg");
TestCallable t3 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588579963142&di=69d697e367f8b3bc144a759a51377c5a&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20171110%2Fbd4b16a2eaa74eb683704ff4f85dc813.jpeg","一人之下3.jpg");

//创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(3); //线程池
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();

System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);

//关闭服务
ser.shutdown();

}
}

Runnable接口使用execute而不是submit