线程
线程简单介绍
程序:程序是指令和数据的有序集合,它是静态的,没有运行的含义。
进程:进程就是执行程序的过程,它是动态的。是系统资源分配器的单位。
线程:进程可以分成多个单元同时运行,这些再被进程分配的多个单元叫做线程。线程是cpu调度的最小单位。
进程控制块:控制进程。里面存放进程的信息。
下面是关系图:
实际上,一个CPU,同一时刻只能执行一个线程。但是CPU运算速度很快,达到纳秒级别,我们在宏观上是多个任务同时执行,即并行。
多任务:同时执行多个任务。例如:边做什么边做什么。
多线程:多个线程同时执行。
线程的创建
三种创建线程的方式:继承Thread类、实现Runnable接口、实现Callable接口。
Thread类
该类实现了Runnable接口。
继承Thread类创建线程
创建步骤:
- 自定义一个类
- 继承Thread类
- 重写run方法
- 调用start方法,启动线程。
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我是线程1--"+ i);
}
}
public static void main(String[] args) {
//创建一个线程对象
MyThread1 myThread1 = new MyThread1();
//开启线程
myThread1.start();
//主线程
for (int i = 0; i < 2000; i++) {
System.out.println("我是主线程--" + i);
}
}
}
实现Runnable接口创建线程(推荐)
- 实现Runnable接口
- 重写run方法。
- 执行线程需要丢入runnable接口的实现类,调用start方法
//创建线程方式2:实现Runnable接口,重写run,执行线程需要传入runnable接口实现类,调用start()方法
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法线程体
for(int i = 0; i < 200; i++){
System.out.println("线程1正在运行-------"+i);
}
}
public static void main(String[] args){
//创建线程1
TestThread3 myThread = new TestThread3();
//开启线程
new Thread(myThread).start();
for(int i = 0; i < 2000; i++){
System.out.println("我是主线程正在运行----"+i);
}
}
}
模拟龟兔赛跑
//赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
//跑道为1000步,如果谁先跑1000步谁就赢
for (int i = 0; i <= 1000; i++) {
//模拟兔子睡觉,每10步兔子睡一次觉
if(Thread.currentThread().getName().equals("兔子")&& i%10 == 0 ) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
if(flag){
break;
}
//打印谁跑多少步
System.out.println(Thread.currentThread().getName()+"----跑了"+i+"步");
}
}
//判断胜利者
private boolean gameOver(int steps) {
if (winner != null) {
return true;
} else {
if (steps >= 1000) {
winner = Thread.currentThread().getName();
System.out.println(winner + "win");
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
//比赛开始
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
实现Callable接口创建线程
实现步骤:
- 实现callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务
- 提交服务
- 获取服务
- 关闭服务
图片下载案例:
//线程创建方式3,
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name){
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
WebDownloader1 webDownloader = new WebDownloader1();
webDownloader.downDownload(url,name);
System.out.println("下载文件名:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建目标对象
TestCallable testThread1 = new TestCallable("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1601280086&di=2572142a87268b8c40db22edb56399d8&src=http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg","1.jpg");
TestCallable testThread2 = new TestCallable("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1601280086&di=2572142a87268b8c40db22edb56399d8&src=http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg","2.jpg");
TestCallable testThread3 = new TestCallable("https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1601280086&di=2572142a87268b8c40db22edb56399d8&src=http://a3.att.hudong.com/14/75/01300000164186121366756803686.jpg","3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> result1 = ser.submit(testThread1);
Future<Boolean> result2 = ser.submit(testThread2);
Future<Boolean> result3 = ser.submit(testThread3);
//获取结果
boolean rs1 = result1.get();
boolean rs2 = result2.get();
boolean rs3 = result3.get();
System.out.println(rs1+"-"+rs2+"-"+rs3);
//关闭服务
ser.shutdownNow();
}
}
class WebDownloader1{
//下载方法
public void downDownload(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("io异常");
}
}
}
静态代理
静态代理模式:
-
目标对象和代理对象都要实现同一个接口
-
代理对象要代理目标对象
好处:代理对象可以做目标对象做不了的东西,而目标对象可以只关注自己的事情
结婚代理案例:
public class StaticProxy {
public static void main(String[] args) {
//委托人
You you =new You();
//代理人
WeddingCompany weddingCompany = new WeddingCompany(you);
//执行代理方法
weddingCompany.happyMarry();
}
}
//结婚接口
interface Marry{
//结婚
void happyMarry();
}
//结婚对象
class You implements Marry{
//某人结婚
@Override
public void happyMarry() {
System.out.println("某人结婚");
}
}
//婚庆公司
class WeddingCompany implements Marry{
//委托人,代理对象
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
//举办婚礼
@Override
public void happyMarry() {
System.out.println("布置现场");
this.target.happyMarry();
System.out.println("收拾现场");
}
}
线程的五大状态
线程的五大状态:创建状态,就绪状态、运行状态、阻塞状态、死亡状态。
线程停止
- 建议线程正常停止,利用次数,不建议死循环。
- 简历使用标志位,设置标志位。
- 不建议使用stop,destroy方法。
线程休眠
- sleep指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间到达后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等。
- 每一个对象都是锁,sleep不会释放锁
休眠案例:
public class TestSleep2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("倒计时:"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//10秒倒计时
new Thread(new TestSleep2()).start();
//获取系统当前时间
Date startTime = new Date(System.currentTimeMillis());
while (true){
try {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
Thread.sleep(1000);
startTime = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让
-
礼让方法:yield()
-
线程礼让,让当前执行的线程暂停,但不阻塞
-
让cpu重新调度,礼让不一定成功
-
将线程从运行转为就绪状态
线程强制执行——join
public class TestJoin implements Runnable{
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
if(i == 200){
thread.join();
}
System.out.println("main");
}
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程插队");
}
}
}
观察测试线程状态
用getState()获取当前线程的状态。
线程的优先级
-
Java提供一个线程调度器类监控程序中启动后进入就绪状态的所有线程,线程调度按照优先级决定应该调度哪个线程来执行
-
线程的优先级用数字表示,范围从1-10
-
getPriority()和setPriority(int xxx)用来设置和获取优先级
注意:先设置再start。高优先级不一定先调用。
public class TestPriority{
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+ "->" +Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority);
Thread thread2 = new Thread(myPriority);
Thread thread3 = new Thread(myPriority);
Thread thread4 = new Thread(myPriority);
Thread thread5 = new Thread(myPriority);
Thread thread6 = new Thread(myPriority);
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(4);
thread3.start();
thread4.setPriority(Thread.MAX_PRIORITY);
thread4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "->" +Thread.currentThread().getPriority());
}
}
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You1 you1 =new You1();
Thread thread1 = new Thread(god);
thread1.setDaemon(true);//默认false,代表用户线程
thread1.start(); //上帝线程启动
new Thread(you1).start();//你线程启动
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("上帝线程守护你线程");
}
}
}
//你
class You1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("你线程执行");
}
System.out.println("你线程结束");
}
}
线程同步(线程安全)
线程同步:出现在多个线程操作同一个资源。是一种等待机制。
线程同步:sychronized关键字。
并发:同一个对象被多个线程同时操作。
并发问题:
//多个线程同时操作同一个对象
//买火车票
public class TestThread4 implements Runnable{
//票数
private int ticketNums = 10;
@Override
public void run() {
while (ticketNums > 0) {
try {
//模拟延时
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "线程1拿票第" + ticketNums-- + "票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"小花").start();
new Thread(ticket,"小红").start();
}
}
执行结果:
小红线程1拿票第10票
小花线程1拿票第9票
小明线程1拿票第10票
小红线程1拿票第8票
小花线程1拿票第8票
小明线程1拿票第7票
小花线程1拿票第6票
小明线程1拿票第4票
小红线程1拿票第5票
小红线程1拿票第3票
小花线程1拿票第1票
小明线程1拿票第2票
sychronized
sychronized使用:
-
sychronized方法
在方法用sychronized修饰
public sychronized void buy(){ } //sychronized相当于一把锁,它把这个方法对应的对象锁着,一次只能一个人访问该对象。 //对于上述买车票问题线程同步实现 public sychronized void run() { while (ticketNums > 0) { try { //模拟延时 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程1拿票第" + ticketNums-- + "票"); } } //该锁锁的是TestThread4这个对象
-
sychronized块
sychronized(obj){ 实现; } //块锁的是该obj对象,一次只能一个人访问该对象,而不是方法对应的对象。 //将上述买票重新写一下,用锁块实现同步,这个锁的是ticket这个对象 class Ticket{ public int ticketNums = 20; } public class UnsafeBuyTicket implements Runnable{ //票数 private int ticketNums = 10; boolean flag = true; Ticket ticket = new Ticket(); @Override public void run() { //买票 while(flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } public void buy() throws InterruptedException { synchronized (ticket){ if(ticket.ticketNums <= 0){ this.flag = false; return; } Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName() + "买到第" + ticket.ticketNums-- +"张票"); } } public static void main(String[] args) { UnsafeBuyTicket unsafeBuyTicket = new UnsafeBuyTicket(); new Thread(unsafeBuyTicket,"小明").start(); new Thread(unsafeBuyTicket,"小华").start(); new Thread(unsafeBuyTicket,"小红").start(); } }
死锁
死锁:都在相互等待对方的资源。
产生死锁的4个必要条件:
- 互斥条件:一个支援每次只能被一个进程使用。
- 请求和保持条件:一个进程因请求资源而堵塞时,对方获得的资源褒词不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免程序中出现死锁。
锁(Lock)
Lock是接口,常用实现类ReentrantLock(可重复锁)。
import java.util.concurrent.locks.ReentrantLock;
//测试lock锁
public class TestLock {
public static void main(String[] args) {
TestLock1 testLock11 = new TestLock1();
new Thread(testLock11).start();
new Thread(testLock11).start();
new Thread(testLock11).start();
}
}
class TestLock1 implements Runnable{
int ticket = 10;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if(ticket >0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket--);
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
线程通信
生产者消费者 问题
管程法
//生产者消费者模型,管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
produce();
}
void produce() {
for (int i = 0; i < 30; i++) {
container.push(new Thing(i+1));
System.out.println("生产了第"+ (i+1) +"个产品");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 30 ; i++) {
System.out.println("消费了第"+ container.pop().id +"个产品");
}
}
}
//产品
class Thing{
int id;//产品编号
public Thing(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer{
Thing[] things = new Thing[10];
int count = 0;
//生产者放入产品
public synchronized void push(Thing thing){
//容器满,等待消费者取
if(count == things.length){
//等待消费者取
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
//容器未满,放产品
things[count++] = thing;
this.notifyAll();
}
//消费者取出产品
public synchronized Thing pop(){
//判断是否能消费
if(count == 0){
//等待生产者放产品
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Thing thing = things[count];
this.notifyAll();
return thing;
}
}
线程池
//ExecutorService 线程池接口
//Executors 线程池工具类
//创建线程池,参数位线程池中的线程个数
ExecutorService ser = Executors.newFixedThreadPool(2)
//Runnable接口用execute()无返回值
ser.execute(new RunnableImpl)
//Callable接口用submit()有返回值
Future<V> result = ser.submit(new CallableImpl);