多线程
一、进程与线程
一个进程表示内存中一个独立的应用程序,每一个进程在内存中占有独立的内存空间;
一个线程代表进程中的一个环节,有独立的内存空间,线程之间可以相互切换,一个进程至少包含一个线程;
二、同步与异步,并发与并行
同步:线程同步执行,效率低,线程数据不安全;
异步:线程排队执行,线程数据安全;
并发:同一段时间内执行;
并行:同一时刻执行,同时执行;
三、创建线程
1、继承Thread类
重写run()方法,功能在run中实现
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("我来了!"+i);
}
}
}
2、实现Runnable接口
采用Runnable接口创建任务的好处:
1、通过创建任务,然后给线程分配的方式实现多线程,解决多个线程同时执行相同任务的情况;
2、可以避免单继承所带来的局限性,允许多接口;
3、任务和线程本身是分离的,提高了线程的健壮性;
4、后续实现线程池技术,接受Runnable类型的任务,不接受Thread类型的线程;
public class MyRunnable implements Runnable{
@Override
synchronized public void run() {
for (int i = 0; i <10; i++) {
System.out.println("我来了"+i);
System.out.println( Thread.currentThread().getName());
}
}
}
3、创建Callable对象
对象执行后有一个返回值,主线程可以通过返回值执行下一步操作。
public class ThreadDemo08 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c=new MyCallable();
FutureTask<Integer> task=new FutureTask<>(c);
new Thread(task).start();
//主线程等待输出返回值
System.out.println(task.get());
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 {
for (int i = 0; i <10; i++) {
Thread.sleep(100);
System.out.println(i);
}
return 100;
}
}
}
四、执行线程
1、开始线程
//创建一个线程任务
MyRunnable r=new MyRunnable();
//创建一个进程,并为它分配一个任务
Thread t=new Thread(r);
//设置这个线程为守护线程,主线程结束,t也结束
t.setDaemon(true);
//执行这个线程
t.start();
2、线程休眠
线程休眠500毫秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
3、线程中断
线程是否中断应该尤其本身决定,而不应从外部强行中断;
五、线程安全问题
多个线程在对同一数据进行操作时,可能会出现数据不安全问题,会出现不符合现实规则的数据。
1、同步方法
同步方法对买票的动作进行同步,使票的数量安全。
public class ThreadDemo04 {
public static void main(String[] args) {
Runnable runnable=new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).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(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:"+count);
return true;
}else{
return false;
}
}
}
}
2、同步代码块
同步代码块采用锁的方式使买票的整个动作同步,任何对象都可以成为锁,需要同步的线程同步锁必须是同一把锁。
public class ThreadDemo03 {
public static void main(String[] args) {
//线程不安全
Runnable runnable=new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).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(Thread.currentThread().getName()+"正在准备买票:");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:" + count);
}else{
break;
}
}
}
}
}
}
3、公平锁和非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,都是队列的第一位才能得到锁。
非公平锁:多个线程去获取锁,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
//传的参数为true,就表示公平锁
private Lock l=new ReentrantLock(true);
4、显示锁
更好的体现锁和解锁的概念;
该代码在每一次执行while循环就加锁,执行完一次后就解锁;
public class ThreadDemo05 {
public static void main(String[] args) {
Runnable runnable=new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable{
private int count=10;
//传的参数为true,就表示公平锁
private Lock l=new ReentrantLock(true);
@Override
public void run() {
while (true) {
l.lock();
if (count > 0) {
System.out.println("正在准备买票:");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:" + count);
} else {
break;
}
l.unlock();
}
}
}
}
5、线程死锁
线程1需要线程2返回的结果才能执行,线程2同时需要执行线程1的结果,线程1,线程2都得不到执行完毕的前提,于是线程死锁;
六、生产者和消费者
在生产的时候不能消费,就有了等待wait(),唤醒notify()的方法,才能保证数据在传输时候的安全;
示例中,用到了食物,生产者,消费者三个实体类,通过判断标记来确认是否可以执行对应线程;
public class ThreadDemo07 {
public static void main(String[] args) {
Food f=new Food();
Cook cook = new Cook(f);
cook.start();
Waiter waiter = new Waiter(f);
waiter.start();
}
static class Cook extends Thread{
private Food f;
public Cook(Food f){
this.f=f;
}
@Override
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;
}
@Override
public void run(){
for (int i = 0; i <100; i++) {
f.get();
}
}
}
static class Food{
private String name;
private String taste;
//true表示可以生产
private boolean flag=true;
public String getName() {
return name;
}
public synchronized void setNameAndTaste(String name,String taste) {
if (flag) {
this.name = name;
this.taste = taste;
System.out.println("厨师生产的饭菜是:"+this.name+",味道是:"+this.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();
}
}
}
}
}
七、Lambda表达式
public static void main(String[] args) {
//冗余的Runnable代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我来了!");
}
}).start();
//实现同一功能的Lambda表达式,括号内还可以传入不同的参数
Thread t=new Thread(()-> System.out.println("我来了"));
t.start();
}
八、线程池
线程池执行完任务后,在一定时间内不会关闭,等待线程任务;
1、缓存线程池
线程不会死亡,可以重复使用,节省创建和销毁线程的时间;
//缓存线程池
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、定长线程池
线程池的线程个数为定长,即最大同时进入的线程个数为线程长度
//定长线程池
ExecutorService service1 = Executors.newFixedThreadPool(2);
service1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "定长线程池");
}
});
service1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "定长线程池");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service1.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "定长线程池");
}
});
3、单线程线程池
也就是一个线程的固定线程池,适用于需要异步执行但需要保证任务顺序的场景;
//单线程线程池
ExecutorService service2 = Executors.newSingleThreadExecutor();
service2.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "单线程线程池");
}
});
service2.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "单线程线程池");
}
});
4、周期任务定长线程池
适用于定期执行任务场景,支持按固定频率定期执行和按固定延时定期执行两种方式;
//周期任务定长线程池
ScheduledExecutorService service3 = Executors.newScheduledThreadPool(2);
/**
* 定时执行一次
* 定时执行的任务
* 时长数字
* 时长数字的时间单位,TimeUnit的常量
* */
service3.schedule(new Runnable() {
@Override
public void run() {
System.out.println("周期");
}
}, 5, TimeUnit.SECONDS);
/**
* 周期性执行任务
* 任务
* 延时时长数字(第一次执行在什么时候以后)
* 周期时长数字(每隔多久执行一次)
* 时长数字的单位
*
* */
service3.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("周期1");
}
},5,1,TimeUnit.SECONDS);