0%

java-Day8——线程

多线程

java.lang.Thread类代表线程,所有线程对象都必须是Thread类或其子类的对象

线程创建并启动步骤

方式一:继承Thread类

  1. 创建一个继承于Thread类的子类

  2. 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中

  3. 创建当前Thread的子类的对象

  4. 通过对象调用start():1.启动线程 2.调用当前线程的run()

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 EvenNumberTest {
public static void main(String[] args) {
//创建当前Thread的子类的对象
PrintNumber t1 = new PrintNumber();
//调用对象启动start
t1.start();

//再创建一个线程
PrintNumber t2 = new PrintNumber();
t2.start();
//main()所在线程执行操作
for(int i =1; i<=10000;i++){
if(i % 2 ==0){
System.out.println(Thread.currentThread().getName()+":"+i+"****");
}
}
}
}

//创建一个继承于Thread的子类
class PrintNumber extends Thread{
//重写Thread类的run()
@Override
public void run() {
for(int i =1;i<=1000;i++){
if(i%2 ==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}

方法二:实现Runnable接口

  1. 创建一个实现Runnable接口的类
  2. 实现接口的run() –>将此线程要执行的操作,声明在此方法体中
  3. 创建当前实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
  5. Thread类的实例调用start()
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 EvenNumberTest {
public static void main(String[] args) {
//创建当前实现类的对象
EvenNumberPrint p = new EvenNumberPrint();
//将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
Thread t1 = new Thread(p);
//Thread类的实例调用start()
t1.start();

//再创建一个线程
Thread t2 = new Thread(p);
t2.start();

//main方法对应的主线程
for(int i=1; i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}


//创建一个实现Runnable接口的类
class EvenNumberPrint implements Runnable{
//实现接口的run() -->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for(int i=1; i<=100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

两种方式对比

共同点

​ 1.创建线程的对象,都是Thread类或其子类的实例

​ 2.启动线程都使用的是Thread类中的start()

不同点

​ 一个是类的继承,一个是接口的实现

建议使用实现Runnable接口的方式。

Runnable好处:1.避免单继承的局限性 2.更适合处理有贡献数据的问题 3.实现代码与数据的分离

二者联系:public class Thread implements Runnable

线程常用结构

线程的构造器

  • public Thread(): 分配一个新的线程对象

  • public Thread(String name):分配一个指定名字的新的线程对象

  • public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法

  • public Thread(Runnable target, String name):分配一个带有指定目标新的线程对象并指定名字

线程中常用方法

  • start():1.启动线程 2.调用线程的run()
  • run():将线程要执行的操作,声明在run()中
  • currentThread():获取当前执行代码对应的线程
  • getName():获取线程名
  • setName():设置线程名
  • sleep(long millis):静态方法,调用时,可以使得代码所在当前线程睡眠指定的毫秒数
  • yield():静态方法,一旦执行此方法,就释放cpu的执行权
  • join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
  • isAlive():判断当前线程是否存活

线程优先级

(java是抢占式调度)

Thread类的三个优先级常量

MAX_PRIORITY(10):最高优先级

MIN_PRIORITY(1):最低优先级

NORM_PRIORITY(5):普通优先级,默认情况main线程具有普通优先级

getPriority():获取线程的优先级

setPriority():设置线程的优先级,范围[1,10],大数值就有大概率

线程生命周期

JDK1.5之前:5种状态

LmeC3.png

JDK5.0之后:6种状态

Lmgm9.png

线程安全机制

当多个线程访问同一资源(同一个变量、同一个文件、同一条记录)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程对资源有读和写操作,就容易出现线程安全问题。

方法一:同步代码块

synchronized(同步监视器){

​ //需要被同步的代码

}

说明:

  • 需要被同步的代码,即为操作共享数据的代码
  • 共享数据,即多个线程都需要操作的数据
  • 需要被同步的代码,在被synchronized包裹以后,就是的一个线程在操作这些代码过程中。其他线程必须等待。
  • 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码
  • 同步监视器,可以使用任何一个类的对象。但是,多个线程必须公用同一个同步监视器。

注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this

​ 在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用当前类.class

继承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
32
33
34
35
36
37
38
39
40
41
42
public class WindowTest {
public static void main(String[] args) {

SaleTicket w1 = new SaleTicket("窗口1");
SaleTicket w2 = new SaleTicket("窗口2");
SaleTicket w3 = new SaleTicket("窗口3");
w1.start();
w2.start();
w3.start();
}
}

class SaleTicket extends Thread{
static int ticket = 1000;
static Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (SaleTicket.class) {//this:此时表示w1,w2,w3不饿能保证唯一性
//obj:加上static,可以保证唯一性
if (ticket > 0) {

try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket);
ticket--;

} else {
break;
}

}
}
}

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

实现Runnable接口的应用

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
public class WindowTest {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

class SaleTicket implements Runnable{
int ticket = 1000;
@Override
public void run() {
while(true){
synchronized (this) {
if (ticket > 0) {

try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket);
ticket--;

} else {
break;
}

}
}
}
}

方式二:同步方法

说明:

  • 如果操作共享数据的代码完整声明在了一个方法中,那么我就可以将此方法声明为同步方法即可。

  • 非静态的同步方法,默认同步监视器是this

  • 静态同步方法,默认监视器是当前类本身。

继承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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class WindowTest {
public static void main(String[] args) {

SaleTicket w1 = new SaleTicket("窗口1");
SaleTicket w2 = new SaleTicket("窗口2");
SaleTicket w3 = new SaleTicket("窗口3");
w1.start();
w2.start();
w3.start();
}
}

class SaleTicket extends Thread{
static int ticket = 1000;
static boolean isFlag = true;

@Override
public void run() {
while(isFlag){
show();

}
}

public static synchronized void show(){
if (ticket > 0) {

try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket);
ticket--;

} else {
isFlag = false;
}



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

实现Runnable接口的应用

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 WindowTest {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);

t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");

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

class SaleTicket implements Runnable{
int ticket = 1000;
static boolean isFlag = true;
@Override
public void run() {
while(isFlag){
show();
}
}

public synchronized void show(){ //此时的同步监视器是:this,此问题中还是唯一的
if (ticket > 0) {

try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket);
ticket--;
}else {
isFlag=false;
}
}
}

synchronized优缺点

好处:解决了线程问题

弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。

单例模式-懒汉式(线程安全解决版)

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
public class BankTest {
static Bank b1 = null;
static Bank b2 = null;

public static volatile void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
b1 = Bank.getInstance();
}
};

Thread t2 = new Thread(){
@Override
public void run() {
b2 = Bank.getInstance();
}
};

t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1==b2);
}
}

class Bank {
private Bank() {
}

private static volatile Bank instance = null;

public static synchronized Bank getInstance() {
try {
if (instance == null) {
Thread.sleep(1000);
instance = new Bank();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return instance;
}

//第二种方式(效率更高)
public static Bank getInstance(){
if(instance ==null){
synchronized(Bank.class){
if(instance==null){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
instance = new Bank();
}
}
}
return instance;
}
}


除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的锁方式

步骤

  1. 创建Lock的实例,需要确保多个线程公用同一个Lock实例!需要考虑将此对象声明为static final
  2. 执行lock()方法,锁定对共享资源的调用
  3. Unlock()的调用,释放对共享资源的锁定
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
public class WindowTest {
public static void main(String[] args) {

SaleTicket w1 = new SaleTicket("窗口1");
SaleTicket w2 = new SaleTicket("窗口2");
SaleTicket w3 = new SaleTicket("窗口3");
w1.start();
w2.start();
w3.start();
}
}

class SaleTicket extends Thread{
static int ticket = 1000;
static boolean isFlag = true;
//1.创建Lock的实例,需要确保多个线程公用一个Lock实例,需要考虑将此对象声明为static finally
private static final ReentrantLock lock =new ReentrantLock();
@Override
public void run() {
while(isFlag){
try {
//2. 执行lock方法,锁定对共享资源的调用
lock.lock();
show();
}finally {
lock.unlock();
}
}
}

public void show(){
if (ticket > 0) {

try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为" + ticket);
ticket--;
} else {
isFlag = false;
}
}
public SaleTicket(String name) {
super(name);
}
}

Lock与synchronized的区别

synchronized不管是同步代码块还是同步方法,都需要在结束一对{},释放对同步监视器的调用。

Lock是通过两个方法控制需要被同步的代码,更灵活一些。

Lock作为接口,提供了多种实现类,适合更多更复杂,效率更高。

通信

线程间通信的理解

当我们’需要多个线程’来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信可以协调它们的工作,以此实现多线程共同操作一份数据。

涉及到三个方法的使用

wait():线程一旦执行此方法,就进入等待状态,同时会释放对同步监视器的调用。

notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程优先级相同就随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

注意

  • 此三个方法的使用,必须是在同步代码块或同步方法中。
  • 此三个方法的调用者,必须是同步监视器。否则会报IllegalMontiorStateException异常
  • 此三个方法声明在Object类中
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
public class PrintNumberTest {
public static void main(String[] args) {
PrintNumber p = new PrintNumber();
Thread t1 = new Thread(p,"线程1");
Thread t2 = new Thread(p,"线程2");
t1.start();
t2.start();
}
}

class PrintNumber implements Runnable{
private int number =1;

@Override
public void run() {
while(true){
synchronized (this) {
notify();
if(number<=100){

try {
Thread.sleep(10);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":"+number);
number++;

try {
wait(); //线程一旦执行此方法,就进入等待状态。同事,会释放对同步监视器的调用。
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
break;
}
}
}
}
}

wait()和sleep()的区别

相同点:一旦执行,当前线程都会进入阻塞状态

不同点

声明位置不同:wait():声明在Object类中

​ sleep()声明在Thread类中,静态的

使用场景不同:wait():只能使用在同步代码块或同步方法中

​ sleep():可以在任何需要使用的场景

使用在同步代码块中:wait()一旦执行会释放同步监视器

​ sleep()一旦执行,不会释放

结束阻塞的方式:wait()到达指定时间自动结束阻塞或通过被notify唤醒,结束阻塞

sleep():到达指定时间自动结束阻塞。

消费者与生产者练习

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
public class ProducerConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro1 = new Producer(clerk);
Consumer con1 = new Consumer(clerk);
pro1.setName("生产者1");
con1.setName("消费者");
pro1.start();
con1.start();

}
}

class Clerk{
private int ProductNum = 0;

//增加产品数量方法
public synchronized void addProduct(){
if(ProductNum >= 20){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
ProductNum++;
System.out.println(Thread.currentThread().getName()+"生产了第"+ProductNum+"个产品");
notify();
}

//减少产品数量方法
public synchronized void minusProduct(){
if(ProductNum<=0){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
ProductNum--;
System.out.println(Thread.currentThread().getName()+"消费了第"+ProductNum+"个产品");
notify();
}

}
}

class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
System.out.println("生产者生产产品");
try{
Thread.sleep(20);
}catch (InterruptedException e){
e.printStackTrace();
}
clerk.addProduct();
}
}
}

class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}

@Override
public void run() {
while (true) {
System.out.println("生产者生产产品");
try{
Thread.sleep(50);
}catch (InterruptedException e){
e.printStackTrace();
}
clerk.minusProduct();
}
}
}

线程创建方式三-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
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 1; i<=100; i++){
if(i%2 ==0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}

public class CallableTest{
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();

//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);

//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
Thread t1 = new Thread(futureTask);
t1.start();

try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutreTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();//(自带一个阻塞)
System.out.println("总和为" + sum);
} catch (Exception e) {
e.printStackTrace();

}
}
}

与Runnable方式的对比

  • call()可以有返回值,更灵活
  • call()可以使用throws方式处理异常,更灵活
  • Callable使用了泛型参数。可以指明具体的call()的返回值类型,更灵活

缺点:

如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的

线程创建方式四-使用线程池

此方式的好处:

  • 提高了程序执行的效率(因为线程已经提前创建好了)
  • 提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
  • 可以设置相关的参数,对线程池的线程的使用进行管理
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
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;

//设置线程池的属性
service1.setMaximumPoolSize(50);//设置线程池中线程数的上限

//2.执行指定的线程的操作-需要提供实现Runnable接口或Callable接口1实现类的对象
service.execute(new NumberThread());
service.execute(new NumberThread1());

//3.关闭连接池
service.shutdown();
}
}

class NumberThread implements Runnable{
@Override
public void run() {
for(int i =0;i<=100;i++){
if(i%2 ==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}

class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i=0;i<=100;i++){
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}

常见问题

1.线程和进程相关定义

2.多线程使用场景

  • 手机app应用图片的下载
  • 迅雷的下载
  • Tomcat服务器上web应用,多个客户端发起请求,Tomcat针对多个请求开辟多个线程

3.如何实现多线程

4.Thread类中的Start()和run()的区别?

start():1.开启线程 2.调用线程的run()

5.sleep()和yield()区别?

sleep():一旦调用,就进入阻塞状态(或TiMED_WAITING状态)

yield():释放cpu的执行权,处在runnable的状态

6.线程生命周期

ex:线程有哪些状态,如何让线程进入阻塞

ex线程有几个状态,就绪和阻塞有什么不同

7.线程优先级怎么定义

8.stop()和suspend()方法为何不推荐使用

stop():一旦执行,线程就结束了。导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全

suspend():与resume()搭配使用,导致死锁

9.线程安全的理解,线程安全如何造成的

10.多线程公用一个数据变量需要注意什么

11.多线程保证线程安全一般有几种方式

同步机制

Lock接口

12.用什么关键字修饰同步方法?

13.synchronized加在静态方法和普通方法的区别

同步监视器不同。静态:类本身 非静态:this

14.当一个线程进入一个对象的一个synchronized方法后,其他线程是否可以进入此对象的其他方法?

需要看其他方法是否使用synchronized修饰,同步监视器的this是否是同一个。

只有当使用了synchronized且this是同一个的情况下,就不能访问

15.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?

同步一定阻塞。 阻塞不一定同步

16.wait和notify方法为什么一定要在同步块中调用?

调用者必须是同步监视器。

17.生产者、消费者问题

18.wait和sleep的区别

19.手写单例模式。

-------------本文结束感谢您的阅读-------------