多线程
程序:静态的代码;
进程:程序执行的过程,操作系统分配资源的单位
线程:CPU调度和执行的单位,独立执行的路径。互不影响,线程的执行由调度器安排,对同一资源进行操作时,需要并发控制
创建多线程,
方法1:定义一个类继承Thread,重写run()方法,run()方法里是程序执行体,
调用,创建一个该类的对象,并用 对象.start()方法开始
方法2:定义一个类实现Runnable借口,编写run()方法,创建一个该类的对象t1,再创建一个Thread类的匿名对象,把t1作为参数传进Thread类的构造方法。并调用start()方法。
方法3:实现callable接口,需要返回值类型;
重写call方法,需要抛出异常;
创建目标对象;几个线程就创几个对象
然后使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
线程池创建线程
1.创建Runnable或Callable的实现类对象;
2.创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1);
3.选择对应的方法
1)Runnable: ser.execute(Runnable的实现类对象);
2)Callable:提交执行:Future result1 = ser.submit(Callable的实现类对象);
获取结果:boolean r1 = result1.get();(Callable实现类才有返回值)
4.关闭服务:ser.shutdownNow();
多线程并发共享一个资源
多线程同时操作同一资源的情况下,线程不安全,数据紊乱。
模拟延时。Thread.sleep(200);
Thread.currentThread().getName();获取当前线程的名称
使用Callable接口的时候,几个线程就需要几个Callable实现类的对象,因为callable的call()方法是被Runnable的run()方法调用的。一个对象中的call()不能被几个线程同时调用,所以要创多个Callable实现类的对象
静态代理:
就是代替你去完成一些工作
静态代理模式总结:
真是对象和代理对象都要实现同一个接口,代理对象要代理真实角色。
好处:代理对象可以做很多真实对象做不了的事,真是对象专注做自己的事情。
new Tread(new Runnable(){
public void run(){
}
} ).start();
Tread和Runnable都实现了Runnable接口,他们都有一个共同方法叫run()方法
Lamda表达式
任何接口,如果只包含唯一一个抽象方法,那么它就是函数式接口。
对于函数式接口,我们可以用lamda表达式来创建该接口的对象。
lambda表达式的前提必须是函数式接口
public class Lambda01{
public static void main(String[] args){
Ilike a = (str) -> System.out.println("I like " + str);
a.talk("you");
}
}
interface Ilike{
abstract void talk(String str);
}
线程五大状态
1、创建状态
2、就绪状态
3、运行状态
4、阻塞状态
5、死亡状态
线程的停止
不推荐使用jdk提供的stop(),destory()等已废弃的方法。
推荐让线程自己停下来
建议使用一个标志位进行终止变量,当flag = false,则线程终止;
public class ThreadStop implements Runnable{
private boolean flag = true;
public void run(){
int i = 0;
while (flag){
System.out.println("thread:" + i++);
}
System.out.println("stoping~~~");
}
public void stop(){
this.flag = false;
}
public static void main(String[] args){
ThreadStop t1 = new ThreadStop();
new Thread(t1).start();
for (int i = 0; i < 150; i++){
System.out.println("main" + i);
if (i == 90){
t1.stop();
}
}
}
}
线程休眠
模拟网络延时:放大问题的发生性
Thread.sleep(时间) ;当前线程阻塞的毫秒数
sleep存在InterruptedException(中断异常)
sleep时间到达后线程进入就绪状态;
sleep可以模拟网络延迟,倒计时等
每一个对象都有一个锁,sleep不会释放锁。
import java.util.Date;
import java.text.SimpleDateFormat;
public class CountDown{
public static void main(String[] args){
// tenDown();
// sysTime();
}
public static void sysTime(){
while (true){
Date time = new Date(System.currentTimeMillis());
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time));
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void tenDown(){
int num = 10;
while (num >= 0){
try{
Thread.sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(num--);
}
}
}
现成礼让
Thread.yield();
礼让线程,让当前正在执行的线程暂停,但不阻塞。
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功!看CPU心情
线程强制执行
Thread.join();
join合并线程,待此线程执行完成之后,再执行其他线程,其他线程阻塞
可以想象成插队
线程状态
新生状态,就绪状态,运行状态,阻塞状态,死亡;
监听线程状态,Thread.State state = Thread.getState()
Thread.State.TERMINATED 线程死亡
线程优先级
线程优先级越高,权重越高,越容易优先执行
Thread.getPriority()获取优先级
Thread.setPriority()设置优先级
优先级的范围是从1~10;最小是1,最大是10;寻常优先级为5;
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待用户线程执行完毕
守护线程有后台记录操作日志、监控内存、垃圾回收等待
设置守护线程Thread.setDaemon(true);默认为false;
线程同步机制
多个线程调用同一资源,会引发并发问题,因为线程修改同一资源时,是把资源复制到自己的内存空间再进行出列的,就会导致不安全。这时候需要并发控制和死锁,保证线程同步执行。
同步方法:
synchronized 修饰的方法只能一个对象调用完之后,另一个对象才能调用。锁的是调用该方法的对象。默认锁的this
同步代码块:
synchronized(obj){
}
保证该对象只能被一个对象访问完之后才能被另一个对象访问。锁的时候要锁变化的量,也就是要访问的资源。
ArrayList线程是不安全的;CopyOnWriteArrayList是安全的;
死锁
多个线程需要等待其他线程占有资源才能运行,多个线程相互等待对方的资源,都停止执行的情形就是死锁
多个线程互相抱着对方需要的资源,然后形成僵持。
同时拥有两个以上对象的锁,就可能发生死锁问题。
产生死锁的四个必要条件:
1、互斥条件:一个资源只能被一个进程调用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对方获得的资源保持不放。
3、不剥夺条件:进程已获得资源,在未使用完之前不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源状态。
只要破坏其中的任意一个条件,就可以避免死锁。
可重入锁lock
显式定义同步锁。同步锁实用Lock对象充当。每次只能有一个线程对Lock对象加锁,ReentrantLock类实现了Lock,比较常用,性能更高,扩展性好,提供更多的子类
手动开启和关闭,synchronized是隐式锁,出了作用域自动释放。
优先使用顺序Lock>同步代码块>同步方法
使用方法:
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();//启动锁
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
//如果同步代码有异常,要讲unlock()写入finally语句块。
}
}
生产者消费者问题
生产者生产出来东西,告诉消费者消费,没有产品时,要通知消费者等待。
synchronized可以阻止并发更新同一个资源,实现了同步,但是不能实现不同线程之间的通信。
obj.wait()表示线程一直等待,知道其他线程通知,与sleep不同的是,会释放锁;
obj.wait(long timeout)指定等待的毫秒数。
obj.notify()唤醒一个正在等待的线程
obj.notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度。
都是Object类的方法,只能在同步方法和同步代码块中使用。否则会抛出IIIegalMonitorStateException异常(非法监控状态异常)
-
生产者——>数据缓冲池——>消费者
利用缓冲区解决:管程法
-
信号灯法,标志位解决。
用于数据单一的情况,即线程依次轮换的情况
线程池
ExecutorService ser = Executors.newFixedThreadPool(5);
Runnable接口用:
ser.execute(对象);
Callable接口用:
Future r = ser.submit(对象);
ser.shutdown();关闭线程池;
可以提前创建好几个线程,放入线程池中,使用的时候,直接获取,使用完放回池中,可以避免频繁创建销毁线程,实现重复利用。