粗略的讲,多线程通常是指在程序运行时,使用CPU的多个线程,由多个线程同时共同完成某些任务,这样的行为叫做多线程处理,这样的方式就是多线程。
在java应用中,最常见的莫过于WEB应用了,而如今的WEB应用大多都有着高并发的需求,需要在很短的时间处理大量的用户请求,因此使用多线程同时处理任务尤为重要。并发可以由多线程来完成,多线程同时做某一件事,从而大大的提升程序的并发量。提高程序的请求吞吐量。线程是进程中的一个最小的执行单元。线程处于进程中,而进程则在某种意义上又组成了软件。如果你的程序需要高并发,那么多线程的使用则必不可少
1.线程间的那些是共享的?
答:假设有线程A和线程B,那么他们的堆内存和方法区共享,但是栈内存独立,每一个线程都拥有一个栈。
类 | 说明 | 位置 |
---|---|---|
Thread | 创建的具体线程的类型,自定义线程对象衍生于此 | java.long.Thread |
synchronized | 线程同步机制 | 关键字 |
FutureTask | 一种可取消的线程任务类 | java.util.concurrent.FutureTask |
要想使用多线程首先要能创建一个线程。以下为创建线程的三种方式
第一步:自定义一个类继承java.lang.Thread类 重写 run() 方法
public class MyThread extends Thread {
/*
run()方法的作用是创建一个线程,但线程并启动。此时线程处于新建状态。
run()方法也仅仅是一个普通的方法
*/
@Override
public void run() {
//程序执行到此处时线程就处于运行状态
//线程需要执行的代码........
}
}
第二步:创建线程对象,调用线程对象的start()方法
public class testThread {
public static void main(String[] args) {
MyThread thread = new MyThread();
/*
执行start()方法只会【启动】一个分支线程,在JVM中创建一个新的栈空间,此时线程就开始运行了。
*/
thread.start();//此时线程处于就绪状态
}
}
第一步:创建一个类实现 java.lang.Runnable 接口,实现 run()方法。
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <100; i++){
System.out.println("分支线程------->"+i);
}
}
}
第二步:new 一个 Thread 对象,将线程对象作为参数传入,调用 Thread 对象的 start()方法启动线程。
public class test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i = 0; i <100; i++){
System.out.println("主线程-------->"+i);
}
}
}
第一步:创建一个未来任务类,实现 callable 接口重写 call 方法。
/*
callable接口的位置是 java.util.concurrent.Callable
设计目的就是用来返回结果和可抛出异常的,成为一个线程可执行的,可取消执行的任务类。
*/
public class MyFutureTask implements Callable {
@Override
public Object call() throws Exception {
//需要执行的代码
return null;
}
}
第二步:创建FutureTask对象将未来任务类对象传入
/*
FutureTask类的位置是java.util.concurrent.FutureTask
FutureTask实现了RunnableFuture接口而RunnableFuture接口实现了Runnable, Future
成为了线程类和可终止有返回值的任务类
*/
public class Test {
public static void main(String[] args) {
FutureTask task = new FutureTask(new MyFutureTask());
//启动线程。。。。
}
}
第三步:启动线程
Thread thread = new Thread(task);
thread.start();
获取返回值(!!!get方法的运行会阻塞当前线程)
//返回FutureTask任务类的执行结果
get();
尝试取消执行此任务`
cancel(boolean mayInterruptIfRunning)
将此未来的结果设置为给定值,除非此未来已被设置或已被取消。
set(V v)
String name = thread.getName();//获取线程的名称
thread.setName("Thread_01"); //修改线程的名称
long i = thread.getId();//获取线程的ID
int priority =thread.getPriority();//获取线程的优先级
thread.setPriority(10);//设置线程的优先级
源码中对线程优先级的说明
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
Thread.State state = thread.getState();//获取线程的当前状态
ThreadGroup group = thread.getThreadGroup();//返回此线程所属的线程组 如果线程已经死亡(停止),此方法返回null。
int number = thread. enumerate(Thread[] tarray);//将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中。返回放入数组的线程数
此行代码写在哪里就是获取那个线程的线程对象
Thread current = Thread.currentThread();//返回对当前正在执行的线程对象的引用
这些方法都返回boolean类型
thread.interrupted();// 测试当前线程是否中断。
thread.isAlive();// 测试这个线程是否活着
thread.isDaemon();//测试这个线程是否是守护线程。
thread.isInterrupted();//测试这个线程是否被中断。
Thread.yield();//使【当前】线程放弃抢夺到的CPU时间片,让位给其他线程,让位后会继续加入CPU时间片的抢夺
/*合并线程*/
thread.join();
/*
休眠和唤醒
使[当前]正在执行的线程以指定的毫秒数暂停(暂时停止执行),
并放弃CPU时间片,具体取决于系统定时器和调度程序的精度和准确性。
*/
Thread.sleep(long millis);
Thread.sleep(long millis, int nanos);
thread.interrupt();//唤醒当前线程,依靠异常打断线程的睡眠,被唤醒的线程会重新抢夺CPU时间片。
wait方法和notify方法都是 java.long包下Object类的方法,即每个对象都有这些方法 。
假设:
有一个User对象有一个线程 t ,t线程占有User的对象锁,当t线程调用User的 wait()方法后,会使占有User对象的当前t线程入无限期等待状态,并释放占有的对象锁,在调用notify()方法后会启动t线程恢复运行。假若有两个线程轮流占有锁使用notifyAll()方法即可唤醒这两个线程。常见于消费者于生者模式。
只有在多线程的情况下,且多个线程对数据有修改的操作,这种情况下我们才需要线程同步机制。
在java中线程安全需要使用synchronized来解决, synchronized 是线程同步机制。每一个对象都有一把对象锁,使用synchronized获取对象锁并持有,在一个线程占有对象锁时,其他线程无法获取该对象锁,也就无法对数据进行修改,这种锁被称为排它锁,而在线程对数据完成操作后,该线程会归还对象锁,其他线程才可以获得此对象的对象锁,获取对象的操作权。synchronized也有很大的缺点,即不支持并发,这使得线程排队操作对象,效率低下。同时也可能出现死锁现象。
synchronized线程同步使用的是锁机制,锁机制不支持并发,在需要并发时使用synchronized会使程序的执行效率降低,所以在考虑解决线程安全问题时synchronized并不是最优先的选择,而应是最后的选择。我们应该优先考虑其他方式,比如将需要线程安全的实例或静态变量声明为局部变量,将需要线程安全的对象创建多个等方式。
区别在于他们的使用场景不同。
从单纯使用的角度来讲:
在使用synchronized代码块时,我们可以自由的选择代码块中需要锁止的内容,可以是对象也可以是类,再者,在代码块中我们还可以执行其他的操作,且位置可变,更像是一个拿在手里可以加工东西的工具,随时加入可以选择的物料进行加工,想在哪用在哪用,相比更加灵活。
在使用synchronized修饰静态方法或实例方法时,只有在调用方法时才会占有锁,即方法是在类中,那么锁止的是this的对象锁或类的类锁,即调用那个类的方法就锁止那个类的对象锁或类的类锁,这种方式更像是被动,等待锁被占有。synchronized代码块则像是主动选择,使用修饰的方式更像是镶嵌在墙中的饮水机,在我们需要使用时,走过去摁下按钮。在我们提前知道要那些整体都固定需要锁的地方时,使用synchronized修饰来修饰来方法更加合适,而在需要动态或者不确定时,则使用代码块更加灵活,
从锁的角度来讲:
使用 synchronized代码块 和 synchronized修饰实例方法 时两者都是占有对象锁,一个类可以有new多个对象,从而得到多个不同的对象。获取不同对象的锁。而用synchronized修饰静态方法是占有类锁,一个类只有一个类锁,一旦类锁被占用其他线程则不能占有类锁,但可以占有这个类的对象锁。在真实使用时在执行到调用synchronized修饰的方法的这一步时,执行方法时会快速的占有锁,释放锁,而synchronized代码块中若代码复杂方法众多,执行代码块中的内容或许会慢一些。因此高内聚的必要性可见一斑。
将多个线程需要共享的对象放入synchronized()中,代码块中是此次需要排他执行的代码。我们应将必须进行线程安全操作的元素放入代码块中,而非有关于本次操作的所有代码,或只将必要的方法调用写在代码块中,和将代码块中需要的数据提前准备好,在代码块中以最快捷的方法调用,这样可以提高效率
synchronized (){
//需要执行的代码
}
将synchronized使用在 实例方法 上,线程将会获取到当前类的 this 的对象锁,且整个方法体都会同步,可能会无故扩大同步的范围。
public synchronized void test(){
//实例方法的方法体...
}
将synchronized使用在 静态方法上,线程将会获取到当前类的 类锁,且整个方法体都会同步,可能会无故扩大同步的范围。
public static synchronized void test(){
//静态方法的方法体...
}
Java中线程一般分为两大类:守护线程和用户线程,守护线程也叫后台线程,默默的在后台执行和监听,守护线程一般是一个死循环,在所有用户线程结束后守护线程才会自动结束(main方法 是用户线程)。例如可以使用守护线程在固定的时间备份数据等。
在创建线程对象之后启动线程对象之前,使用该线程对象调用此方法设置该线程为守护线程。
thread.setDaemon(true);
定时器在固定的时间执行固定的任务。在Java中已经实现了定时器,在java.util.Timer类中。
创建一个定时任务类(TimerTask 实现了Runnable,他也是一个线程)
public class MyTimerTask extends TimerTask {
@Override
public void run() {
//这里是任务的具体内容
}
}
创建时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse("2022-02-02 02:02:02");
创建一个定时器
Timer t = new Timer(String name, boolean isDaemon);//定时器对象
t.schedule( new MyTimerTask , date , 1000*10);//从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。
等待补充超链接
等待补充超链接
------------------------------------------完
暂未完结~
欢迎补充,欢迎指正。