今日内容
零、 复习昨日
一、作业
二、线程安全的集合
三、死锁
四、线程通信
五、线程池
零、 复习昨日
见晨考
一、线程安全[重点]
1.0 线程不安全
当前线程的数据被其他线程修改
1.1 线程安全
临界资源:共享资源(同⼀个对象),一次只可以有一个线程操作,才可以保证准确性
原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱
线程不安全: 1) 完整的步骤可能会被破坏 2) 线程内的数据可能被别的线程修改
1.2 实现线程安全方式
- 同步方法
- 给方法加锁,即设置同步锁关键词
synchronized
- 锁对象是当前对象,即this
- 同步代码块
- 将需要安全的代码使用同步代码块包裹,设置锁对象.
锁可以是任意对象
- 但是线程之前应该是
同一把锁
才能锁住,保证安全
其实就是给需要"同步",需要"安全",需要"步骤一致,不能打乱"的代码
加锁
.
需求: 要求打印机类的两个方法分别被两个线程同时执行.但是方法执行时不能被破坏,即方法执行时不能被另外一个线程抢走资源,要保证方法执行时步骤完整性.
1.2.1 同步方法
同步方法就是给方法设置synchronized关键词
注意事项:
- 方法要同步就都要上锁
- 需要同步的方法锁的得是同一个锁对象,即得是同一把锁
同步方法锁对象是this
,即得是同一个this才能锁住
public class Printer {
/**
* 同步方法,就是给方法设置synchronized关键字
* 同步方法锁对象是this
* 需要同步的代码必须是同一把锁(即相同锁对象)才能保证同步,否则无效
*/
public synchronized void print1() {
System.out.print("1 " );
System.out.print("2 " );
System.out.print("3 " );
System.out.print("4 " );
System.out.println();
}
public synchronized void print2() {
System.out.print("A " );
System.out.print("B " );
System.out.print("C " );
System.out.print("D " );
System.out.println();
}
}
public class TestSync1 {
public static void main(String[] args) {
Printer printer = new Printer( );
// 另一个对象
// Printer printer2 = new Printer( );
// 线程1执行打印1任务
new Thread(){
@Override
public void run() {
while (true){
printer.print1();
}
}
}.start();
// 线程2执行打印2任务
new Thread(){
@Override
public void run() {
while (true){
printer.print2();
// 另一个对象调用方法,虽然方法加锁了,但是锁对象不一样,没用!!!
// printer2.print2();
}
}
}.start();
}
}
1.2.2 同步代码块
ps: 代码块其实就是{}包裹着一段代码
同步代码块,就是使用synchronized关键词+{}包裹着的一段代码
作用: 被同步代码块包裹的代码是线程安全的,即同步,运行时是原子操作
语法: synchronized(对象){ 需要同步的代码 }
注意事项:
- 锁对象是任意对象
- 但是需要同步的方法锁对象得是同一个
public class Printer2 {
private Object obj = new Object();
/**
* synchronized (锁对象){}
* 锁对象可以是任意对象,但是需要同步的方法锁对象得是同一个
*/
public void print1() {
synchronized (obj){
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n ");
}
}
public void print2() {
synchronized (obj) {
System.out.print("A ");
System.out.print("B ");
System.out.print("C ");
System.out.print("D ");
System.out.print("\r\n ");
}
}
}
public class TestSync1 {
public static void main(String[] args) {
Printer2 printer = new Printer2( );
// 线程1执行打印1任务
new Thread(){
@Override
public void run() {
while (true){
printer.print1();
}
}
}.start();
// 线程2执行打印2任务
new Thread(){
@Override
public void run() {
while (true){
printer.print2();
}
}
}.start();
}
}
1.2.3 区别
- 同步方法
- 是在方法上加synchronized,即整个方法上锁
- 锁对象是this
- 锁整个方法,锁的范围大
- 同步代码块
- 是在方法内部给局部代码添加synchronized,即局部代码上锁
- 锁对象是任意对象
- 锁部分代码,锁的范围小
总结: 同步方法会锁整个方法,同步代码块锁部分代码,如果方法内不是必须所有都同步,那么同步代码块最合适.
1.3 转账案例
需求: 假设银行账户类,属性有卡号,余额,名字 ; 另外有ATM机器,机器内能接收账户,给ATM指定取的钱数.ATM支持多线程操作.
账户类
public class Account {
private String num;
private double money;
private String name;
public Account() {
}
public Account(String num, double money, String name) {
this.num = num;
this.money = money;
this.name = name;
}
public String getNum() {
return num;
}
public void setNum(String num) {
this.num = num;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用Runnable接口实现
public class ATM implements Runnable {
private Account account;
private double money;// 要取出的钱
// 取钱动作要并发执行
/**
* 这里不能使用同步方法,因为创建两个线程对象
* 两个线程对象启动线程,调用run,那么run上面的synchronized锁对象
* 就不是同一个,锁不住,无法保证线程安全
* ---------------------------------------
* 换成同步代码块,保证线程运行时,synchronized()中的锁是同一个对象就行
*/
@Override
public /*synchronized*/ void run() {
while (true) {
synchronized (account) {
// 要取的钱<=账户余额
if (money <= account.getMoney( )) {
//t1,t2进
// t1抢到
// 获得线程对象
Thread thread = Thread.currentThread( );
System.out.println(thread.getName( ) + "正在取出" + money + "元");
// 账户余额要减少
double a = account.getMoney( );
double b = a - money;
account.setMoney(b);
// t1结束,t2抢回去
// t1又抢回来,取出最新值-100
// 获得最新余额
double balance = account.getMoney( );
System.out.println(thread.getName( )+"账户余额:" + balance);
} else {
System.out.println("余额不足!");
break;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}
public ATM(Account account, double money) {
this.account = account;
this.money = money;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
public class TestATM {
public static void main(String[] args) {
// 来张银行卡
Account account = new Account("666666", 1000, "张三");
// 来一个ATM机,给设置账户,设置要取的钱
ATM atm = new ATM(account, 100);
// 创建线程并启动
new Thread( atm ,"ATM1号").start();
// 再来一个ATM机,给设置账户,设置要取的钱
ATM atm2 = new ATM(account, 100);
// 创建线程并启动
new Thread( atm2 ,"ATM2号").start();
}
}
练习: 实现继承Thread实现
1.4 火车票案例
售票: 有一个窗口类售票,总共100张票,窗口不止一个可以多个窗口同时售票. 要保证票不能卖超,不能卖重复的票
package com.qf.exercise2;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 售票窗口
*/
public class TicketWindow extends Thread{
private static int ticketNum = 100;
public TicketWindow(String name){
super(name);
}
/**
* 售票功能,需要并行操作(多个窗口同时售卖)
*/
@Override
public void run() {
while (true) {
synchronized(TicketWindow.class) {
if (ticketNum > 0) {
System.out.println(getName( ) + "正在售出第" + ticketNum + "张票");
ticketNum--;
// 让线程陷入阻塞,模拟出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace( );
}
} else {
System.out.println("票已售罄!");
break;
}
}
}
}
}
public class TestTicket {
public static void main(String[] args) {
new TicketWindow("窗口[1]").start();
new TicketWindow("窗口[2]").start();
new TicketWindow("窗口[3]").start();
}
}
1.5 sleep问题[重点]
非线程安全的情况下,sleep会让出系统资源,让线程执行
线程安全的情况下,即加锁的情况下(在同步方法/代码内)使用sleep,不会让出系统资源,别的线程不能执行 -->
抱着锁睡
public static void main(String[] args) {
new Thread( "线程1" ){
@Override
public void run() {
// 加锁
synchronized (Demo2.class) {
for (int i = 1; i < 1000; i++) {
if (i == 5) {
// 加锁后休眠,不释放资源
// 即其他线程无法执行,都处于阻塞状态
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
System.out.println(getName( ) + "--->" + i);
}
}
}
}.start();
new Thread( "线程2" ){
@Override
public void run() {
// // 加锁
synchronized (Demo2.class) {
for (int i = 1; i < 1000; i++) {
System.out.println(getName( ) + "--->" + i);
}
}
}
}.start();
}
1.6 保证线程安全的方式
不只有synchronized同步方法可以保证线程安全,其实还有很多
ThreadLocal 线程本地变量
Volatile 共享变量
Lock 锁
ReentrantLock 重入锁
等
二、线程安全的类[了解]
ArrayList是线程不安全
Vector 是线程安全
HashMap 是线程不安全 , 快
Hashtable 是线程安全 , 慢
比HashMap安全,比Hashtable快,即安全又快的集合ConcurrentHashMap[很重要]
ConcurrentHashMap类所在包是java.util.concurrent, 是并发包,简称JUC
三、死锁[了解]
死锁是指两个或多个线程(或进程)在互相等待对方释放资源的情况下陷入无限等待的状态。每个线程都在等待其他线程所持有的资源,同时又不释放自己已经持有的资源,导致所有线程都无法继续执行下去。
互相持有对方的锁还不释放
public class MyLock {
// 左筷子锁
static Object zuo = new Object();
// 右筷子锁
static Object you = new Object();
}
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
synchronized (MyLock.LEFT){
System.out.println("女朋友拿到左筷子" );
synchronized (MyLock.RIGHT){
System.out.println("女朋友拿到右筷子-吃饭" );
}
}
}
}.start();
new Thread(){
@Override
public void run() {
synchronized (MyLock.RIGHT){
System.out.println("男朋友拿到右筷子" );
synchronized (MyLock.LEFT){
System.out.println("男朋友拿到左筷子-吃饭" );
}
}
}
}.start();
}
解决死锁问题的方法通常包括以下几种:
- 预防死锁:通过破坏死锁四个必要条件之一来预防死锁。例如,避免使用不必要的资源互斥,确保线程在申请资源时不保持已有资源,引入资源的优先级等。
- 避免死锁:使用资源分配算法来避免系统进入死锁状态。如银行家算法(Banker’s algorithm)。
- 检测与恢复:通过周期性检测系统中的死锁状态,然后采取相应的恢复措施。常用的方法是使用图论中的资源分配图(Resource Allocation Graph)进行检测,并采取抢占资源或终止某些线程的策略来解除死锁。
- 忽略死锁:某些情况下,可以通过对系统进行建模与分析,确定死锁发生的概率非常低,因此可以选择忽略死锁的处理。
四、线程通信[熟悉]
4.1 介绍[重要]
线程通信的前提是得保证线程安全(同步)
线程通信,就是线程之间产生联系.
即通知,例如线程A执行到一定时候会
停下
,同时通知
另外的线程B执行,
线程B执行到一定时候,也停下,通知线程A执行
以上操作需要**
Object类
**的方法
- wait() 让当前线程等待
- notify() 唤醒一个处于等待状态的线程
- notifyAll() 唤醒所有处于等待状态的线程
4.2 两个线程通信
需求: 之前打印机方法,让print1()和print2()方法交替执行
package com.qf.notify;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 通信
*/
public class Printer {
/**
* 打印机执行的标志
* 此标志如果是1,说明该打印机1执行,否则打印机1停下
* 此标志如果是2,说明该打印机2执行,否则打印机2停下
*/
private int flag = 1;
public synchronized void print1() throws InterruptedException {
if (flag != 1) { // 判断不是自己执行时
// 线程停下不执行
// 锁对象是谁,谁去wait方法
this.wait();
}
System.out.print("1 " );
System.out.print("2 " );
System.out.print("3 " );
System.out.print("4 " );
System.out.print("\r\n" );
// 改变标志
flag = 2;
// 通知其他处于等待状态的线程,起来干活
// 锁对象是谁,谁去notify方法
this.notify();
}
public synchronized void print2() throws InterruptedException {
if ( flag != 2) {
this.wait();
}
System.out.print("A " );
System.out.print("B " );
System.out.print("C " );
System.out.print("D " );
System.out.print("\r\n" );
flag = 1;
this.notify();
}
}
public class TestNotify {
public static void main(String[] args) {
Printer printer = new Printer( );
new Thread(){
@Override
public void run() {
while(true){
try {
printer.print1();
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
try {
printer.print2();
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}
}.start();
}
}
换用同步代码块实现
package com.qf.notify;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc 通信
*/
public class Printer2 {
Object obj = new Object();
private int flag = 1;
public void print1() throws InterruptedException {
synchronized (obj) {
if (flag != 1) { // 判断不是自己执行时
// 线程停下不执行
// 锁对象是谁,谁去wait方法
obj.wait( );
}
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n");
// 改变标志
flag = 2;
// 通知其他处于等待状态的线程,起来干活
// 锁对象是谁,谁去notify方法
obj.notify( );
}
}
public void print2() throws InterruptedException {
synchronized (obj) {
if (flag != 2) {
obj.wait( );
}
System.out.print("A ");
System.out.print("B ");
System.out.print("C ");
System.out.print("D ");
System.out.print("\r\n");
flag = 1;
obj.notify( );
}
}
}
总结
- 通信的代码(wait和notify等)需要放在同步方法或者同步代码块里面
- 通信的代码(wait和notify等)必须使用当前锁对象来调用
4.3 练习
创建A1 A2 两个线程,分别打印1-10,11-20,保证A1执行完再 执行A2线程,A2执行完再执行A1
A1 —> 1
A2 —>11
A1 —> 2
A2 —>12
package com.qf.exercise3;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class A {
private int flag = 1;
public synchronized void a1() throws InterruptedException {
for (int i = 1; i < 11; i++) {
if (flag != 1) {
this.wait();
}
System.out.println("A1 -->" + i);
flag = 2;
this.notify();
}
}
public synchronized void a2() throws InterruptedException {
for (int i = 11; i < 21; i++) {
if (flag != 2) {
this.wait();
}
System.out.println("A2 -->" + i);
flag = 1;
this.notify();
}
}
}
package com.qf.exercise3;
import com.qf.notify.Printer;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestNotify {
public static void main(String[] args) {
A a = new A( );
new Thread("打印机1") {
@Override
public void run() {
try {
a.a1( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}.start( );
new Thread("打印机2") {
@Override
public void run() {
try {
a.a2( );
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
}.start( );
}
}
4.4 总结[重要]
特殊的:
- wait和notify方法需要在同步方法或者同步代码块内执行
- wait和notify方法需要使用当前锁对象调用
- wait会让当前线程进入等待状态,让出资源,其他线程可以执行
问:wait和sleep有什么区别?
答:
相同点:
- wait和sleep都可以让当前线程进入阻塞状态
不同点
- wait是Object类的方法,sleep是Thread类方法
- 但是wait阻塞当前线程,会让出系统资源,其他线程可执行;但是sleep阻塞当前线程,会持有锁不释放,其他线程无法执行
- wait需要在同步方法或同步代码快中使用,但是sleep可以在同步或非同步都可以使用
问:为什么wait和notify方法要设计在Object类中?
答: 因为锁可以是任意对象,又因为wait和notify需要被 锁对象调用,那么wait和notify方法也能被任意对象调用,所以就设计在Object类中,因为Object类是所有类的父类
五、线程池[面试]
5.1 线程池概念
- 如果有非常多的任务需要非常多的线程来完成,每个线程的工作时间不长,就需要创建很多线程,工作完又立即销毁[
线程频繁创建和销毁线程
]- 频繁创建和销毁线程非常消耗性能,那么线程池,就是可以创建一些线程,放在"池子"中,用的时候去池子取一个线程去使用,使用完再放回去,线程可以重用
- 线程池,底层其实就是集合队列,里面存储线程对象,用的时候去抽即可,就不要频繁创建线程了
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资 源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存(OOM Out Of Memory)或者“过度切换”的问题 --> 摘自阿里官方手册
5.2 线程池原理
将任务(task)提交(submit/execute)给线程池(threadpool),由线程池分配线程,运行任务,任务结束后,线程重新放入线程池供后续线程使用
5.3 创建线程池的方式
使用线程池创建线程,执行任务
JDK提供了关于线程池的一些类和接口
- Executor: 线程池的根接口,通过execute(Runnable task) 执行任务
- ExecutorService: Executor的子接口,通过submit(Runnable task) 来提交任务,执行任务,并且可以获得返回值
ThreadPoolExecutor
: ExecutorService的子实现类,通过submit(Runnable task) 来提交任务,执行任务Executors
: 执行器类(线程池工厂类),通过该类提供的静态方法来获得不同特点的线程池对象
5.4 不同特点的线程池
通过Executors调用以下静态方法获得不同特点的线程池对象
方法 类型 解释 newFixedThreadPool 固定大小线程池 池中包含固定数目的线程,空闲线程一直保留。只有核心线程,线程数量固定,任务队列为LinkedBlockingQueue newCachedThreadPool 动态大小的线程池,原则上无上限 无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列SynchronousQueue newScheduledThreadPool 可以执行定时任务的线程池 用于调度执行的固定线程池,执行定时或周期性任务。和弦线程数量固定,非核心线程数量无线,执行完闲置10ms后回收,任务队列为DelayedWorkQueue newSingleThreadExecutor 单线程线程池 只有一个线程的池,会顺序执行提交的任务,只有一个核心线程,无非核心线程,任务队列为LinkdBlockingQueue newSingleThreadScheduledExecutor 单线程定时任务线程池 newWorkStealingPool 1.8提供新的方式创建线程池
以上线程池操作在阿里java开发手册中是不建议用的…
说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 ----------------------- OOM 内存溢出,即系统资源耗尽
线程池执行任务时,可以采用两种方法:
execute(): 没有返回值,无法判断任务是否执行成功
submit():会返回Future对象,通过该对象判断任务是否执行成功
package com.qf.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestThreadPool {
public static void main(String[] args) {
/**
* 通过Executors工具类创建不同的线程池
*/
}
/**
* 单线程
*/
private static void show4() {
ExecutorService executor = Executors.newSingleThreadExecutor( );
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable( ) {
@Override
public void run() {
Thread thread = Thread.currentThread( );
String name = thread.getName( );
System.out.println(name+"执行任务" );
}
});
}
}
/**
* 调度线程池
* 会在一定时间后执行
*/
private static void show3() {
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 10; i++) {
// [注意:使用的是schedule方法,而不是execute方法]
// 此方法参数2,3是延时时间和单位
threadPool.schedule(new Runnable( ) {
@Override
public void run() {
Thread thread = Thread.currentThread( );
String name = thread.getName( );
System.out.println(name+"执行任务" );
}
},5, TimeUnit.SECONDS);
}
threadPool.shutdown();
}
/**
* 动态调整大小的线程池
* 即根据任务数调整池子大小
*/
private static void show2() {
ExecutorService threadPool = Executors.newCachedThreadPool( );
for (int i = 0; i < 100; i++) {
threadPool.execute(new Runnable( ) {
@Override
public void run() {
Thread thread = Thread.currentThread( );
String name = thread.getName( );
System.out.println(name+"执行任务" );
}
});
}
threadPool.shutdown();
}
/**
* 固定大小线程池
*/
private static void show1() {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
// for循环,模拟出现多个任务
for (int i = 0;i < 10;i++) {
threadPool.execute(new Runnable( ) {
@Override
public void run() {
Thread thread = Thread.currentThread( );
String name = thread.getName( );
System.out.println(name+"执行任务" );
}
});
}
// 线程池关闭
threadPool.shutdown();
}
}
5.5 ThreadPoolExecutor[重要]
- ThreadPoolExecutor
很重要,有7个参数
参数名 解释 备注 int corePoolSize 指定线程池的线程数量(核心线程数) 不能小于0 int maximumPoolSize 指定线程池可支持的最大线程数 最大数量>=核心线程数 long keepAliveTime 指定临时线程的最大存活时间 不能小于0 TimeUnit unit 指定存活时间的单位(秒,分,时,天) 时间单位 BlockingQueue workQueue 指定任务队列 ThreadFactory threadFactory 指定哪个线程工厂创建线程 RejectedExecutionHandler handler 指定线程忙,任务队列满的时候新任务来了怎么办?拒绝策略
这几个参数解释(某大型火锅店会例子)
- 核心线程数5, 即店里面的固定员工5个
- 最大线程数15,即突然顾客太多,5个人忙不过来,临时招聘10个人来干活
- 最大存活时间,即顾客不多的时候,这些临时工可以待多长时间
- 时间单位
- 任务队列10,即集合, 固定员工加上临时工还处理不了顾客,在店门口放几10张凳子
- 线程工厂, 如何创建出的线程? 即怎么找到的员工
- 拒绝策略. 当固定员工,临时工,以及门口的凳子都坐满了,不让吃了,不让排队,直接拒绝
问: 什么时候创建临时线程?
答: 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建线程问:什么时候开始拒绝任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来就会拒绝
package com.qf.threadpool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class TestThreadPool {
public static void main(String[] args) {
/**
* int corePoolSize, 核心线程数 - 5
* int maximumPoolSize, 最大线程数 - 15
* long keepAliveTime, 生存时间 - 1
* TimeUnit unit, 时间单位 - 天
* BlockingQueue<Runnable> workQueue, 阻塞队列 处理不及时的线程进阻塞队列
* ThreadFactory threadFactory, 线程工厂
* RejectedExecutionHandler handler , 拒绝策略
*/
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
// 至少需要5个参数
// new ThreadPoolExecutor(5,15,1, TimeUnit.MINUTES,queue);
// 多1个线程工厂,可以使用最简单的默认工厂
// new ThreadPoolExecutor(5,15,1, TimeUnit.MINUTES,queue, Executors.defaultThreadFactory());
// 多1个拒绝策略,默认的拒绝策略是 抛出异常
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,
15,
1, TimeUnit.MINUTES,
queue,
Executors.defaultThreadFactory( ),
new ThreadPoolExecutor.AbortPolicy( ));
for (int i = 1; i < 55; i++) {
executor.execute(new Runnable( ) {
@Override
public void run() {
Thread thread = Thread.currentThread( );
String name = thread.getName( );
System.out.println( name+"执行任务");
}
});
}
executor.shutdown();
}
}
面试肯定会问,重点是理解,要能说出来!
六、总结
关于线程API
1) 创建线程构造方法
2) 启动线程start,运行线程任务run
3) 获得线程对象Thread.currentThread()
4) 线程休眠sleep()
----------------------
关于线程同步:
1 什么是线程不安全: 线程数据被其他线程篡改?
为什么被篡改? 是因为当前线程执行过程中,别的线程抢走资源也执行
2 什么是线程安全: 当前线程执行时,不要让别的线程抢走资源,这样就不会篡改数据
3 如何做到? 就是给方法加锁
4 两种方案: 同步方法,同步代码块
5 注意事项: 保证锁对象是同一个
----------------------
关于通信:
1) 什么叫线程通信?
2) 如何做到的? 调用哪些方法做到...
3) wait和notify使用时有注意事项:
是不是必须要写在同步内?
被谁调用?
wait和sleep有什么异同?