一、多线程的常用方法及API
java.lang.Thread 类中提供了大量的相关的方法:
new Thread();
new Thread(name);
new Thread(Runnable,name);
new Thread(Runnable)
常用方法:
getId() :获取线程的唯一标识
getName() :获取线程名
getPriority():获取线程的优先级: 优先级从1-10 , min-priority:1 max-priority:10 norm- priority:5 注意说明优先级高的获取到cpu资源的概率越大,并不是一定会优先执行完成。
currentThread():获取当前线程的对象引用
getState():获取线程的状态 (这是返回线程状态的枚举, NEW:未启动,RUNABLE:可运行 BLOCK:阻塞状态, WAITING:等待状态TIMED-WAITIN: 等待另一个线程执行完)
interrupt():中断这个线程
isInterrupted(): 返回boolean 测试当前线程是否中断
isAlive():该线程是否处于活动状态
isDaemon():判断该线程是否是守护线程
setDaemon():设置该线程是否是守护线程
join() :合并线程,使它变为单线程
sleep(ms) :让当前线程休眠 ,休眠时间到了,自动唤醒
yield(): 让出cpu资源,使当前线程处理可运行状态(可运行状态也随时可以获取cpu资源)
案例1: 测试线程的基本属性
System.out.println("当前主线程:"+Thread.currentThread().getName());
System.out.println("主线程id:"+Thread.currentThread().getId());
//设置主线程的线程级别
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
System.out.println("主线程的线程级别:"+Thread.currentThread().getPriority());
// 创建线程对象
MyThread my = new MyThread();
//设置线程名
my.setName("线程A");
//设置优先级
my.setPriority(10);
my.start();
MyThread my1 = new MyThread();
my1.setName("线程B");
//设置优先级 线程A 获取到资源的概率 大于线程B (大概率线程A优先执行完)
my1.setPriority(1);
//新生态
System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());
my1.start();
//可运行状态(就绪)
System.out.println("线程"+my1.getName()+"状态-----"+my1.getState());
for(int i = 0;i<100;i++){
System.out.println("主线程------"+i);
}
守护线程
案例2: 守护线程
线程类型分为两种 一种是用户线程一种是守护线程,用户线程是执行某一个任务的独立代码 ,守护线程是用于守护用户线程的线程, 它的特点是 当用户线程执行完毕后守护现在自动结束,当用户线程没有执行完, 守护线程也不会停止
操作系统中有守护进程 ,用于操作系统的运行,只有关机进程自动结束,这里守护线程和守护进程类似。
//创建线程对象
DaemonThread daemonThread = new DaemonThread();
//设置该线程为守护线程 守护的是与它并行的线程类 ,当主线程或其他线程执行完毕,守护线程自动结束
// daemonThread.setDaemon(true);
System.out.println("是否是守护线程:"+daemonThread.isDaemon());
daemonThread.start();
for(int i=0;i<100;i++){
System.out.println("主线程i------"+i);
}
活动的线程总数: Thread.activeCount()
线程中断
案例3: 关于终止线程
线程中止就是当线程运行时由于满足特定的条件需要停止运行,此时我们需要考虑如何安全的中止线程这里中止线程提供几个方法
方法1 : 打标记中断法
线程运行1000,当程序达到500时,中止程序
public class ThreadEnd extends Thread {
@Override
public void run() {
boolean isOver=false;
for(int i = 0 ;i<1000;i++){
if(i>=500){
isOver= true;
return ;
}
System.out.println("线程结果i-----------"+i);
}
System.out.println("正常结束");
}
public static void main(String[] args) {
ThreadEnd th = new ThreadEnd();
th.start();
}
}
方法2: 异常中断法
- interrupt() :给线程打一个中断标记,不会立马中断
- interrupted() : 检测线程是否中断,并清除中断标记,返回boolean ,如果线程打标记了,就返回true
- isInterrupted() : 检测线程是否中断,但不清除中断标记, 返回boolean
注意用法: interrupted() : 它所处于的位置,对应于它作用的位置 ,通过线程类名调用
interrupt() 和 isInterrupted() : 使用线程对象调用。
public class Thread1 extends Thread {
@Override
public void run() {
int i =0;
while(true){
System.out.println("线程--------------"+i);
//判断当前线程是否有中断标记 ,但是不清除中断标记
if(this.isInterrupted()){
// 通过抛出异常或 break
System.out.println("当前线程打中断标记,可以停止了");
break;
}
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread1 th = new Thread1();
th.start();
// 休眠一会儿
Thread.sleep(2000);
//给th打中断标记
System.out.println("打标记");
th.interrupt(); //给th打标记
}
3个方法的用法
// Thread.currentThread().interrupt();
// System.out.println("判断当前线程是否打标记 (清除标记):"+ Thread.interrupted());
System.out.println("判断线程是否打标记(不清除标记)"+ Thread.currentThread().isInterrupted());
System.out.println("判断当前线程是否打标记 (清除标记):"+ Thread.interrupted()); // 静态方法
join用法
案例四: join的用法: 合并当前线程 ,使其变为单线程 ,哪个线程调用join方法,就立即将该线程剩下的部分执行完成,再执行其他线程
public class ThreadJoin extends Thread {
@Override
public void run() {
ThreadJoin2 th = new ThreadJoin2();
th.setName("线程C");
th.start();
for(int i=0;i<100;i++){
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
threadJoin.setName("线程A");
threadJoin.start();
// ThreadJoin threadJoin2 = new ThreadJoin();
// threadJoin2.setName("线程B");
// threadJoin2.start();
for(int i=0;i<100 ;i++){
if(i==50){
// 合并线程 (threadJoin线程的所有代码合并到 主线程中,先执行threadJoin线程)
threadJoin.join();
}
// if(i==70){
// threadJoin2.join();
// }
System.out.println("main---"+i);
}
}
sleep用法
案例五: sleep的用法: 用于休闲当前线程 ,休眠时间结束后自动唤醒继续执行,如果同时有多个线程执行 ,如果线程没有同步的情况下,相互休眠不影响,资源被公用。
public static void main(String[] args) {
for(int i =0;i<10;i++){
try {
//让当前线程休眠200毫秒 200毫秒后自动唤醒线程 继续执行
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
class ThreadSleep implements Runnable{
@Override
public void run() {
for(int i =0;i<100;i++){
try {
Thread.sleep(1000); // 当前线程休眠时 不影响其他线程执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
ThreadSleep obj = new ThreadSleep();
Thread th1 = new Thread(obj , "线程A");
Thread th2 = new Thread(obj , "线程B");
th1.start();
th2.start();
yield用法
案例六:yield的用法 : 出让cpu, 让当先线程变为可运行状态 ,并也可以继续抢占cpu资源
public static void main(String[] args) {
ThreadYiled th = new ThreadYiled();
th.start();
// yield 让出cpu资源
for(int i = 0;i<100;i++){
if(i==50){
//主线程让cpu
System.out.println("让出cpu");
Thread.currentThread().yield();
}
System.out.println("主线程----"+i);
}
}
二、线程同步
同步的解决办法:
1、将需要操作公共资源的代码增加 “同步锁” (也叫互斥锁)
语法:
synchronized(对象锁){
代码块
}
注意这里的对象锁必须满足 两个线程是同一个对象(同一把锁)
public void run() {
System.out.println("开始取钱了");
// 增加互斥锁,协同步伐 ,这个锁必须是公有的对象
synchronized(account) {
//先判断账户余额是否足够
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取钱");
System.out.println(Thread.currentThread().getName() + "正在取钱");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了钱,卡里余额还剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里余额不足,不能取1000元");
}
}
// 以上代码需要将操作通过资源的代码块 增加同步关键字
}
2、 同步方法
在方法的返回值前面增加 “synchronize” , 此时的锁代表的是当前this对象
public synchronized void get(){
//先判断账户余额是否足够
if (account.getMoney() >= 1000) {
System.out.println(Thread.currentThread().getName() + "可以取钱");
System.out.println(Thread.currentThread().getName() + "正在取钱");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
account.setMoney(account.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() +
"取到了钱,卡里余额还剩:" + account.getMoney());
} else {
System.out.println("抱歉,卡里余额不足,不能取1000元");
}
}
同步代码块和同步方法的区别:
1、语法不同,同步代码块更灵活,可以自定义锁对象 ,而同步方法不可以指定锁对象
一、线程死锁
线程死锁的产生
线程同步可以帮助我们解决多个线程操作同一个资源而导致数据不安全的问题,但线程同步也有可能产生隐患,假如一个线程中出现多个锁对象时,可能出现锁使用不当,导致锁与锁之前相互等待对方释放资源,从而形成一种 “相互等待”的僵局,这就是线程死锁。 例如哲学家吃饭
模拟线程死锁
public class DeadThread implements Runnable {
Object obj1 = new Object();
Object obj2 = new Object();
@Override
public void run() {
// 模拟线程死锁
// 如果当前线程为线程A 先拿到obj1锁 ,等待obj2锁资源
// 如果当前线程为线程B 先拿到obj2锁 ,等待obj1锁的资源
if(Thread.currentThread().getName().equals("线程A")){
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"拿到了obj1的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj2锁。。。。。");
synchronized (obj2){
System.out.println("我已经拿到了obj2锁。。。。");
}
}
}else{
//线程B
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"拿到了obj2的锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我在等待obj1锁。。。。");
synchronized (obj1){
System.out.println("我已经拿到了obj1锁,我和开心");
}
}
}
}
}
public static void main(String[] args) {
DeadThread dead = new DeadThread();
Thread th1 = new Thread(dead,"线程A");
Thread th2 = new Thread(dead,"线程B");
th1.start();
th2.start();
}
之所以会发生死锁,因为对象锁直接没有良好的“沟通”,导致互相获取对方的锁 ,进入等待中 ,可以通过线程类的几个方法 解决线程之间通信问题
二、线程通信的几个方法
wait() : 让当前线程处于等待中,会释放对象锁 ,但是不会自动唤醒,需要其他线程唤醒
notify() : 唤醒等待中的一个线程
notifyAll: 唤醒所有等待中的线程
他们都属性Object的方法,需要相同的对象 ,使用时 通过Object的对象调用
注意: 以上方法的调用必须满足两个条件: a、他们必须在同步代码块中执行, b、调用该方法的对象是锁对象
案例1:模拟3个人,张飞、李逵和刘备,来买电影票,售票员只有一张5元的钱,电影票5元钱一张。
-
张飞拿20元一张的人民币排在李逵和刘备的前面,李逵和刘备各拿了一张5元的人民币买票。
package com.j2008.waitnotify; /** * ClassName: TicketThread * Description: * date: 2020/11/10 11:27 * * @author wuyafeng * @version 1.0 softeem.com */ public class TicketThread implements Runnable{ //公共资源: int fiveCount=1; int twentyCount =0; @Override public void run() { //开始买票 如果是张飞,他是20元面值,其他都是5元 if(Thread.currentThread().getName().equals("张飞")){ //20元面值买票 takeTicket(20); }else{ // 5元面值买票 takeTicket(5); } } /** * 买票过程 给方法加同步 ,锁对象默认是 方法 * @param money */ public synchronized void takeTicket(int money){ if(money ==20){ // 验证 当前公共资源 中是否有3张5元 while(fiveCount<3){ //等待 System.out.println(Thread.currentThread().getName()+"不能买到票,要继续等待"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 程序执行这里,说明 fiveCount >=3 System.out.println(Thread.currentThread().getName()+"正在买票。。"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fiveCount-=3; twentyCount++; System.out.println(Thread.currentThread().getName()+"已经买到了票。。"); }else{ System.out.println(Thread.currentThread().getName()+"正在买票。。"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } fiveCount++; System.out.println(Thread.currentThread().getName()+"已经买到了票。。"); // 唤醒等待的线程 this.notify(); } } }
public static void main(String[] args) { TicketThread ticketThread = new TicketThread(); Thread th1 = new Thread(ticketThread,"张飞"); Thread th2 = new Thread(ticketThread,"李逵"); Thread th3 = new Thread(ticketThread,"刘备"); //开启线程 th1.start(); th2.start(); th3.start(); }
三、线程的生产者和消费者模式
多个线程同时运行时,会产生线程并发可使用同步操作确保数据的安全性,如果需要各线程之间交互,可是使用线程等待和唤醒模式,在这里常用的等待唤醒中经典的模式为“生产者和消费者模式”
生产者和消费者由两类线程组成: 若干个生产者线程 负责提交用户的请求,若干个消费者线程负责处理生成出来的任务。 他们操作一块共享内存区进行数据通信。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v6DtAFB0-1605448559613)(assets/1604992093615.png)]
生成/消费的产品(数据): Mobile (手机编号)
生成者线程类: Provider : 无限制的生成手机
消费者线程类:Customer : 无限制的消费手机
共享存储区: Storage ( push 、pop) 存储手机的对象数组
测试类
public class Mobile {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Mobile(int id) {
this.id = id;
}
}
存储区
package com.j2008.provider_customer;
/**
* ClassName: Storage
* Description:
* date: 2020/11/10 15:32
* 存储区,它是生产者和消费者共享的空间 ( 生产者和消费者将该对象作为公有锁)
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Storage {
// 定义存储手机的对象数据
Mobile [] mobiles = new Mobile[10];
int index=0; // 个数
static int n=1000;
/**
* 存放手机
* @param mobile
*/
public synchronized void push(Mobile mobile){
//考虑容器上限已满,必须等待
while(index == mobiles.length){
System.out.println("容器已满,需等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//通知消费者取消费 ,将其他线程全部唤醒
this.notifyAll();
mobiles[index]=mobile;
index++;
}
/**
* 取出手机 1 2 3 4
* 1 2 3
* index--;
* mobile[index] =null
* @return 取出的手机对象
*/
public synchronized Mobile pop(){
Mobile m = null;
// 判断index是否小于0
while(index<=0){
//等待
System.out.println("容器中没有手机,需要等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
m = mobiles[index];
//将容器中的这个位置 设置为空
mobiles[index]=null;
// 通知生产者去生产
this.notifyAll();
return m;
}
public synchronized int getSize(){
return index;
}
}
生产者:
package com.j2008.provider_customer;
/**
* ClassName: Provider
* Description:
* date: 2020/11/10 15:54
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Provider implements Runnable {
//共享存储区
Storage storage =null;
public Provider(Storage storage){
this.storage = storage;
}
@Override
public void run() {
//手机编号
int n=1000;
//一直生产
while(true){
Mobile m = new Mobile(n);
storage.push(m);
System.out.println(Thread.currentThread().getName()+
"生产了一部手机,其编号:"+m.getId()+" 其库存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
n++;
}
}
}
消费者
package com.j2008.provider_customer;
/**
* ClassName: Customer
* Description:
* date: 2020/11/10 15:58
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class Customer implements Runnable {
Storage storage=null;
public Customer(Storage storage){
this.storage = storage;
}
@Override
public void run() {
while(true){
Mobile mobile = storage.pop();
System.out.println(Thread.currentThread().getName()+
"消费了一部手机,编号》》"+mobile.getId()+" 库存:"+storage.getSize());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类
package com.j2008.provider_customer;
/**
* ClassName: TestProviderCustomer
* Description:
* date: 2020/11/10 16:01
*
* @author wuyafeng
* @version 1.0 softeem.com
*/
public class TestProviderCustomer {
public static void main(String[] args) {
//创建公有的存储空间
Storage storage = new Storage();
Provider provider1 = new Provider(storage);
Provider provider2 = new Provider(storage);
Provider provider3 = new Provider(storage);
Thread th1 = new Thread(provider1,"张飞");
Thread th2 = new Thread(provider2,"刘备");
Thread th3 = new Thread(provider3,"关羽");
th1.start();
th2.start();
th3.start();
//消费者上场
Customer customer1 = new Customer(storage);
Customer customer2 = new Customer(storage);
Customer customer3 = new Customer(storage);
Thread th4 = new Thread(customer1,"张飞的老婆");
Thread th5 = new Thread(customer2,"刘备的老婆");
Thread th6 = new Thread(customer3,"关羽的老婆");
th4.start();
th5.start();
th6.start();
}
}
测试结果
关羽生产了一部手机,其编号:1000 其库存:1
张飞的老婆消费了一部手机,编号》》1000 库存:0
张飞生产了一部手机,其编号:1000 其库存:1
刘备生产了一部手机,其编号:1000 其库存:2
刘备的老婆消费了一部手机,编号》》1000 库存:1
关羽的老婆消费了一部手机,编号》》1000 库存:0
容器中没有手机,需要等待
关羽生产了一部手机,其编号:1001 其库存:0
张飞的老婆消费了一部手机,编号》》1001 库存:0
张飞生产了一部手机,其编号:1001 其库存:2
刘备生产了一部手机,其编号:1001 其库存:2
关羽的老婆消费了一部手机,编号》》1001 库存:0
刘备的老婆消费了一部手机,编号》》1001 库存:0
容器中没有手机,需要等待
关羽生产了一部手机,其编号:1002 其库存:0
张飞的老婆消费了一部手机,编号》》1002 库存:0
刘备生产了一部手机,其编号:1002 其库存:2
张飞生产了一部手机,其编号:1002 其库存:2
刘备的老婆消费了一部手机,编号》》1002 库存:1
关羽的老婆消费了一部手机,编号》》1002 库存:0
四、线程池
1、定义
用于创建和管理线程的容器就是线程池 (Thread Pool) ,在线程池中的线程执行完任务后不会立马进入销毁状态,而是重置到线程池中变为“空闲线程” 。 有利于避免频繁创建线程消耗资源,提供线程复用率,有限管理该线程。
2、使用线程池的原因:
在多线程环境下,对于不断创建和销毁效率非常消耗系统资源,对于多线程之间的切换存在线程安全问题, 这是使用统一的管理类管理一些线程是比较好的解决办法
3、线程的运行机制:
- 在线程池模式下,任务是提交给线程池,由线程池根据当前空闲线程进行分配任务,如果没有空闲线程,由管理类创建线程或者进入任务等待队列中。
- 一个线程同时只能执行一个任务,但多个任务可以同时提交给这个线程池。
线程池的常用类 (ExecutedService)
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
参数1: corePoolSize:核心线程数
参数2:maximumPoolSize :最大线程数
参数3: keepAliveTime : 线程活动时长
参数4: 对于参数3的单位
1、可缓存的线程池 newCacheThreadPool(n);如果线程池中没有空闲线程,则创建新线程并放入线程池中,无上限线程数,如果有空闲线程则直接使用该线程
public static void main(String[] args) {
// 创建可缓存线程
ExecutorService service =Executors.newCachedThreadPool();
// 创建10个线程
int n=0;
for(int i = 0 ;i < 10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override //你们内部类
public void run() {
//任务
System.out.println(Thread.currentThread().getName()+"---"+n);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//由于以上的线程 每次执行完成只需要500毫秒 ,会释放线程对象到线程池中
// 1000毫秒后创建的线程 就会复用上一次的线程对象
}
//关闭线程池
service.shutdown();
}
}
2、可重用的固定线程池: newFixedThreadPool(n) ,线程数量固定,如果没有空闲线程,则存放无界队列中等待
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(3);
// 连续创建10个线程, 由于只有3个线程,所有线程只能等待
// 2秒后再执行3个
for(int i =0;i<10;i++){
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 结果: 每2秒执行3个线程
}
3、 固定长度的可执行定时任务的线程池 newScheduledThreadPool , 类似于定时器线程
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟1秒后每3秒执行一次");
}
}, 1, 3, TimeUnit.SECONDS);
4、单线程的线程池 newSingleThreadExecutor : 线程池中只有一个线程数,所有的任务都通过该线程执行,它可以保证所有的任务是FIFO模式, 满足队列结构