一、线程简介
比如在看一个视频的时候,声音、字幕、图像都是不同的线程,共同构成一个进程(视频)
真正的多线程是指有多个cpu,即多核,如服务器
而模拟出来的多线程,是一个cpu(调度器)的情况下,来回切换,因为切换的很快所以看不出来,但在同一个时间点,cpu只能执行一行代码
注意的点:
(1)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
(2)线程会带来额外的开销,如cpu调度时间,并发控制开销
二、线程的创建
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
1、继承Thread类
步骤:
自定义线程类继承Thread类
重写run()方法
创建线程对象,调用start()方法启动线程
run()方法是先执行主方法,再执行后面的run()方法
start()方法是多条执行路径交替执行的
public class StartThread1 extends Thread{
public void run(){
for(int i = 0;i < 10;i++){
System.out.println(i + "我在学习多线程");
}
}
public static void main(String[] args) {
StartThread1 uu = new StartThread1();
uu.start();//主方法和run方法的内容就会交替执行
uu.run();//先执行主方法,再执行run方法
for(int i = 0;i < 2000;i++){
System.out.println(i + "希望之后能学懂");
}
}
}
2、实现Runnable接口
定义MyRunnable类实现Runnable接口
重写run()方法
创建线程对象,调用start()方法启动线程
public class StartThread2 implements Runnable{
public void run(){
for(int i = 0;i < 10;i++){
System.out.println(i + "我在学习多线程");
}
}
public static void main(String[] args) {
StartThread2 yu = new StartThread2();
new Thread(yu).start();//执行线程需要丢入Runnable
for(int i = 0;i < 2000;i++){
System.out.println(i + "希望之后能学懂");
}
}
}
多个线程同时操作同一个对象
//多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class StartThread3 implements Runnable{
private int piaoshu = 10;
public void run(){
while (true){
if(piaoshu <= 0){
break;
}
try {
Thread.sleep(200);//延时,之所以延时就是因为太快了,分辨不出来了
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + piaoshu +"票");
}
}
public static void main(String[] args) {
StartThread3 ii = new StartThread3();
new Thread(ii,"小明").start();
new Thread(ii,"老师").start();
new Thread(ii,"程序员").start();
}
}
3、实现Callable接口
静态代理模式:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实对象
好处:
代理对象可以做很多真实对象做不了的事情
真实对象可以专注做自己的事
Lamda表达式
public class TestLamda1 {
//3、静态内部类
static class Like2 implements ILike{
public void lamda(){
System.out.println("i love lamda3");
}
}
public static void main(String[] args) {
ILike uu = new Like();//外部实现类
uu.lamda();
ILike ii = new Like2();//静态内部类的实现方法
ii.lamda();
//4、局部内部类
class Like3 implements ILike{
public void lamda(){
System.out.println("i love lamda4");
}
}
ILike gg = new Like3();
gg.lamda();
//5、匿名内部类
ILike ww = new Like() {
public void lamda(){
System.out.println("i like lamda5");
}
};
ww.lamda();
//6、用lamda简化
ILike qq = () ->{
System.out.println("i like lamda6");
};
qq.lamda();
}
}
//1、定义一个函数式接口
interface ILike{
void lamda();
}
//2、实现类
class Like implements ILike{
public void lamda(){
System.out.println("i love lamda2");
}
}
//lamda表达式
// 前提是函数式接口
public class TestLamda2 {
public static void main(String[] args) {
ILove rr = (a,b,c) -> {//重写对象的实现方法去执行
System.out.println("i love you -->" + a);
System.out.println("too");
};
rr.love(520,502,250);
}
}
//定义一个函数式接口(只有唯一一个抽象方法的接口是函数式接口)
interface ILove{
void love(int a,int b,int c);
}
三、线程的状态
线程停止
线程休眠 sleep
线程礼让 yield
1000.for(快捷键)
线程同步:多个线程操作同一个资源的时候用这项技术
并发:同一个对象被多个线程同时操作
这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入对象的等待池形成队列,等前面的执行完,下一个再使用
要保证线程同步的安全性,就需要线程+锁
相当于1000个线程过来,只有一个线程能进入操作,那为了保证它的安全,那就给它加锁,关上门它做完自己的事了,那就轮到其他的线程来了,同样的加锁
同步方法和同步块都能解决不安全的问题
同步方法和同步代码块的区别
区别:
同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
为何使用同步?
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(增删改查),将会导致数据的不准确,相互之间产生冲突。类似于在atm取钱,银行数据确没有变,这是不行的,要存在于一个事务中。因此加入了同步锁,以避免在该线程没有结束前,调用其他线程。从而保证了变量的唯一性,准确性。
1.同步方法:
即有synchronized修饰符修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用给方法前,要获取内置锁,否则处于阻塞状态。
例:public synchronized getMoney(){}
注:synchronized修饰静态方法,如果调用该静态方法,将锁住整个类。
2.同步代码块
即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁。实现同步。
例:synchronized(Object o){}
同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。
同步方法默认用this或者当前类class对象作为锁。
同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。
什么是JUC:
java.util.concurrent包名的简写,是关于并发编程的API
与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。
volatile、ReentrantLock
四、死锁
两个或者多个线程各自占有一些共享资源,都在等待对方释放资源;某一个同步块同时拥有两个以上对象的锁时,会发生死锁
拿到镜子的人要去拿口红,拿到口红的人要去拿镜子
五、生产者和消费者模式(线程协作)
解决线程通信的两种方式:管程法和信号灯法
1、管程法
用notify()互相通知
2、信号灯法
标志位来解决,true等待,false通知
这两种方法主要要解决什么时候通知,什么时候等待的问题
六、线程池