1、概念
通常在一个进程中可以包含若干线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态概念。
进程是执行程序的一次执行过程,是一个动态概念。
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核。
线程是独立的执行路径;
在程序运行时,即使自己没有创建线程,后台也会有多个线程,如主线程main,gc线程;
在一个线程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能认为控制;
对同一份资源操作时,会存在资源抢占问题,需要加入并发控制;
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致;
2、线程的创建
2.1、继承Thread类
步骤
自定义线程类继承Thread类
重写run()方法,编写线程执行体
创建线程对象,调用start()方法启动线程
//多线程的实现1:继承Thread
public class ThreadTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在学习代码规范"+i);
}
}
/**
* 多线程由cpu调度执行
* @param args
*/
//主线程
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
//子线程
threadTest.start();
for (int i = 0; i < 500; i++) {
System.out.println("我在学习多线程"+i);
}
}
}
我们为什么要继承Thread类,并调用其的start方法才能开启线程呢?
继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?
如下代码:
Thread t1 = new Thread();
t1.start();//这样做没有错,但是该start调用的是Thread类中的run方法
//而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。
创建线程的目的是什么?
是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。
对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。
2.2 、实现Runnable
避免单继承局限性,方便同一个对象被多个线程使用
//多线程实现方式2:实现Runnable接口
public class ThreadTest2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("runnable实现中"+i);
}
}
public static void main(String[] args) {
ThreadTest2 test2=new ThreadTest2();
new Thread(test2).start();
for (int i = 0; i < 200; i++) {
System.out.println("实现方式二"+i);
}
}
}
实现接口方式的好处
第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
继承Thread类,线程对象和线程任务耦合在一起。
一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。
实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。
Runnable接口对线程对象和线程任务进行解耦。
(降低紧密性或者依赖性,创建线程和执行任务不绑定)
2.3、实现Callable接口
/*
* 实现线程程序的第三个方式,实现Callable接口方式
* 实现步骤
* 工厂类 Executors静态方法newFixedThreadPool方法,创建线程池对象
* 线程池对象ExecutorService接口实现类,调用方法submit提交线程任务
* submit(Callable c)
*/
public class ThreadPoolDemo1 {
public static void main(String[] args)throws Exception {
ExecutorService es = Executors.newFixedThreadPool(2);
//提交线程任务的方法submit方法返回 Future接口的实现类
Future<String> f = es.submit(new ThreadPoolCallable());
String s = f.get();
System.out.println(s);
}
}
/*
* Callable 接口的实现类,作为线程提交任务出现
* 使用方法返回值
*/
public class ThreadPoolCallable implements Callable<String>{
public String call(){
return "aaa";
}
}
2.4 应用
1、模拟多线程共用一个对象
//买票
public class ThreadTest3 implements Runnable {
//票数
private int ticket=10;
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"票");
}
}
public static void main(String[] args) {
ThreadTest3 ticket=new ThreadTest3();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
2、模拟龟兔赛跑
public class ThreadTest4 implements Runnable {
private String wine;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟让白兔睡觉
if (Thread.currentThread().getName().equals("白兔")&&i%10==0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean b = gameOver(i);
if(b){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
//模拟胜利者
public boolean gameOver(int steps){
if(wine!=null){
return true;
}{
if (steps>=100){
wine=Thread.currentThread().getName();
System.out.println("胜利者为"+wine);
return true;
}
}
return false;
}
public static void main(String[] args) {
ThreadTest4 threadTest4=new ThreadTest4();
new Thread(threadTest4,"乌龟").start();
new Thread(threadTest4,"白兔").start();
}
}
3、Lamda表达式
避免匿名内部类定义过多;
对于函数式接口,可以通过Lamda表达式来创建该接口的对象。
函数式接口:接口中只包含唯一一个抽象方法。那么它就是一个函数式接口。
public class LambdaTest1 {
//4、静态类
static class Like2 implements Ilike{
@Override
public void like() {
System.out.println("i love you 1");
}
}
public static void main(String[] args) {
//5、局部内部类
class Like3 implements Ilike{
@Override
public void like() {
System.out.println("i love you 2");
}
}
//3、创建对象
Ilike ilike=new Like();
ilike.like();
Like2 like2 = new Like2();
like2.like();
Like3 like3 = new Like3();
like3.like();
//匿名内部类,无类名,依赖于接口或是父类
ilike= new Ilike(){
@Override
public void like() {
System.out.println("i love you 3");
}
};
ilike.like();
//lambda
ilike=()-> { System.out.println("i love you 4");};
ilike.like();
}
}
4、线程状态
4.1、概念
4.2、线程方法
4.2.1 、停止线程
不直接使用Jdk提供的stop().destroy()方法;
使用一个标志位进行终止线程,如当flag=false,则终止线程运行。
public class TestStop implements Runnable {
private boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("Thread....run"+i++);
}
}
//自己设置停止方法
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 600; i++) {
System.out.println("main"+i);
if (i==300){
testStop.stop();
System.out.println("该线程已停止");
}
}
}
}
4.2.2 、线程休眠
Sleep可以模拟网络延时,倒计时等;
每一个对象都有一个锁,sleep不会释放锁。
//模拟Sleep倒计时、显示系统时间
public class TestSleep {
public static void main(String[] args) {
//tenDown();
//显示当前系统时间
Date date=new Date(System.currentTimeMillis());
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前时间为"+new SimpleDateFormat("HH:mm:ss").format(date));
date=new Date(System.currentTimeMillis());
}
}
//模拟倒计时
public static void tenDown(){
int num=10;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("num="+num--);
if(num<=0){
break;
}
}
}
}
4.2.3 、线程礼让
礼让线程,让当前正在执行的线程暂停,但不堵塞;
礼让不一定成功,由cpu调度;
public class TestYield {
public static void main(String[] args) {
MyYield myYield=new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止");
}
}
4.2.3 、Jion
相当于插队
//测试join 可以想象成插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("vip客户来了"+i);
}
}
public static void main(String[] args) {
TestJoin testJoin=new TestJoin();
Thread thread=new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
System.out.println("main在执行"+i);
if(i==200){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
4.2.3 、线程状态State
//测试线程的状态
public class TestState {
public static void main(String[] args) {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("run...");
}
});
Thread.State state = thread.getState();
System.out.println("对象创建"+state);//new
thread.start();
state=thread.getState();
System.out.println("就绪状态"+state);//启动
while (state!=Thread.State.TERMINATED){//线程不终止就一直获取状态
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state=thread.getState();
System.out.println(state);
}
//线程死了,判断是否可以再次开启线程
//thread.start();
}
4.2.3 、线程优先级Priority
//设置线程优先级
public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
MyPriority myPriority=new MyPriority();
Thread t1=new Thread(myPriority);
Thread t2=new Thread(myPriority);
Thread t3=new Thread(myPriority);
Thread t4=new Thread(myPriority);
Thread t5=new Thread(myPriority);
//设置优先级再启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
t4.setPriority(5);
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
}
}
4.2.4 、守护线程daemon
线程分为用户线程和守护线程;
虚拟机必须确保用户线程执行完毕;
虚拟机不用等待守护线程执行完毕。
//测试守护线程
public class TestDaemon {
public static void main(String[] args) {
Game game=new Game();
You you =new You();
Thread thread = new Thread(game);
thread.setDaemon(true);//默认为false.开启守护线程
thread.start();
new Thread(you).start();
}
}
class Game implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("运行游戏");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("游戏开始");
}
}
}
4.2.5 、线程同步
由于同一进程的多个线程共享同一块存储空间,访问时会发生冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制synchronized。
包含两种用法:synchronized方法和synchronized块
synchronized方法控制对“对象”的访问,每个对象对应一把锁。
5、死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而
导致两个或者两个以上的线程都在等待对方释放资源,都处于停止的执行的情形。
5.1、死锁避免的方法
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用;
2、请求与等待条件:一个进程因请求资源而阻塞时,对已有的资源保持不放;
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
4、循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
6、lock锁
Lock锁是显示锁(手动开启和关闭锁),而synchronized是隐式锁,出了作用域自动释放;
Lock只有代码块锁,synchronized有代码块锁和方法锁;
7、线程通信
void notify()
唤醒在此对象监视器上等待的单个线程。
void notifyAll()
唤醒在此对象监视器上等待的所有线程。
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void wait(long timeout)
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出IllegalMonitorStateException
7.1、方式一
生产者/消费者模式–>管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//生产者消费者问题:管程法
//生产者、消费者、产品、缓存区
public class TestPC2 {
public static void main(String[] args) {
SynPlace synPlace=new SynPlace();
new Productor(synPlace).start();
new Customer(synPlace).start();
}
}
//生产者
class Productor extends Thread{
SynPlace synPlace;
public Productor( SynPlace synPlace){
this.synPlace=synPlace;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
synPlace.push(new Chicken(i));
System.out.println("生产者生产了"+i+"只鸡");
}
}
}
//消费者
class Customer extends Thread{
SynPlace synPlace;
public Customer( SynPlace synPlace){
this.synPlace=synPlace;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("消费者消费了第" + synPlace.pop().id + "只鸡");
}
}
}
//产品
class Chicken {
//产品编号
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynPlace{
//容器大小
Chicken[] chicken=new Chicken[10];
int count=0;
//生产者放入产品
public synchronized void push(Chicken chickens){
//如果容器满了,需要等待消费者消费
if(count==chicken.length){
//通知消费者消费,生产者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,则继续生产
chicken[count]=chickens;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断是否有产品
if(count == 0){
//通知生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Chicken chickens = chicken[count];
//通知生产者生产
this.notifyAll();
return chickens;
}
}
7.1、方式二
标志位切换法
/生产者消费者:标志位切换法
public class TestPC1 {
public static void main(String[] args) {
Tv tv=new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--》表演者
class Player extends Thread {
Tv tv;
public Player(Tv tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
tv.play("快乐大本营");
}else{
tv.play("周亚偶记");
}
}
}
}
//消费者--》观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品 --》节目
class Tv{
String voice;
boolean flag=true;
//表演
public synchronized void play(String voice){
//观众收看,演员等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演的节目"+voice);
this.voice=voice;
//通知观众收看
this.notifyAll();
this.flag=!this.flag;
}
public synchronized void watch(){
if (flag){
//观众等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众收看了"+voice);
//通知演员
this.notifyAll();
this.flag=!this.flag;
}
}