什么是多线程及多线程的创建
基本概念
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。进程一般由程序,数据集合和进程控制块三部分组成。
线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程:多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。(例如:餐厅中一个客户点了3个菜,单线程就好比厨房只有一个厨师,那么3个菜只有一个一个上菜,多线程就好比厨房有3个厨师,三个厨师可以同一时间每人个做一个菜,那么上菜时就可以三个一起上)
使用如何创建一个线程
java创建线程的方式有两种。一种是通过继承Thread类,另一种是通过实现Runnable接口。下面通过模拟火车站售票窗口,开启窗口售票,总票数为100张,这个例子,使用两种方式创建一个线程。
(1)方式一:通过继承Thread类,创建线程。
package com.test;
/**
* 使用继承方式模拟火车站售票窗口,开启窗口售票,总票数为100张
* 创建方式(注:暂不涉及线程安全问题,进简单创建一个线程):
* // 1、创建一个继承Thread类的子类
* class SubClassName extends Thread {
* // 2、实现重写Thread类的run()方法,实现子线程要完成的功能
* public void run() {
* // TODO 子线程完成的功能
* }
* }
*
* public static void main(String[] args) {
* // 3、创建子类对象
* SubClassName scn = new SubClassName();
* // 4、调用线程的start()方法:启动此线程,调用相应的run()方法
* scn.start();
* }
*
* @author Anna.
* @date 2019-08-26
*/
public class SellTicketWindowsByExtendsTest01 {
public static void main(String[] args) {
// 创建共享数据
Windows windows = new Windows(100);
// 3、创建子类对象
SellTicketThread scn = new SellTicketThread(windows);
// 4、调用线程的start()方法:启动此线程,调用相应的run()方法
scn.setName("窗口1");
scn.start();
}
}
/** 创建共享数据 */
class Windows {
private int ticket;
public Windows(int ticket) {
this.ticket = ticket;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public void sell(){
ticket--;
System.out.println(Thread.currentThread().getName() + ":售出一张票,还剩" + ticket + "张票");
}
}
/** 1、创建一个继承Thread类的子类*/
class SellTicketThread extends Thread {
private Windows windows;
public SellTicketThread(Windows windows){
this.windows = windows;
}
public Windows getWindows() {
return windows;
}
public void setWindows(Windows windows) {
this.windows = windows;
}
/** 2、实现重写Thread类的run()方法,实现子线程要完成的功能*/
@Override
public void run() {
// 子线程完成的功能
while (windows.getTicket() > 0) {
windows.sell();
}
}
}
(2)方式二:通过实现Runnable接口,创建线程。
package com.test;
/**
* 使用实现方式模拟火车站售票窗口,开启窗口售票,总票数为100张
* 创建方式(注:暂不涉及线程安全问题,进简单创建一个线程):
* // 1、创建一个实现Runnable接口的子类
* class SubClassName implements Runnable {
* // 2、实现重写Thread类的run()方法,实现子线程要完成的功能
* public void run() {
* // TODO 子线程完成的功能
* }
* }
* <p>
* public static void main(String[] args) {
* // 3、创建实现Runnable接口的子类对象
* SubClassName scn = new SubClassName();
* // 4、创建一个Thread对象
* Thread th = new Thread(scn);
* // 5、调用线程的start()方法:启动此线程,调用相应的run()方法
* th.start();
* }
*
* @author Anna.
* @date 2019-08-26
*/
public class SellTicketWindowsByImplementTest02 {
public static void main(String[] args) {
// 创建共享数据
Windows2 windows2 = new Windows2(100);
// 3、创建实现Runnable接口的子类对象
SellTicketThread2 scn = new SellTicketThread2(windows2);
// 4、创建一个Thread对象
Thread th = new Thread(scn);
th.setName("窗口1");
Thread th2 = new Thread(scn);
th2.setName("窗口2");
// 5、调用线程的start()方法:启动此线程,调用相应的run()方法
th.start();
th2.start();
}
}
/**
* 创建共享数据
*/
class Windows2 {
private int ticket;
public Windows2(int ticket) {
this.ticket = ticket;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public void sell() {
ticket--;
System.out.println(Thread.currentThread().getName() + ":售出一张票,还剩" + ticket + "张票");
}
}
/**
* 1、创建一个实现Runnable接口的子类
*/
class SellTicketThread2 implements Runnable {
private Windows2 windows2;
public SellTicketThread2(Windows2 windows2) {
this.windows2 = windows2;
}
public Windows2 getWindows2() {
return windows2;
}
public void setWindows2(Windows2 windows2) {
this.windows2 = windows2;
}
/**
* 2、实现重写Thread类的run()方法,实现子线程要完成的功能
*/
@Override
public void run() {
// 子线程完成的功能
while (windows2.getTicket() > 0) {
windows2.sell();
}
}
}
(3)对比两种方式的区别与联系:
联系:从源码上看Thread类实际上是实现了Runnable接口
源码:
/**
* A <i>thread</i> is a thread of execution in a program. The Java
* Virtual Machine allows an application to have multiple threads of
* execution running concurrently.
* <p>
* Every thread has a priority. Threads with higher priority are
* executed in preference to threads with lower priority. Each thread
* may or may not also be marked as a daemon. When code running in
* some thread creates a new <code>Thread</code> object, the new
* thread has its priority initially set equal to the priority of the
* creating thread, and is a daemon thread if and only if the
* creating thread is a daemon.
* <p>
* When a Java Virtual Machine starts up, there is usually a single
* non-daemon thread (which typically calls the method named
* <code>main</code> of some designated class). The Java Virtual
* Machine continues to execute threads until either of the following
* occurs:
* <ul>
* <li>The <code>exit</code> method of class <code>Runtime</code> has been
* called and the security manager has permitted the exit operation
* to take place.
* <li>All threads that are not daemon threads have died, either by
* returning from the call to the <code>run</code> method or by
* throwing an exception that propagates beyond the <code>run</code>
* method.
* </ul>
* <p>
* There are two ways to create a new thread of execution. One is to
* declare a class to be a subclass of <code>Thread</code>. This
* subclass should override the <code>run</code> method of class
* <code>Thread</code>. An instance of the subclass can then be
* allocated and started. For example, a thread that computes primes
* larger than a stated value could be written as follows:
* <p><hr><blockquote><pre>
* class PrimeThread extends Thread {
* long minPrime;
* PrimeThread(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <p><blockquote><pre>
* PrimeThread p = new PrimeThread(143);
* p.start();
* </pre></blockquote>
* <p>
* The other way to create a thread is to declare a class that
* implements the <code>Runnable</code> interface. That class then
* implements the <code>run</code> method. An instance of the class can
* then be allocated, passed as an argument when creating
* <code>Thread</code>, and started. The same example in this other
* style looks like the following:
* <p><hr><blockquote><pre>
* class PrimeRun implements Runnable {
* long minPrime;
* PrimeRun(long minPrime) {
* this.minPrime = minPrime;
* }
*
* public void run() {
* // compute primes larger than minPrime
* . . .
* }
* }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <p><blockquote><pre>
* PrimeRun p = new PrimeRun(143);
* new Thread(p).start();
* </pre></blockquote>
* <p>
* Every thread has a name for identification purposes. More than
* one thread may have the same name. If a name is not specified when
* a thread is created, a new name is generated for it.
*
* @author unascribed
* @version %I%, %G%
* @see Runnable
* @see Runtime#exit(int)
* @see #run()
* @see #stop()
* @since JDK1.0
*/
public
class Thread implements Runnable {
...
}
区别:
(1)实现的方式,避免了单继承的局限性
(2)如果多个线程操作同一份资源/数据,更适合使用实现的方式
Thread常用方法
1.start():启动线程并执行相应的run()方法。(这里需要注意的是必须通过调用.start()方法启动线程,不是通过调用run()方法,示例如下:)
...
public static void main(String[] args) {
// 创建共享数据
Windows windows = new Windows(100);
// 3、创建子类对象
SellTicketThread scn = new SellTicketThread(windows);
// 4、调用线程的start()方法:启动此线程,调用相应的run()方法,如下通过调用子类对象run()方法,并不是启动线程,只是单独的调用了类的方法,这种启动方式是错误的
scn.run();
}
...
2.run():子线程要执行的代码放在run()方法中
3.currentThread():静态的,调用当前的线程
4.getName():获取此线程的名称
5.setName():设置此线程的名称
6.yield():调用此方法的线程释放当前CPU的执行权
7.join():在A线程中调用B线程的join()方法,表示,当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行
8.isAlive():判断当前线程是否还存活
9.sleep(long time):显示的让当前线程睡眠time毫秒
10.线程通信: wait() notify() notifyAll() (注:该3个方法时Object中的方法,java中每一个对象都包含这三个方法)
wait() :使当前线程进入等待状态,让出CUP资源
notify() : 随机唤醒一个等待的线程
notifyAll() : 唤醒所有等待的线程
11.设置线程的优先级:
setPriorty(int newPriorty):改变线程的优先级,并不代表一定是优先级高的先执行完毕后在执行优先级低的线程,只表示加大了线程抢占到CUP的概率
getPriorty():返回线程的优先值
线程安全问题分析
前面示例中存在线程安全问题:打印程票时,会出现重票和错票。效果如下:
出现原因:由于一个线程在操作共享数据的过程中,未执行完的情况下,另一个线程参与进来,导致共享数据存在安全问题
如果没有可以将使用实现Runnable接口的示例,通过调用.sleep()手动将结果放大,代码如下:
...
public void sell() {
ticket--;
// 在操作共享数据自减与打印剩余共享数据之间添加.sleep()手动放大错误结果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售出一张票,还剩" + ticket + "张票");
}
...
解决方法:必须让一个线程操作共享数据完毕后,其他线程才有参与共享数据的操作
Java如何实现线程的同步机制
(1)同步代码块
synchronized (同步监视器) {
// 需要被同步的代码块(即为操作共享数据的代码)
}
A、共享数据:多线程共同操作的数据(变量)
B、同步监视器:由一个对象充当,哪一个线程获取此监视器,谁就执行大括号里被同步的代码,俗称:锁(注:要求所有的线程必须共用同一把锁,在实现的方式中可以使用this,即当前对象来充当同步锁,在继承的方式中不可以)
(2)同步方法,放在方法声明中,表示整个方法为同步方法
public synchronized void show () {
...
}
将操作共享数据的方法声明为synchronized,即此方法为同步方法,能够保证当一个线程在执行此方法时,其他线程在外进入等待状态直至此线程执行完此方法,同步方法的锁是this,即当前类。(注:在实现的方式中可以使用同步方法,但是在继承方式中,由于声明了多个对象,锁并不是唯一的,所有不能直接使用synichronized声明)
对于上述案例,采用实现方式实现同步机制:
(方式一)、同步代码块
package com.test.synchronize;
/**
* 使用实现方式模拟火车站售票窗口,开启窗口售票,总票数为100张
* java实现线程的同步机制:
* (1)、同步代码块
* synchronized (同步监视器) {
* // 需要被同步的代码块(即为操作共享数据的代码)
* }
*
* 1、共享数据:多个线程共同操作的数据(变量)
* 2、同步监视器:由一个对象来充当,哪一个线程获取此监视器,谁就执行大括号里被同步的代码,俗称:锁
* (注:要求所有的线程必须共用同一把锁,在实现的方式中可以使用this来充当同步监视器,在继承的方式中不可以)
*
* @author Anna.
* @date 2019-08-26
*/
public class SellTicketWindowsByImplementTest03 {
public static void main(String[] args) {
// 创建共享数据
Windows2 windows2 = new Windows2(100);
// 3、创建实现Runnable接口的子类对象
SellTicketThread2 scn = new SellTicketThread2(windows2);
// 4、创建一个Thread对象
Thread th = new Thread(scn);
th.setName("窗口1");
Thread th2 = new Thread(scn);
th2.setName("窗口2");
// 5、调用线程的start()方法:启动此线程,调用相应的run()方法
th.start();
th2.start();
}
}
/**
* 创建共享数据
*/
class Windows2 {
private int ticket;
public Windows2(int ticket) {
this.ticket = ticket;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public void sell() {
if (ticket > 0) {
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售出一张票,还剩" + ticket + "张票");
}
}
}
/**
* 1、创建一个实现Runnable接口的子类
*/
class SellTicketThread2 implements Runnable {
private Windows2 windows2;
public SellTicketThread2(Windows2 windows2) {
this.windows2 = windows2;
}
public Windows2 getWindows2() {
return windows2;
}
public void setWindows2(Windows2 windows2) {
this.windows2 = windows2;
}
/**
* 2、实现重写Thread类的run()方法,实现子线程要完成的功能
*/
@Override
public void run() {
// 子线程完成的功能
// 同步代码块
while (windows2.getTicket() > 0) {
synchronized (this) {
windows2.sell();
}
}
}
}
(方式二)、同步方法
package com.test.synchronize;
/**
* 使用实现方式模拟火车站售票窗口,开启窗口售票,总票数为100张
* java实现线程的同步机制:
* (2)、同步方法,放在方法声明中,表示整个个方法为同步方法
* public synchronized void show () {
*
* }
*
* 将操作共享数据的方法声明为synchronized,即此方法为同步方法,能够保证当其中一个线程再执行此方法时,
* 其他线程在外等待直至此线程执行完成此方法,同步方法的锁:this,当前对象。
*
* (注:在实现方式中可以使用同步方法,但是在继承方式中由于声明了多个对象,所有不能使用直接使用synchronized声明)
*
* @author Anna.
* @date 2019-08-26
*/
public class SellTicketWindowsByImplementTest04 {
public static void main(String[] args) {
// 创建共享数据
Windows2 windows2 = new Windows2(100);
// 3、创建实现Runnable接口的子类对象
SellTicketThread2 scn = new SellTicketThread2(windows2);
// 4、创建一个Thread对象
Thread th = new Thread(scn);
th.setName("窗口1");
Thread th2 = new Thread(scn);
th2.setName("窗口2");
// 5、调用线程的start()方法:启动此线程,调用相应的run()方法
th.start();
th2.start();
}
}
/**
* 创建共享数据
*/
class Windows2 {
private int ticket;
public Windows2(int ticket) {
this.ticket = ticket;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public synchronized void sell() {
if (ticket > 0) {
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售出一张票,还剩" + ticket + "张票");
}
}
}
/**
* 1、创建一个实现Runnable接口的子类
*/
class SellTicketThread2 implements Runnable {
private Windows2 windows2;
public SellTicketThread2(Windows2 windows2) {
this.windows2 = windows2;
}
public Windows2 getWindows2() {
return windows2;
}
public void setWindows2(Windows2 windows2) {
this.windows2 = windows2;
}
/**
* 2、实现重写Thread类的run()方法,实现子线程要完成的功能
*/
@Override
public void run() {
// 子线程完成的功能
// 同步代码块
while (windows2.getTicket() > 0) {
windows2.sell();
}
}
}