Java多线程
1.什么是多线程
打开任务管理器我们可以看到许多进程,一个应用程序一启动就可以是一个进程。线程是进程里面的子任务,我们学Java都配置过环境变量,那个”系统属性“页面 就很烦啊,一旦打开了 ”环境变量“,就必须要关闭或者确认后才能重新操作 ”系统属性“,否则不但无法操作而且 ”环境变量“ 的框还会一闪一闪的。这就是单线程,只允许单线操作。
再举个多线程的例子,玩扫雷游戏的时候,只要开始操作,那个计时器就会持续不断计数。也就是说我们的操作和计时器是分为两个线程运行的。
2.实现多线程的两种方式
- 继承Thread,重新run方法
public class demo1 extends Thread{
@Override
public void run() {
for(int i =0;i<10;i++){
System.out.println(getName()+i);//getName是继承自父类
}
}
public static void main(String[] args) {
demo1 a = new demo1();
demo1 b = new demo1();
//demo1 c = new demo1();
a.setName("路飞");
b.setName("索隆");
//获取当前线程名,当前是主线程,也就是main
System.out.println(Thread.currentThread().getName());
//设置主线程名称(当前线程)
Thread.currentThread().setName("罗杰");
a.start();
b.start();
//c.start();
//主线程任务
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
介绍部分函数:
try {
Thread.sleep(1000);//运行后休息1秒,需要捕获或抛出异常
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过有参构造直接给线程赋值,无需setName
public demo1(String name){
super(name);
}
demo1 a = new demo1("路飞");
demo1 b = new demo1("索隆");
//获取线程的优先级,默认为5
System.out.println(a.getPriority());
System.out.println(b.getPriority());
//设置线程优先级
c.setPriority(10);//1-10数字
b.setPriority(Thread.MAX_PRIORITY);//最大10
a.setPriority(Thread.MIN_PRIORITY);//最小1
- 实现Runnable接口
public class demo2 implements Runnable{
/**
* 实现Runnable接口的好处:
* 1.避免单继承的局限性
* 2.适合相同的代码去处理同一个资源,体现面向对象的思想
*/
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
demo2 aa = new demo2();
Thread a = new Thread(aa,"路飞");
Thread b = new Thread(aa,"索隆");
a.start();
b.start();
}
}
3.守护线程
当线程中全部都是守护线程,jvm退出,因为有延迟,所以还会往后执行一丢丢的时间。
a.setDaemon(true);
b.setDaemon(true);
c.setDaemon(true);
举个例子,我们把子线程都设为守护线程,一旦主线程先执行结束,子线程再执行短暂时间后立即结束。
4.线程安全
在多线程中一旦共享数据且线程中对该数据进行了处理,就会出现线程安全的问题,当一个线程刚要输出共享数据时,另一个线程抢到了cpu执行权先对数据进行了修改,那第一个线程中的数据就发生了变化。
提供一个情景:a线程有一个if语句,它成功进入if语句后,b线程抢到了执行权,修改了共享数据,使其不满足if,当执行权回到a后,数据已经不满足了,但它早已通过了if。
解决线程安全的三种方式(都是基于synchronized):
- 使用synchronized代码块,用synchronized将语句块框住,要传入任意对象,注意每次都要传入同一个对象,这才表明是同一个锁,否则每次执行又是新锁根本锁不住。我没给例子所以说明它用的少。
- 使用同步方法:
分为静态和动态方法
public static synchronized void sellTickets(){
//这是静态方法的例子,
//相当于synchronized(类名.class),也就是synchronized代码块,传入的锁是类名.class
}
public synchronized void sellTicket(){
//这是动态方法的例子
//相当于synchronized(this),也就是synchronized代码块,传入的锁是当前对象,就是this
}
3.使用Lock类
private Lock lock = new ReentrantLock();//先在类中定义一个锁
lock.lock();//加锁
lock.unlock();//解锁
//由于程序可能出现异常导致解锁的步骤未执行就已经出问题
//所以一般使用try/finally语句块,try中加锁,finally中解锁
5.等待唤醒
使用wait()停止当前线程,注意它需要在synchronized内使用,比如在同步方法内使用;
使用notifyAll()唤醒全部等待线程;
举个使用场景:买卖流程,当商品卖完后就要让消费者的线程进入等待状态,等到生产者生产完一批商品后再唤醒消费者的线程。
当商品多到一定的阈值,就要让生产者进入等待状态,等待消费者消费了一定的商品后再唤醒生产者。
6.总结
以上就是线程的主要内容,线程在软件的开发中是十分常见的,线程锁和数据库的锁都需要谨慎使用,它们既能保证线程和数据安全,同时也会降低运行效率,所以一定要慎之又慎。