java多线程的学习

29 篇文章 0 订阅
17 篇文章 0 订阅

怎么进入死锁,线程死锁:     

既然可以上锁,那么假如有2个线程,一个线程想先锁对象1,再锁对象2,恰好另外有一个线程先锁对象2,再锁对象1。

在这个过程中,当线程1把对象1锁好以后,就想去锁对象2,但是不巧,线程2已经把对象2锁上了,也正在尝试去锁对象1。

什么时候结束呢,只有线程1把2个对象都锁上并把方法执行完,并且线程2把2个对象也都锁上并且把方法执行完毕,那么就结束了,

但是,谁都不肯放掉已经锁上的对象,所以就没有结果,这种情况就叫做线程死锁。

 

线程锁:在多线程环境中,当我们需要保持线程同步时,通常通过锁来实现.

 

同步代码块和同步方法:

 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。

另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

     二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

     三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞

同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好

 

线程的生命周期,创建一个线程的时候,重写的run方法是什么状态(准备)

新建--就绪--运行--阻塞--死亡

Sleep:没有释放锁,时间到了,会自动等待CPU时间片执行

Wait: 释放锁,必须用notify和notifyAll唤醒,只能在同步控制方法或者同步控制块里面用

Yeild: 停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield()  方法将不会起作用。 

Join:使当前线程停下来等待,直至另一个调用join方法的线程终止

如何停止一个正在运行的线程?

可以使用正在运行的线程,支持线程中断,通常是定义一个volatile的状态变量,在运行线程线程中读这个变量,其它线程中修改这个变量

 

java如何实现多线程之间同步1.同步方法;2.同步代码块;3.使用特殊域变量(volatile)实现线程同步;4.使用重入锁实现线程同步;5.使用局部变量实现线程同步 。

 

 

Lock 和 synchronized 有一点明显的区别  lock 必须在 finally 块中释放。

Lock接口比同步方法和同步块提供了更具扩展性的锁操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

  • 可以使锁更公平
  • 可以使线程在等待锁的时候响应中断
  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
  • 可以在不同的范围,以不同的顺序获取和释放锁

整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

 

Lock和synchronized区别:

 

主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释   放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

 

 

如何实现线程间的通信

可以通过3个方法来解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。需要注意的是:由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。另外,只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。

为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁

什么是多线程的上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

 

如何在两个线程之间共享数据

通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的

Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?

自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性。

volatile关键字的作用是:保证变量的可见性。 
在java内存结构中,每个线程都是有自己独立的内存空间(此处指的线程栈)。当需要对一个共享变量操作时,线程会将这个数据从主存空间复制到自己的独立空间内进行操作,然后在某个时刻将修改后的值刷新到主存空间。这个中间时间就会发生许多奇奇怪怪的线程安全问题了,volatile就出来了,它保证读取数据时只从主存空间读取,修改数据直接修改到主存空间中去,这样就保证了这个变量对多个操作线程的可见性了。换句话说,被volatile修饰的变量,能保证该变量的 单次读或者单次写 操作是原子的。

但是线程安全是两方面需要的 原子性(指的是多条操作)和可见性。volatile只能保证可见性,synchronized是两个均保证的。 
volatile轻量级,只能修饰变量;synchronized重量级,还可修饰方法。 
volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞

 当我们使用volatile关键字去修饰变量的时候,所以线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

 

线程池:Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程

 

单个任务处理的时间很短而请求的数量巨大。

能立即为请求服务,提高了响应速度;可以通过调整线池中线程的数目防止出现资源不足的情况。

 

①newSingleThreadExecutor

单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务

②newFixedThreadExecutor(n)

固定数量的池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行

③newCacheThreadExecutor(推荐使用)

可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行

④newScheduleThreadExecutor

大小无限制的线程池,支持定时和周期性的执行线程

 

ExecutorService ex = Executors.newCacheThreadExecutor();

for(int i=0; i<5; i++){

runnable rn = new runnable();

ex.execute(rn);

}

 

Synchronized底层实现:

主要基于对象头和monitor来实现

每个对象都有一个锁(Monitor,监视器锁),class对象也有锁,如果synchronized关键字修饰同步代码块,通过反编译可以看到,其实是有个monitorenter和monitorexit指令,也就是说,某个线程必须首先获得该对象的监视器锁,才能进入同步代码块,如果此时其它线程也去获取该对象的锁,则会阻塞直至当前线程释放掉监视器锁。synchronized(this)是锁当前对象,synchronized(A.class)是锁class对象。

 

进程间通信的方式?

    (1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

    (2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

    (3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

    (4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

    (5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

(6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

 

---------继承---------

class Thread1 extends Thread{

private String name;

     public Thread1(String name) {

        this.name=name;

    }

public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(name + "运行  :  " + i);

            try {

                sleep((int) Math.random() * 10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

       

}

}

public class Main {

public static void main(String[] args) {

Thread1 mTh1=new Thread1("A");

Thread1 mTh2=new Thread1("B");

mTh1.start();

mTh2.start();

 

}

}

 

---------实现------------------

class Thread2 implements Runnable{

    private int count=15;

@Override

public void run() {

  for (int i = 0; i < 5; i++) {

  System.out.println(Thread.currentThread().getName() + "运行  count= " + count--);

            try {

             Thread.sleep((int) Math.random() * 10);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

}

}

public class Main {

public static void main(String[] args) {

Thread2 my = new Thread2();

        new Thread(my, "C").start();

        new Thread(my, "D").start();

        new Thread(my, "E").start();

}

 

}

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值