进程 process 线程 Thread
main称之为主线程,为系统的入口,用于执行整个程序
线程创建的三种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类:
public class Test extends Thread { @Override public void run() { //run 方法线程体 for (int i = 0; i < 20; i++) { System.out.println(i); } } public static void main(String[] args) { //创建一个线程对象 Test t=new Test(); //开启线程 t.start(); //知道跟run方法的区别 //main线程 主线程 for (int i = 0; i < 2000; i++) { System.out.println("main方法"+i); } } }
分析调用start跟run方法的区别:
调用start方法主线程跟子线程会并行交替执行。 run方法主线程会先执行完之后再执行子线程。
总结:注意,线程开启不一定立即执行,是由cpu调度的。
实现Runnable接口:
实现该接口,重写run方法,执行线程需要丢入runnable接口实现类。调用start方法
public class Test implements Runnable { @Override public void run() { //run 方法线程体 for (int i = 0; i < 20; i++) { System.out.println(i); } } public static void main(String[] args) { //创建Runnable接口的实现类对象 Test t=new Test(); //创建线程对象,通过线程对象来开启我们的线程,代理 Thread t1=new Thread(t); t1.start(); //main线程 主线程 for (int i = 0; i < 2000; i++) { System.out.println("main方法"+i); } } }
推荐使用Runnable接口实现多线程
小结:
关于线程的不安全性示例代码:
public class Test implements Runnable { private int ticketNum=10; @Override public void run() { while(true){ if (ticketNum<=0){break;} try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } //run 方法线程体 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"张票"); } } public static void main(String[] args) { //创建Runnable接口的实现类对象 Test t=new Test(); //创建线程对象,通过线程对象来开启我们的线程,代理 new Thread(t,"小红").start(); new Thread(t,"小明").start(); new Thread(t,"小张").start(); } }
输出结果为:
此时可以看到 同一张票被两个不同的人抢到,此时就表明存在线程安全问题。当多个线程操作一个对象的时候会出现线程安全问题。
继续看以下龟兔赛跑代码示例:
public class Test3 implements Runnable{ private String winner; @Override public void run() { for (int i = 0; i < 101; i++) { //模拟兔子休息 if (Thread.currentThread().getName().equals("兔子")&&i%10==0){ try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } if (GameOver(i)){ break; } System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步"); } } //判断比赛是否结束 private Boolean GameOver(int step){ if (winner!=null){ return true; }else { if (step>=100){ winner=Thread.currentThread().getName(); System.out.println("胜利者是---->"+winner); } } return false; } public static void main(String[] args) { Test3 t=new Test3(); new Thread(t,"乌龟").start(); new Thread(t,"兔子").start(); } }
当线程为兔子时模拟兔子睡觉,即Thread.sleep()。胜利者一定是乌龟。
实现Callable接口:
---------------------------------------------------------------------------------------
扩展知识:
关于静态代理:
静态代理模式总结:真实对象跟代理对象都要实现同一个接口,代理对象要代理真实角色。
好处:代理对象可以在做很多真实对象无法实现的事情,真实对象可以专注自己可以做的事。
Thread类的底层实现原理就是使用静态代理类。
函数式接口:
- 任何接口只包含唯一一个抽象方法,那么它就是一个函数式接口。
- 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
-----------------------------------------------------------------------------------------
线程状态:
线程方法:
停止线程:
线程礼让:yield 方法。因为线程执行由cpu调度,所以礼让不一定成功,看cpu心情。
线程插队:join 方法。即两个线程在执行的时候,本来主线程会先执行完毕再执行子线程,但是此刻我设定一个条件当主线程执行到某一步的时候
我使用join方法,此时主线程会进行等待,等子线程执行完毕之后才会继续执行。
关于线程的状态有以下几种;
线程优先级:
线程分为 用户线程 守护线程
关于线程锁:
每个对象都有一把锁
重点:synchronized
1. 同步方法,锁的是This。上面抢票示例代码会出现线程安全问题,当我们在“抢票”的方法上使用 synchronized 时,表示该方法
在执行时会被上锁,只有该方法执行完之后,锁会被释放,后续对象才可以拿到该锁进行处理。Buy()方法上使用锁代表锁的是BuyTicket对象。
2 . 同步块
示例代码:
public class Test2 { public static void main(String[] args) throws InterruptedException { List<String> arrList=new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(( )->{ arrList.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(2000); System.out.println(arrList.stream().count()); } }
结果:
从输出结果可看出并未讲10000个线程对象添加到集合中,原因是 两个线程在同一时间将同一个对象添加到了同一个内存位置,因此集合中的对象会出现覆盖的情况,所以产生了此种情况。
处理方法:
public class Test2 { public static void main(String[] args) throws InterruptedException { List<String> arrList=new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(( )->{ synchronized(arrList){ arrList.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(2000); System.out.println(arrList.stream().count()); } }
将“arrList”对象添加锁,同步代码块。
JUC:CopyOnWriteArrayList 此集合为一个线程安全的集合,与arrayList集合不同。是java.util.concurrent包下的
关于死锁:
不要在一个锁还未被释放的时候就想着去取另一个锁,这种情况就容易造成死锁的产生。即在“synchronized”代码块中嵌套"synhronized"
Lock锁:
Lock锁与synchronized对比:
生产消费者模型:
1.管程法 (生产者,消费者,产品,缓冲区)
public class Test3 { public static void main(String[] args) { SynContainer synContainer=new SynContainer(); new Productor(synContainer).start(); new Customer(synContainer).start(); } } //生产者 class Productor extends Thread{ SynContainer synContainer; public Productor(SynContainer synContainer){ this.synContainer=synContainer; } @Override public void run() { //生产100只鸡仔 for (int i = 1; i < 100; i++) { try { synContainer.push(new Chicken(i)); System.out.println("生产了---->"+i+"只鸡仔"); } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者 class Customer extends Thread{ SynContainer synContainer; public Customer(SynContainer synContainer){ this.synContainer=synContainer; } @Override public void run() { //消费100只鸡仔 for (int i = 1; i < 100; i++) { try { System.out.println("消费了"+synContainer.Consumer().id+"鸡仔"); } catch (InterruptedException e) { e.printStackTrace(); } } } } //产品 class Chicken{ int id; public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer{ //定义一个缓冲区的大小 Chicken[] chickens=new Chicken[10]; //定义鸡的个数 int count=0; //生产鸡仔 public synchronized void push(Chicken chicken) throws InterruptedException { //当生产的鸡仔达到容量时 if (count== chickens.length){ //此时应该暂停生产 this.wait(); } //如果没有满 我们需要丢入鸡仔 chickens[count]=chicken; count++; //通知消费者可以开始消费 this.notifyAll(); } //消费鸡仔 public synchronized Chicken Consumer() throws InterruptedException { //当没有鸡仔可以消费时 if (count==0){ //此时消费者应该处于等待,等待生产者继续生产 this.wait(); } //否则鸡仔将被消费 count--; Chicken chicken = chickens[count]; //并告知生产者鸡仔已经被消费 this.notifyAll(); return chicken; } }
线程池: