进程 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;
}
}
线程池: