首页 > 编程语言 > 详细

Java学习笔记-8-多线程

时间:2019-10-27 22:41:32      阅读:102      评论:0      收藏:0      [点我收藏+]

多线程

多线程,特点,随机性,单核cpu并发一会执行这个线程,一会执行另外一个线程,造成代码随机性的现象

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码,Java使用线程执行体来代表这段程序流。

 

继承Thread类来创建线程

步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体

  2. 创建Thread子类的实例对象,即创建了线程对象。

  3. 调用线程对象的start()方法来启动该线程。

代码:

 1 public class Demo {
 2     public static void main(String[] args) {
 3         //创建线程对象
 4         Thread thread = new TestThread("线程对象");
 5         //开启线程
 6         thread.start();
 7         for (int i = 0; i < 10; i++) {
 8             System.out.println("主线程" + i);
 9         }
10     }
11 }
12 
13 class TestThread extends Thread {
14 
15     public TestThread(String name) {
16         //调用父类Thread的构造方法
17         super(name);
18     }
19 
20     /**
21      * 重写run方法
22      */
23     @Override
24     public void run() {
25         for (int i = 0; i < 10; i++) {
26             System.out.println(getName() + ":" + i);
27         }
28     }
29 }

结果:

主线程0
线程对象:0
线程对象:1
主线程1
线程对象:2
线程对象:3
主线程2
线程对象:4
主线程3
线程对象:5
主线程4
线程对象:6
主线程5
线程对象:7
线程对象:8
线程对象:9
主线程6
主线程7
主线程8
主线程9

 

构造方法

创建对象,Thread类里面成员变量字符数组进行赋值,线程的名字只是把这个字符数组变成字符串

注意jdk1.9把字符串数组变成了字符串

分配一个新的线程对象。默认名字第一个子线程叫Thread-0,其他以此类推

public Thread() 

 

分配一个指定名字的新的线程对象。

public Thread(String name)

 

分配一个带有指定目标任务的,新的线程对象,默认名字第一个子线程叫Thread-0,其他以此类推

public Thread(Runnable target) 

 

分配一个带有指定目标任务的,新的线程对象,并指定线程的名字。

public Thread(Runnable target,String name) 

 

常用API

获取当前线程的名字

public String getName() 

 

设置当前线程的名字

public String setName() 

 

导致此线程开始执行; Java虚拟机调用此线程的run方法。同一个线程对象不能多次调用start方法,否则会报非法线程状态异常:IllegalThreadStateException

public void start() 

 

此线程要执行的任务,在此处定义代码。

public void run() 

 

使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

public static void sleep(long millis) 

 

返回当前正在执行的线程对象的引用,引用即Thread线程的对象名

public static Thread currentThread() 

 

实现Runnable接口方式创建线程

步骤:

  1. 编写一个类实现Runnable,重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

  3. 调用线程对象的start()方法来启动线程。

代码:

1 public class TestRunnable implements Runnable {
2     @Override
3     public void run() {
4         for (int i = 0; i < 5; i++) {
5             System.out.println(i + "线程:" + Thread.currentThread().getName());
6         }
7     }
8 }
 1 public class Demo {
 2     public static void main(String[] args) {
 3         TestRunnable runnable = new TestRunnable();
 4         Thread thread = new Thread(runnable, "多线程");
 5         thread.start();
 6         for (int i = 0; i < 5; i++) {
 7             System.out.println("主线程" + i);
 8         }
 9     }
10 }
结果:

主线程0
0线程:多线程
主线程1
1线程:多线程
主线程2
主线程3
2线程:多线程
主线程4
3线程:多线程
4线程:多线程

 

run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法而已。

 

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。

如果实现了Runable接口,则很容易的实现资源共享(可以传递相同的Runnable接口实现类对象调用同一个run方法做一样的事情,实现资源共享)

 

实现Runnable接口比继承Thread类所具有的优势

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦(耦合:类与类之间关系)操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable接口线程,不能直接放入继承Thread的类。

 

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。

 

使用匿名内部类创建线程

匿名内部类,代表一个类或者一个接口的(干)子类对象,简化格式,前提要存在一个类或者一个接口

格式如下:

new 类名或接口名(){
  重写方法,如果是普通类可以写也可以不重写,如果是抽象类或者是接口必须要重写抽象方法
}

 

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

 1 public class Demo {
 2     public static void main(String[] args) {
 3         new Thread() {
 4             @Override
 5             public void run() {
 6                 System.out.println("开启多线程");
 7             }
 8         }.start();
 9         System.out.println("主线程");
10 
11     }
12 }

 

使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:

 1 public class Demo {
 2     public static void main(String[] args) {
 3         new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 System.out.println("开启多线程");
 7             }
 8         }).start();
 9         System.out.println("主线程");
10     }
11 }

 

线程安全

案例:模拟卖票,分四条执行路径(线程,窗口类模拟线程)

代码:

 1 public class Ticket implements Runnable {
 2 
 3     /**
 4      * 定义票数
 5      */
 6     private int ticket = 50;
 7 
 8     @Override
 9     public void run() {
10         //模拟窗口卖票服务
11         while (true) {
12             if (ticket > 0) {//有票 可以卖
13                 //使用sleep模拟一下出票时间
14                 try {
15                     Thread.sleep(100);
16                 } catch (InterruptedException e) {
17                     e.printStackTrace();
18                 }
19                 System.out.println(Thread.currentThread().getName() + "正在卖" + ticket-- + "张票");
20             } else {
21                 break;
22             }
23         }
24     }
25 }
26 
27 class Demo {
28     public static void main(String[] args) {
29         //创建接口实现类对象
30         Ticket ticket = new Ticket();
31 
32         //创建卖票窗口对象
33         Thread windowsOne = new Thread(ticket, "窗口一");
34         Thread windowsTwo = new Thread(ticket, "窗口二");
35         Thread windowsThree = new Thread(ticket, "窗口三");
36 
37         //执行卖票
38         windowsOne.start();
39         windowsTwo.start();
40         windowsThree.start();
41     }
42 }

结果:

窗口三正在卖48张票
窗口一正在卖49张票
窗口二正在卖50张票
窗口二正在卖47张票
窗口三正在卖47张票

...

窗口三正在卖3张票
窗口一正在卖2张票
窗口二正在卖3张票
窗口二正在卖0张票
窗口三正在卖-1张票
窗口一正在卖1张票

 

结果中有一部分这样现象:

窗口三正在卖3张票
窗口一正在卖2张票
窗口二正在卖3张票
窗口二正在卖0张票
窗口三正在卖-1张票
窗口一正在卖1张票

 

发现程序出现了两个问题:

  1. 相同的票数,比如3这张票被卖了两回。

  2. 不存在的票,比如0票与-1票,是不存在的。

这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

 

线程同步,同步技术,解决线程的安全性问题

同步就可以让cpu在某段时间内只让一个线程进来做事情,其他线程不能进来。(理解为排队),同步技术包括,同步代码块,同步方法,Lock锁

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

根据案例简述:

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。
也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

 

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。

三种方式完成同步操作:

  1. 同步代码块。

  2. 同步方法。

  3. 锁机制。

同步代码块

 

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

 

格式:

synchronized(同步锁){//锁对象可以是任意的,但是如果这个同步代码块被多次调用,要求锁对象是唯一。
     需要同步操作的代码。
}

 

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED进入阻塞状态)。

 

使用同步代码块解决代码:

 1 public class Ticket implements Runnable {
 2 
 3     /**
 4      * 定义票数
 5      */
 6     private int ticket = 50;
 7 
 8     @Override
 9     public void run() {
10         //模拟窗口卖票服务
11         while (true) {
12             synchronized (Ticket.class){
13                 if (ticket > 0) {//有票 可以卖
14                     //使用sleep模拟一下出票时间
15                     try {
16                         Thread.sleep(100);
17                     } catch (InterruptedException e) {
18                         e.printStackTrace();
19                     }
20                     System.out.println(Thread.currentThread().getName() + "正在卖" + ticket-- + "张票");
21                 } else {
22                     break;
23                 }
24             }
25         }
26     }
27 }
28 
29 class Demo {
30     public static void main(String[] args) {
31         //创建接口实现类对象
32         Ticket ticket = new Ticket();
33 
34         //创建卖票窗口对象
35         Thread windowsOne = new Thread(ticket, "窗口一");
36         Thread windowsTwo = new Thread(ticket, "窗口二");
37         Thread windowsThree = new Thread(ticket, "窗口三");
38 
39         //执行卖票
40         windowsOne.start();
41         windowsTwo.start();
42         windowsThree.start();
43     }
44 }

结果:

窗口二正在卖50张票
窗口一正在卖49张票
窗口三正在卖48张票

...

窗口一正在卖4张票
窗口二正在卖3张票
窗口一正在卖2张票
窗口三正在卖1张票

当使用了同步代码块后,上述的线程的安全问题就解决了。

 

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

public  synchronized void method(){
       可能会产生线程安全问题的代码
}
同步锁是谁?

      对于非static方法,同步锁就是this。  

      对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

 

Lock锁,接口

 

Java学习笔记-8-多线程

原文:https://www.cnblogs.com/cmmplb/p/11741236.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!