1.线程的创建方式
建立线程的方式有两种
1-通过继承Thread类,创建一个Thread类的子类XXThread
通过直接new XXXThread()的方式创建线程对象
2-通过实现Runnable接口的方式创建可运行的类XXXRunnable
通过创建一个Thread类的对象,将XXXRunnable对象作为参数传入到new Thread(这里)的构造方法中
两种方式代码如下:
第一种方式:
创建一个类继承Thread类
public class MyThread extends Thread {
// MyThread继承Thread
// Thread就是线程类,那么MyThread继承Thread后,
// MyThread就变成了线程
@Override
public void run() {
super.run(); // 这里super调用父类的方法,就是null的
// super指代父类的对象,调用父类的方法
// this指代本类的对象(先调自己,再调从父类继承过来的)
// 当MyThread线程对象被启动后(start后)
// 就会执行这个方法
try{
Thread.sleep(2000);//2000指的是毫秒
}catch (InterruptedException e){
e.printStackTrace();
}
system.out.println("我是一个子线程");
}
}
在main方法中启动线程
public static void main(String args[]) {
// 创建线程对象
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
// 启动线程
myThread.start();
myThread1.start();
}
第二种方式:
创建一个类实现runnable
public class MyRunnable implements Runnable {
// MyRunnable不是一个Thread
// 是一个可运行的类
@Override
public void run() {
for (int i= 0;i < 20; i++) {
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
system.out.println("我是正在执行Runnable:" + i);
}
}
}
在main启动线程
public static void main(String args[]) {
// 1.创建以XXXRunnable对象
MyRunnable myRunnable = new MyRunnable();
// 2.创建一个Thread对象
// 并将上面创建的XXXRunnable对象传入构造方法中
// public Thread(Runnable target)的构造方法
// target是Runnable接口类型
// 可以将收实现了Runnable接口的任何一个类的对象
// 经常说成:它(指的就是Runnable接口)的实现类对象
Thread t1 = new Thread(MyRunnable);
t1.strat();
}
上面两种创建方式,我们可以通过创建类的匿名内部类,进行建立
通过实现Runnable接口来,创建线程并启动
匿名内部类(是Runnable接口的实现类对象) + 匿名对象
new Thread(new Runnable(){
@Override
public void run() {
while(true) {
system.out.println("这是第一个线程")
}
}
}).strat();
通过继承Thread类来建立线程
new Thread(){
@Override
public void run(){
super.run();
while(true){
system.out.println("------");
}
}
}.start();
当两个或多个线程同时运行
线程是抢占式运行,谁抢到CPU的执行权,那么谁就执行
2.线程的生命周期
1-新建状态
就是new了一个线程对象
2-就绪状态(可运行,可执行状态)
线程调用.start()方法
3-运行状态(执行状态)
抢占到CPU的执行权,正在执行
4-阻塞状态
线程运行过程中调用一些方法进入阻塞状态
sleep():停止一段时间,这段时间不会去抢占CPU
join():等待XXX线程执行完毕,在进入等待抢占CPU
wait():持有锁对象的线程,释放锁对象
yield():是当前线程回到可执行状态
IO流阻塞:键盘输入,等待输入的状态
锁对象:线程ABCD这行同一方法,线程A拿到锁对象,执行方法,其他三个线程进入阻塞状态,直到线程A释放锁对象,其他三个线程进入到可执行的状态
5-死亡状态
程序异常
线程结束
退出run()方法
3.对象锁
在多线程并发时会出现问题
如代码所示
定义一个类
public Class DemoThread extends Thread{
private static int cont = 1000;
@Override
public synchronized void run() {
super.run();
for(int i=0;i < 2500;i++){
cont -= 1;
system.out.println(cont);
}
}
}
创建一个main方法
public static void main(String[] args){
DemoThread d1 = new DemoThread();
DemoThread d2 = new DemoThread();
DemoThread d3 = new DemoThread();
DemoThread d4 = new DemoThread();
d1.strat();
// 取值为10000 做-1操作后为9999
// 但是还没有输出9999呢
// 就会被抢占执行权
// 就会出现
// 9999在9998后面或小于9999的数字的下面
d2.strat();
// d2取值为300,在做-1的操作后赋值给cont时
// d3可能也取值到了300,做了一次-1的操作
// 那么d2和d3这两次操作
// 实际上只让cont减少了1
// 所以可能出现四个线程执行完毕,结果大于0的情况
d3.strat();
d4.strat();
}
想要解决上述代码出现的状况,就可以使用同步关键字(对象锁)
锁是什么?
锁是一个对象
假如A线程县访问del方法
那么锁对象就被线程A持有
其他线程(B,C,D)就都不能进入到del方法中
当A线程执行完毕del方法,会将锁对象释放出来
这时候(B,C,D)线程在争夺执行权
谁抢到了,谁来执行del方法
谁执行del方法,那么谁就又先拿到了锁对象
这时候剩下的两个线程就又进不来del方法中了
使用synchronized关键字的两种方式:
-在方法声明上,使用synchronized关键字修饰
-使用synchronized代码块将要执行的代码包起来
// 当创建一个对象
Demo demo = new Demo();
// 启动两个线程,创建一个对象,分别调用自己的两个方法
new Thread(){
@Override
public void run(){
demo.show();
}
}.start();
new Thread(){
@Override
public void run(){
demo.del();
}
}.start();
show方法,del方法都是被synchronized关键字修饰的
而synchronized关键字修饰方法的锁对象
就是该方法的调用者
对象.方法();
这里调用show方法,与调用del方法的对象是一个
所以show方法的锁对象
与del方法的锁对象也就是一个了
而第一个线程已经进入了show方法
也就持有了锁线程
第二个线程,想要进入到del方法
需要等第一个线程,从show的方法中出来,释放锁对象的时候
才能进入到del方法中
死锁
有两把钥匙
有甲乙两个人各拿一把,甲拿A钥匙,乙拿B钥匙,分别走两条路
甲走的路上,有两道门上了锁分别为A,B,甲开了一道,停在了第二道们前,因为没有钥匙打开
乙走的路上,有两道门上了锁分别为B,A,乙开了一道,停在了第二道们前,因为没有钥匙打开
甲乙谁都没有办法打开第二道门,卡在了那,就叫做死锁
接口回调
接口实现类,通过接口类型传递,在很远的地方对象调用的方法,就是传递的对象,调用自己的方法