今天学习内容:
一、多线程的实现方式
二、线程安全问题
三、线程状态
一、多线程的实现方式
1.实现方式一:
- 1)写一个子线程类,继承Thread类
- 2)复写run方法
- 3)在main方法创建子类对象
- 4)调用start()开启线程
启动线程的方法只能是调用start()方法实现,如果是在main()方法中调用线程的run()方法那么只是当作一个普通类调用了一个普通的方法,并不会开启一个新的方法
public DownloadThread extends Thread{
@Override
public void run(){
for(int i =1;i<=100;i++){
System.out.println(getName()+"...正在下载..."+i+"%");
}
}
}
测试类部分:
public class Demo01{
public static void main(String[] args){
//创建Thread子类对象
DownloadThread dt1 = new DownloadThread();
dt1.setName("线程1");
dt1.start();
//创建Thread的子类对象
DownloadThread dt2 = new DownloadThread();
dt2.setName("线程2");
dt2.start();
}
}
2.Thread类的常用方法:
常用的多线程的构造方法
Thread(Runnable target)
分配新的Thread对象
Thread(Runnable target,String name)
分配新的Thread对象
//非静态方法,需要用Thread对象调用
public void setName(String name)
设置线程的名称
public String getName()
获取Thread类的名称
public void start()
启动一个线程
//静态方法,用Thread类名调用
public static Thread currentThread()//获取Thread类的对象(比较常用)
获取当前正在执行的线程对象
public static void sleep(long time)
让线程睡眠指定的毫秒值
2.实现方式二(比较常用):
- 1)写一个实现类 实现Runnable接口
- 2)复写run方法
- 3)创建实现类的对象
- 4) 创建Thread类的对象把Runable的实现类对象传递给Thread
- 5)调用start()方法开启线程
即:new Thread(线程执行的任务体对象(实现Runnable接口子类对象),线程名字).start();
Runnable接口中没有getName方法
不能直接getName()获取线程名
要通过Thread.currentThread().getName()获取
实现Runnable接口创建多线程的好处:
1.避免了第一种的单继承的局限性
Java中一个类只能继承一个类,类继承了Thread类就不能再继承其他类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的解耦性(解耦)
实现Runnable接口的方式,把设置线程任务的开启线程进行了分离(解耦)
创建了Runnable对象,调用start()方法,用来开启新的线程
3.对多个线程而言,实现了资源的共享
写一个实现类,实现Runnable接口
public class DownloadRunnable implements Runnable{
//复写run方法
//这段代码被谁执行,获取的线程名字就是谁
//run()方法是用来设置线程任务的方法
public void run(){
for(int i=0;i<=100;i++){
String name = Thread.currentThread().getName();//Thread.currentThread()获取Thread类的对象,再通过对象调用getName()获取线程对象的名字
}
}
}
测试类:
public class Demo2{
public static void main(String[] args){
//创建实现类的对象,(线程的执行任务)
DownloadRunnable dr = new DownloadRunnable();
//创建Thread类的对象,把Runnable的实现类对象传递给Thread
Thread th1 = new Thread(dr);
th1.setName("线程1");
th1.start();
//创建Thread类的对象,把Runnable的实现类对象传递给Thread
Thread th2=new Thread(dr);
th2.setName("线程2");
th2.start();
}
}
二、线程安全问题
线程安全问题产生的原因:
多个线程访问【共享数据】时,当一个线程正在执行的时候还没有执行完,CPU的执行权就被其他线程抢走了,这个时候就有可能出现安全问题
原理图:
线程安全问题解决方案:
1)同步代码块:
当一个线程进入同步代码块的时候,就会被锁住,其他线程就进不来;
直到同步代码块执行完,其他线程才能进来执行
锁对象:任意对象,但要保证被锁的对象唯一(否在就会锁不住)
synchronized(被锁的对象){
可能出现线程安全问题的代码
}
2)同步方法
同步方法:一个线程执行该方法,方法中的代码就全部锁住
public synchronized void 方法名(){
可能出现线程安全问题的代码
}
类名.class(字节码文件/对象(类的字节码也是可以作为被锁的对象而且更好因为字节码对象只会有一个,符合锁对象要求,只不过是Class类型))
3)Lock锁
//创建锁对象
Lock lock = new ReentrantLock();
lock.lock();//上锁
....
有可能有安全问题的代码
....
lock.unlock();//解锁
同步代码块和同步代码方法的使用要比同步锁使用的多
三、线程状态
NEW(新建状态):至今尚未启动的线程处于这种状态。
RUNNABLE(运行状态):正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED(阻塞状态):在等待CPU的执行权
WAITING(无限等待):无限等待,没有叫醒你就一直等
TIMED_WAITING(计时等待):在有限的时间内等待,时间到了就自动醒来
TERMINATED(死亡状态):线程执行完毕
1.当线程较少时,其他线程执行notify()方法,唤醒处于无限等待的线程,此时线程就获得锁对象直接进入可运行状态,如果线程较多(一般有2~3个以上)就进入被阻塞状态参与对cpu的竞争中;
2.处于计时等待的线程同上
线程计时等待与唤醒的方式:
等待和唤醒案例
消费者(顾客)
1)先告知老板做包子
2)包子还没做好,把自己变为等待状态
3)包子做好了,开吃
生成者(老板)
1)老板花2秒时间做包子
2)包子做好了之后,通知顾客吃包子
public class Demo06{
public static void main(String[] args){
//创建一个锁对象
Object obj = new Object();//用于接收任何传进来的线程对象
//创建消费者的线程
new Thread(){
@Override
public void run(){
while (true){
synchronized(obj){
//1.先告知老板做包子
System.out.println("顾客告知老板做包子的馅和数量");
//2)包子正在制作中,把自己变为等待状态(需要使用锁对象调用wait()方法)
try{
obj.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
//3)包子做好了,开吃
System.out.println("包子做好了,顾客开吃...");
System.out.println("-------------------------");
}
}
}
}
}.start();
//创建生成者(老板)的线程
new Thread(){
@Override
public void run(){
while(true){
//1)老板花2秒时间做包子
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
//2)包子做好了之后,通知顾客吃包子
synchronized(obj){
System.out.println("包子好了,通知顾客吃包子");
obj.notify();
}
}
}
}.start();
}
}
正在运行的程序就是进程
一个方法就是一个线程,而一个进程中不止包含一个方法,但一个方法就是执行进程的基本单元
主线程(main线程)和其他线程都是同优先级的所以CPU是随机执行的
Thread.sleep()方法是让程序执行速度放慢,放大程序出错的机率,不是程序出错的原因
静态方法访问的方法或成员变量都必须是静态的
Java学习第18天链接:
https://blog.youkuaiyun.com/LJN951118/article/details/89409805