带你快速了解多线程
1、并发与并行2、线程与进程3、如何创建一个多线程3.1 多线程的运行原理:3.2 Thred类的常用方法3.3 实现Runnable 接口3.3 匿名内部类的方式实现线程的创建
4、线程安全问题:4.1 多线程安全问题的原理分析:4.2 线程同步技术解决线程安全问题4.3 同步代码块的原理4.4 线程的状态4.5 等待唤醒(线程之间的通信)4.6 等待唤醒机制(线程间的通信)
5、线程池:
1、并发与并行
并发:两个或者多个事件在同一时间段发生(交替执行)并行:两个或者多个事件在同一时刻发生(cpu多核、同时执行)
2、线程与进程
进程:是一个内存中运行的应用程序,有自己独立的内存空间,一个应用程序至少有一个进程,一个进程至少有一个线程;
线程: 线程是进程中的一个执行单元,是CPU调度和分派的基本单位,能独立运行的基本单位,同一进程中的多个线程之间可以并发执行。 线程调度:
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPUde 时间
抢占式调度:优先让优先级高的线程使用cpu,如果线程的优先级相同,就随机选择一个线程(线程随机性);Java使用的为抢占式调度;
主线程:
单线程程序:Java程序中只有一个线程执行从main方法开始,从上到下依次执行
3、如何创建一个多线程
通过继承Thead类,java.lang.Thead;重写Thead中的run方法,在run方法中设置我们的线程任务,调用start()方法,开启新的线程,执行run方法
public class TheadTest extends Thread{
@Override
public void run() {
for (int i
= 0; i
< 10; i
++){
System
.out
.println(i
);
}
}
TheadTest theadTest
= new TheadTest();
theadTest
.start();
3.1 多线程的运行原理:
多线程内存图解: 多个线程之间是互不影响的,因为在不同的栈空间
3.2 Thred类的常用方法
获取线程名:
getName()static currentThread() 返回当前正在执行的线程对象的引用。Thread.currentThread().getName()
设置线程的名称:
setName(String name) 改变该线程的名称等于参数 name。创建带参的构造方法
static void sleep(long millis)
当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。
3.3 实现Runnable 接口
public class RunnableThead implements Runnable {
@Override
public void run() {
for (int i
= 0; i
< 5 ; i
++) {
System
.out
.println(i
);
}
}
RunnableThead runnableThead
= new RunnableThead();
new Thread(runnableThead
).start();
Thread(Runnable target) 分配一个新的 Thread对象。Thread(Runnable target, String name) 分配一个新的 Thread对象。
- Runnable接口创建多线程和继承Thread类创建多线程的区别:
类只能单继承,实现Runnable还可以继承其他的类Runnable设置线程任务和开启线程分离。
3.3 匿名内部类的方式实现线程的创建
@Test
public void testThread(){
new Thread("线程一"){
@Override
public void run() {
for (int i
= 0; i
< 5; i
++) {
System
.out
.println(getName()+i
);
}
}
}.start();
}
@Test
public void testRunnable(){
new Thread( new Runnable(){
@Override
public void run() {
System
.out
.println("匿名线程");
}
}).start();
}
4、线程安全问题:
多线程访问了共享的数据就会产生线程安全问题;(多线程操作了同一批数据)
public class TicketThead extends Thread{
public static void saleTicket(String ThreadName
){
for (int i
= 10; i
> 0 ; i
--){
System
.out
.println(ThreadName
+"_"+ i
);
}
}
@Test
public void testTicketThrea(){
TicketThead ticketThead
= new TicketThead();
new Thread("线程1"){
@Override
public void run() {
String name
= getName();
ticketThead
.saleTicket(name
);
}
}.start();
new Thread("线程2"){
@Override
public void run() {
ticketThead
.saleTicket(getName());
}
}.start();
new Thread("线程3"){
@Override
public void run() {
ticketThead
.saleTicket(getName());
}
}.start();
}
}
4.1 多线程安全问题的原理分析:
4.2 线程同步技术解决线程安全问题
同步代码块
格式:synchronized(锁对象){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
锁对象可以为任意的对象(一般为共享资源的对象),定义的锁对象一般放在run方法之外,同步代码块放在run方法体内。锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行,线程没有执行完毕,不会释放锁
private int ticket
= 100;
Object obj
= new Object();
@Override
public void run() {
while (true) {
synchronized(obj
){
if (ticket
> 0) {
try {
Thread
.sleep(10);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println(Thread
.currentThread().getName() + "正在卖第" + ticket
--);
}
}
}
}
4.3 同步代码块的原理
同步方法
同步方法的锁对象就是当前实现类对象this
public void run() {
while (true) {
saleTicket();
}
}
private synchronized void saleTicket(){
if (ticket
> 0) {
try {
Thread
.sleep(10);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println(Thread
.currentThread().getName() + "正在卖第" + ticket
--);
}
}
静态同步代码块的锁对象是当前实现类的class文件对象
private static void saleTicket(){
synchronized(Runnable
.class){
if (ticket
> 0) {
try {
Thread
.sleep(10);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println(Thread
.currentThread().getName() + "正在卖第" + ticket
--);
}
}
}
3. 锁机制
接口 Lock -->java.util.concurrent.locks实现类 java.util.concurrent.locks.ReentrantLock所有方法 接口方法 抽象方法 Modifier and Type Method and Descriptionvoid lock() 获取锁。void unlock() 释放锁。使用步骤:在成员位置创建一个ReentrantLock对象
public void run() {
while (true) {
lock
.lock();
if (ticket
> 0) {
try {
Thread
.sleep(10);
System
.out
.println(Thread
.currentThread().getName() + "正在卖第" + ticket
--);
} catch (InterruptedException e
) {
e
.printStackTrace();
}finally {
lock
.unlock();
}
}
}
}
4.4 线程的状态
4.5 等待唤醒(线程之间的通信)
等待状态,一个正在无限期等待另一个线程执行一个特别唤醒动作的线程处于这一状态。Object类中的方法:wait和notify方法必须要在同步代码块或者同步方法中使用,因俄日锁对象必须是同一个void wait() 使当前线程等待另一个线程调用此对象的方法或 notify() notifyAll()方法。void wait(long timeout) //在指定时间内没有被唤醒,会自动醒过来使当前线程等待另一个线程调用此对象的方法或 notify() notifyAll()方法,或一个指定的时间流逝。void notify() 唤醒一个在这个对象的监视器上等待的单个线程。void notifyAll() 唤醒正在等待此对象监视器上的所有线程。
public static void main(String
[] args
) {
Object obj
= new Object();
new Thread("顾客线程"){
@Override
public void run() {
synchronized (obj
){
System
.out
.println(getName()+"告知老板要买的包子数量和种类!");
try {
obj
.wait();
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println("开吃包子咯");
}
}
}.start();
new Thread("老板线程"){
@Override
public void run() {
synchronized (obj
){
try {
Thread
.sleep(5000);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println(getName()+"告知顾客包子做好了");
obj
.notify();
}
}
}.start();
}
4.6 等待唤醒机制(线程间的通信)
就是一个线程执行完规定的操作之后就进入等待状态(调用wait()),等待其他线程完成他们指定的代码后再将其唤醒(notify)wait/notify就是线程间的一种协作机制多个线程在处理同一个资源,但是处理的动作(线程的任务)不一样多个线程并发执行时,默认情况下CPU是随机切换线程的,当我们需要多个线程共同完成一件任务,希望他们能规律的执行,多线程之间需要协调通信,以此达到多线程共同操作一份数据
public class BaoZiPu extends Thread{
private BaoZi bz
;
public BaoZiPu(BaoZi bz
) {
this.bz
= bz
;
}
@Override
public void run() {
synchronized (bz
){
if (bz
.flag
== true){
try {
bz
.wait();
} catch (InterruptedException e
) {
e
.printStackTrace();
}
}
if (bz
.flag
== false){
bz
.name
= "叉烧包";
System
.out
.println(getName() + ":" + "开始生产"+bz
.name
+"包子");
try {
Thread
.sleep(5000);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println("生产好了包子");
bz
.flag
= true;
bz
.notify();
}
}
}
public class Consumer extends Thread {
private BaoZi bz
;
public Consumer(BaoZi bz
) {
this.bz
= bz
;
}
@Override
public void run() {
synchronized (bz
){
if (bz
.flag
== false){
try {
bz
.wait();
} catch (InterruptedException e
) {
e
.printStackTrace();
}
}
if (bz
.flag
== true){
System
.out
.println( getName() + ":" + "开吃" + bz
.name
+ "包子!");
try {
Thread
.sleep(5000);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
System
.out
.println("包子吃完了!");
bz
.notify();
bz
.flag
= false;
}
}
}
}
BaoZi bz
= new BaoZi();
new BaoZiPu(bz
).start();
new Consumer(bz
).start();
5、线程池:
频繁的创建线程和销毁线程需要消耗时间
其实就是一个容纳多个线程的容器,线程池中的线程是可以反复使用的。
java.util.concurrent.Executors
static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,使用固定数量的线程操作了共享无界队列。
Future<?> submit(Runnable task) 提交执行一个Runnable任务并返回一个表示该任务的未来。
void shutdown() 启动一个有序的关机,在以前提交的任务被执行,但没有新的任务将被接受。
ExecutorService executorService
= Executors
.newFixedThreadPool(2);
executorService
.submit(new RunnableThead());