Java 多线程
Java进程与线程的区别
进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少
有一个线程。
Java线程与线程的区别:
- 所有与进程相关的资源,都被记录在PCB(进程控制模块)中。
- 进程是抢占处理机的调度单位。
- 线程属于某个进程,共享进程的资源。
- 线程由堆栈寄存器、程序计数器和TCB(线程控制模块)组成。
- 线程是CPU调度的最小单位,进程是资源分配的最小单位。
线程调度
1.分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
2.抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的为抢占式调度,CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
实现多线程的方式
1.实现Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程任务
for (int i =0;i++<10;){
System.out.println("举头望明月"+i);
}
}
}
MyRunnable my = new MyRunnable();
Thread tr = new Thread(my);
tr.start();
2.继承Thread类
public class MyThread extends Thread {
/**
* RUN方法就是线程要执行的任务方法
*/
@Override
public void run() {
//这里的代码,就是一条新的执行路径
//这个执行路径的处罚方式,不是调用run方法,而是通过thread对象的start()来启动
for (int i =0;i++<10;){
System.out.println("锄禾日当午"+i);
}
}
}
MyThread t = new MyThread().start();
3.实现Callable接口
class mr implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
return 100;
}
}
Callable<Integer> integerCallable = new mr();
FutureTask<Integer> task = new FutureTask<>(integerCallable);
new Thread(task).start();
4.线程池
//线程池 Executors
/***缓存线程池.
*(长度无限制)
*执行流程:
*1.判断线程池是否存在空闲线程
*2.存在则使用
*3.不存在,则创建线程并放入线程池,然后使用*/
ExecutorServiceservice=Executors.newCachedThreadPool();
//向线程池中加入新的任务
service.execute(new Runnable(){
@Override
public void run(){
System.out.println("线程的名称:"+Thread.currentThread().getName());}});
service.execute(new Runnable(){@Override public void run(){System.out.println("线程的名称:"+Thread.currentThread().getName());}});
service.execute(new Runnable(){@Override public void run(){System.out.println("线程的名称:"+Thread.currentThread().getName());}});
当然也包括定长线程池,单线程池,周期性任务定长线程池
实现Runnable接口比继承Thread类所具有的优势
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
Runnable 与 Callable的相同点
都是接口都可以编写多线程程序
都采用Thread.start()启动线程
Runnable 与 Callable的不同点
Runnable没有返回值;
Callable可以返回执行结果Callable接口的call()允许抛出异常;Runnable的run()不能抛出Callable获取返回值
Callalble接口支持返回执行结果,需要调FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞
线程的相关API
//获取当前线程的名字
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
https://blog.csdn.net/evankaka/article/details/44153709#t3
这篇blog中对于这些方法有较完善的展示。
线程通信方法
wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。
由于wait,notify,以及notifyAll都涉及到与锁相关的操作
wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程
有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。
所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当
**
wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
notifyAll(): 一旦执行此方法,就会唤醒所有被wait()的线程
**
显示锁与隐式锁解决线程安全问题
线程的安全问题:
什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
使用同步监视器(锁)
Synchronized(同步监视器){undefined
//需要被同步的代码
}
public static void main(String[] args) {
Runnable runna = new BuyTicket();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
}
static class BuyTicket implements Runnable {
private int count = 10;
private Object o = new Object();
@Override
public void run() {
while (true){
// Object o = new Object();
synchronized (o) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "正在买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票为:" + count+Thread.currentThread().getName());
} else {
break;
}
}
}
}
}
方式二:同步方法
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)
public static void main(String[] args) {
Runnable runna = new BuyTicket();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
}
static class BuyTicket implements Runnable {
private int count = 10;
@Override
public void run() {
while (true){
boolean sell = sell();
if (!sell) {
break;
}
}
}
public synchronized boolean sell(){
//如果不是静态的同步方法就是this,是就是类名+class
if (count>0){
System.out.println("正在买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票为:"+count+Thread.currentThread().getName());
return true;
}
return false;
}
}
总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread。class
方式三:JDK5.0新增的lock锁方法
public static void main(String[] args) {
Runnable runna = new BuyTicket();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
new Thread(runna).start();
}
static class BuyTicket implements Runnable {
private int count = 10;
//显示锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "正在买票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票为:" + count+Thread.currentThread().getName());
} else {
break;
}
lock.unlock();
}
}
}
同步代码块和同步方法都属于隐身锁
显示锁:Lock
转载-----线程状态转换
下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
————————————————
版权声明:本文为CSDN博主「Evankaka」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/evankaka/article/details/44153709