多线程知识点总结
一,实现Runnable与继承Thread相比的优势:
1,通过创建任务,然后给线程分配任务来实现多线程,更适合多个线程同时执行相同的任务
2,可以避免单继承所带来的局限性
3,任务与线程是相分离的,提高线程的健壮性
4,后期学习线程池,只接受Runnable类型的任务,不接受Thread类型
二,如何设置和获取线程名称
Thread t = new Thread(new MyRunnable())
t.setName();//设置名称
t.currentThread().getName();//获取名称
三,线程休眠sleep
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<5;i++){
System.out.println("锄禾日当午")
Thread.sleep(1000);//1000相当于1秒
}
}
}
四,线程中断与守护线程
例如:t1,interrupt(); //给线程t1添加中断标记
一个线程是一个独立的执行路径,它是否结束,应该由自身决定
Thread t1 = new Thread(new MyRunnable());
//设置为守护线程
t1.setDaemon(true);
t1.start();
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给线程添加中断标记
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//1,不想让程序中断
//System.out.println("检测到标记,但我偏不死亡");
//e.printStackTrace();
//2,让程序中断
//System.out.println("检测到标记,我选择死亡");
//return;
e.printStackTrace();
return;
}
}
}
线程分为守护线程和用户线程
用户线程:当一个进程中不包含任何一个存活的用户线程,线程结束
守护线程:守护用户线程,当最后一个用户线程结束时,守护线程自动死亡
t1.setDaemon(true) //将t1设置为守护线程
五,线程安全和线程不安全
1)线程不安全:例如卖票
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
while(count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("准备出票,剩余的票数"+count);
}
}
}
假设还剩一张票,三个线程abc,进入while循环判断>0,a先经过代码块,进入count--操作,这时bc抢到时间篇,也进入count--操作,这时便会出现线程不安全情况,出现负数票
如何解决线程不安全
1,同步代码块synchronized(锁对象){}
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while(true){
synchronized (o){
if(count>0){
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余的票数"+count);
}else{
break;
}
}
}
}
其中一个线程进行while..代码块时,其他线程不能插足,创建锁对象o,一个线程抢占到锁,只有当他执行完之后,其他线程才能开始抢
2,线程安全2,同步方法
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
@Override
public void run() {
while(true){
boolean flag = sale();
if (!flag){
break;
}
}
}
public synchronized boolean sale() {
if (count>0) {
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"准备出票,剩余的票数" + count);
return true;
}return false;
}
}
创建boolean类型的sale方法,用synchronized修饰sale方法,没进入if语句,则证明没票了
flag为true买票成功,如果为false,则证明买票失败,break结束
3,线程安全3,显示锁
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁
//private Lock l = new ReentrantLock();
//显示锁l:fair参数为true,就表示公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while(count>0){
l.lock();
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"准备出票,剩余的票数"+count);
}
l.unlock();
同步代码块和同步方法都为隐式锁
创建显示锁Lock l = new ReentrantLock();
在进入if语句前,进行上锁 l.lock(), 执行完毕解锁 l.unlock()
公平锁与非公平锁:
以上三种线程安全都为非公平锁,大家一起抢占,公平锁则为先到先得,排队执行
公平锁:只需要在创显示锁时传入true
Lock l = new ReentrantLock(true)
六,线程死锁
举例解释线程死锁:ab去两个试衣间,a进1试衣间,将其上锁,b进入2试衣间进行上锁,这时a觉得对自己的1试衣间不满意想换到2,而b也对自己的2试衣间不满意,想换到1试衣间,ab都在等对方先出来,才能进入对方的试衣间,这时便出现线程死锁
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c = c;
this.p = p;
}
public void run(){
p.say(c);
}
}
//罪犯
static class Culprit{
public synchronized void say(Police p){
System.out.println("罪犯:你放我了,我就放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被释放了,人质也获救了");
}
}
//警察
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我就放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救了人质,但是罪犯跑了");
}
}
用代码来解释死锁,只有当罪犯放了人质,警察才能放了罪犯,而罪犯只有当警察放了他,他才能放了人质,于是双方一直僵持着
七,多线程通信问题 生产者与消费者
只有当生产者生产出东西,消费者才能使用
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
public void run(){
for (int i=0;i<100;i++){
if(i%2 == 0){
f.setNameAndTaste("老干妈小米粥","酸辣味");
}else{
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
public void run(){
for (int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true表示可以生产
boolean flag = true;
public synchronized void setNameAndTaste(String name,String taste){
if (flag){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get() {
if (!flag){
System.out.println("服务生端走的菜的名称:"+name+",菜的味道:"+taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
厨师和服务员:没加休眠和唤醒时,可能会出现的问题:将菜的名字和味道整混,例如:煎饼果子本是甜辣味,而最终显示的却是酸辣味,如果用synchronized修饰时会出现另一种情况厨师刚做完一道菜,回头又抢到时间篇,接着又做菜
或者服务员端完盘子,又抢到时间篇,接着执行端盘子操作,但是厨师并没有做菜
出现这种情况的原因是:setandtaste方法中,厨师刚执行完this.name = name;时间篇被服务员抢走了,没执行到this.taste = taste,菜就被端走了,
如何解决这种问题:厨师做菜的时候,对服务员进行休眠wait(),厨师做完菜时,唤醒服务员this.notifyAll(),然后将厨师进行休眠
八,线程的六种状态
1,new创建 2,Runnable运行 3,Blocked上锁排队 4,wait休眠 5,Tinedwainting排队休眠 6,terminated结束
九,带返回值的线程Callable
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
//判断线程任务是否执行完毕
//task.isDone();
//取消
//task.cancel(true);
Integer j = task.get();
System.out.println("获取的返回值为:"+j);
for (int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
for (int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
return 100;
}
}
主副线程交替执行,通过task.get()可以获取返回值100,task.isDone()判断线程任务是否完毕,task.cancel(true)取消
十,线程池
线程池的创建:
ExecutorService service = Executors.newCachThread();
1)缓存线程池
/**
* 1,缓存线程池(长度没限制)
* 任务加入后的执行流程:
* 1,判断缓存池是否存在空闲线程
* 2,存在则使用
* 3,不存在,则创建线程,并放入缓存池,然后使用
*/
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override//已经创建三个线程,第四个任务会使用上面三个其中一个空闲的线程
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
2)定长线程池
/**
* 定长线程池:
* 1,判断线程池是否存在空闲空间
* 2,存在则使用
* 3,不存在空闲线程,且线程池未满的情况下,创建线程并放入线程中,然后使用
* 4,不存在空闲程序,且线程池已满的情况下,则等待线程池存在空闲线程
*
*
* (单线程池,相当于把定长线程池传入的数字为1)
*/
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
3)周期任务,定长线程池
/**
* 周期任务执行时:
* 定时执行,当某个时机触发时,自动执行某任务
*
*/
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1,定时执行一次
* 参数1:定时执行的任务
* 参数2:时长数字
* 参数3:时长数字的时间单位,TimeUnit的常量指定
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
},5, TimeUnit.SECONDS);*/
/**
* 周期性执行
* 参数1,任务
* 参数2,延迟时长数字
* 参数3,周期时长数字
* 参数4,时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("锄禾日当午");
}
},5,1,TimeUnit.SECONDS);
}
}
十一,lambda表达式
public static void main(String[] args) {
print((int x, int y) ->{
return x+y;
},100,200);
}
public static void print(MyMath m,int x,int y){
int num = m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sum(int x,int y);
}
}