线程
1.概念
并发:指两个或多个事件在同一时间段内发生。(交替执行)
并行:指两个或多个事件在同一时间发生
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程是进程的执行单位,一个进程至少含一个线程。
一个进程可以含多个线程,这种称为多线程。
一个进程可以类比于腾讯电脑管家,多个线程可以当作病毒查杀,清理垃圾,电脑加速等。
2.创建线程
主线程:执行(main)方法的线程
单线程程序:java程序中只有一个线程
2.1 Thread
-
创建一个新的执行线程有两种方法。 一个是将一个类声明为
Thread
的子类。 这个子类应该重写run
类的方法Thread
。 然后可以分配并启动子类的实例。 -
实现步骤
- 1.创建一个Thread类的子类
- 2.在Thread类的子类中重写Thread类的run方法,设置线程任务(即开启线程做什么)
- 3.创建Thread类的子类对象
- 4.调用Thread类的start方法开启新的线程。
-
class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } }
-
然后,以下代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(143); p.start();
Thread常用方法
public final String getName()//获取线程名称
- Thread.currentThread().getname()也可以直接获取(main必须使用这个方式)
public static void sleep(long millis)//暂停millis时间后在执行
2.2 Runnable
-
另一种方法来创建一个线程是声明实现类
Runnable
接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。 -
实现步骤:
- 1.创建Runnable接口的实现类
- 2.在实现类中重写Runnable接口的run方法
- 3.创建Runnable接口的实现类对象
- 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 5.调用Thread类的run()方法,开始新的线程
-
class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } }
-
然后,以下代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143); new Thread(p).start();
- 实现Runnable接口创建多线程程序的好处
- 避免了单继承的局限性
- 一个类继承了Thread就不能继承其他类了
- 增强了程序的扩展性,降低了程序的耦合性
- 实现Runnable接口的方式,把设置线程任务和开启新线程进行了解耦
- 实现类中,重写了run()方法:用来设置线程任务
- 创建Thread类对象,调用start方法:用来开启新线程
- 避免了单继承的局限性
public class MyThread {
public static void main(String[] args) {
//Lamda将Runnable实现
new Thread(() -> {for (int i = 0; i < 5; i ++)
System.out.println(Thread.currentThread().getName() + " " + i);
}).start();
//匿名内部类继承Thread
new Thread() {
@Override
public void run() {
for (int i = 0; i < 5; i ++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}.start();
}
}
3.线程的安全问题
卖票案例
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权,让其他的线程只能等待。
package it;
public class RunnableImpl implements Runnable {
private static int ticket = 100;
@Override
public void run() {
while (ticket > 0){
System.out.println(Thread.currentThread().getName() + " 正在卖第"+ (101 - ticket) + "张票");
ticket --;
}
}
}
package it;
public class MyThread {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
以上代码有安全问题,会有重复以及不存在的票
-
解决安全问题的第一种方式:同步代码块
格式:
synchronized(锁对象){ 可能会出现线程安全问题的代码(访问共享数据的代码) }
注意:
1. 通过代码块中的锁对象,可以使用任意的对象 2. 但是必须保证多个线程使用的锁对象是同一个 3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
package it; public class RunnableImpl implements Runnable { private int ticket = 100; @Override public void run() { while (true) { synchronized (this) { if (ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "->>正在卖第" + (101 - ticket) + "张票"); ticket--; } else { break; } } } } }
-
解决线程安全问题的第三种方案:使用Lock锁
void lock(){//获得锁
}
void unlock(){//释放锁
}
- 使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口的方法unlock释放锁
package it;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class RunnableImpl implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
l.lock();
if (ticket > 0) {//判断语句不要放在锁对象外
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + " 正在卖第" + (101 - ticket) + "张票");
ticket--;
}else{
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
- synchronized是自动释放锁(显示),lock需要手动释放和关闭锁(隐式)。
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
- Lock只有代码块锁,synchronized有代码块锁和方法锁。
4.线程的等待唤醒操作
即为操作系统所讲的pv操作
经典案例:生产者消费者
案例:线程之间的通信(买包子)
- 创建一个顾客线程(消费者):告知老板要得包子的种类和数量,调用wait方法,放弃CPU的执行,进入到waiting状态(无限等待)
- 创建一个老板线程(生产者):花了5秒做包子,做好之后调用notify方法,唤醒顾客吃包子
注:
- 老板和顾客是同步代码,保证唤醒和等待只有一个在执行
- 同步使用的锁对象唯一
- 只有锁对象才能使用wait和notify方法
Object类的方法
-
public final void wait()
导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll(),换句话说,这个方法的行为就好像简单地执行呼叫wait(0)
-
public final void notify()
唤醒正在等待对象监视器的单个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。 线程通过调用wait
方法之一等待对象的监视器。
package it;
public class Baozi {
public static void main(String[] args) {
Object obj = new Object();
new Thread(() -> {//消费者
System.out.println("tell the onwer that how many and how much (and then wait 5s)");
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("eat");
}
}).start();
new Thread(() -> {//生产者
synchronized (obj){
System.out.println("spend 5s to do it");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
obj.notify();
System.out.println("it has been finished,u can eat");
}
}).start();
}
}
- 如果wait(long m),即为m毫秒内没被唤醒,自动醒来
- notifyAll(),唤醒所有等待的线程
包子升级版案例:
package ita;
public class Baozi {
public String pi;
public String xian;
public boolean flag = false;
}
package ita;
public class Baozipu extends Thread {
private Baozi bz;
public Baozipu(Baozi bz) {
this.bz = bz;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (bz) {
if (bz.flag == true) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count % 2 == 0) {
bz.pi = "薄皮";
bz.xian = "韭菜";
} else if (count % 2 == 1) {
bz.pi = "厚皮";
bz.xian = "大葱";
}
count++;
System.out.println("正在做" + bz.pi + bz.xian + "馅包子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
bz.flag = true;
bz.notify();
System.out.println("已经做完" + bz.pi + bz.xian + "馅包子");
}
}
}
}
package ita;
public class Chihuo extends Thread {
private Baozi bz;
public Chihuo(Baozi bz) {
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
if (bz.flag == false) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货正在吃" + bz.pi + bz.xian + "馅包子");
bz.flag = false;
bz.notify();
System.out.println("吃货已经吃完" + bz.pi + bz.xian + "馅包子");
System.out.println("+++++++++++++++++++++++++++++++++++");
}
}
}
}
package ita;
public class Main {
public static void main(String[] args) {
Baozi bz = new Baozi();
new Baozipu(bz).start();
new Chihuo(bz).start();
}
}
5.线程池
-
如果并发的线程数量很多,并且每个线程都是执行一个时间执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
-
java.util.concurrent.Executors//线程池的工厂类,用来生产线程池
-
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
-
java.util.concurrent.ExecutorService、、线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
sumbit(Runnable task)提交一个Runnable任务用于执行
关闭/销毁线程池的方法 void shutdown()
-
线程池的使用步骤
- 使用Executors的newFixedThreadPool生产指定线程数量的线程池
- 创建一个类,实现Runnable接口,设置线程任务
- 调用ExecutorService中的方法submit,传递线程任务(实现类)
- 调用shutdown销毁线程池(不建议执行)
package itb;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main2 {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.shutdown();
}
}
package itb;
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"创建了一个新的线程");
}
}
观黑马java所写