一、run()和start()方法区别
run()方法只是线程的主体方法,和普通方法一样,不会创建新的线程。只有调用start()方法,才会启动一个新的线程,新线程才会调用run()方法,线程才会开始执行。
二、如何控制某个方法允许并发访问线程的个数?
代码如下
public class ThreadClass {
static Semaphore semaphore;
public static void main(String[] args) {
semaphore = new Semaphore(1, true);
final ThreadClass threadClass = new ThreadClass();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
threadClass.firstRunThread();
}
});
thread.setName("Thread index :" + i);
thread.start();
}
}
/**
* first run thread.
*/
private void firstRunThread() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "-----in");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "-----out");
semaphore.release();
} catch (Exception e) {
}
}
}
三、在Java中wait和seelp方法的不同
wait()方法属于Object类,调用该方法时,线程会放弃对象锁,只有该对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
sleep()方法属于Thread类,sleep()导致程序暂停执行指定的时间,让出CPU,但它的监控状态依然保存着,当指定时间到了又会回到运行状态,sleep()方法中线程不会释放对象锁。
四、线程阻塞
(1)一般线程阻塞
1)线程执行了Thread.sleep(int millsecond)方法,放弃CPU,睡眠一段时间,一段时间过后恢复执行;
2)线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;
3)线程执行了一个对象的wait()方法,直接进入阻塞态,等待其他线程执行notify()/notifyAll()操作;
4)线程执行某些IO操作,因为等待相关资源而进入了阻塞态,如System.in,但没有收到键盘的输入,则进入阻塞态。
5)线程礼让,Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得CPU时间。线程自闭,join()方法,在当前线程调用另一个线程的join()方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程再由阻塞转为就绪态。
6)线程执行suspend()使线程进入阻塞态,必须resume()方法被调用,才能使线程重新进入可执行状态。
五、讲一下java中的同步的方法
之所以需要同步,因为在多线程并发控制,当多个线程同时操作一个可共享的资源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一一性和准确性。
1)synchronized修饰同步代码块或方法
由于java的每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需获得内置锁,否则就处于阴塞状态。
2)volatile修饰变量
保证变量在线程间的可见性,每次线程要访问volatile修饰的变量时都从内存中读取,而不缓存中,这样每个线程访问到的变量都是一样的。且使用内存屏障。
3)ReentrantLock重入锁,它常用的方法有ReentrantLock():创建一个ReentrantLock实例
lock()获得锁 unlock()释放锁
4)使用局部变量ThreadLocal实现线程同步,每个线程都会保存一份该变量的副本,副本之间相互独立,这样每个线程都可以随意修改自己的副本,而不影响其他线程。常用方法ThreadLocal()创建一个线程本地变量;get()返回此线程局部的当前线程副本变量;initialValue()返回此线程局部变量的当前线程的初始值;set(T value)将此线程变量的当前线程副本中的值设置为value
- 使用原子变量,如AtomicInteger,常用方法AtomicInteger(int value)创建个有给定初始值的AtomicInteger整数;addAndGet(int data)以原子方式将给定值与当前值相加
6)使用阻塞队列实现线程同步LinkedBlockingQueue<E>
六、死锁的四个必要条件?
1.互斥条件。一个资源只能被一个进程占用
2.不可剥夺条件。某个进程占用了资源,就只能他自己去释放。
3.请求和保持条件。某个进程之前申请了资源,我还想再申请资源,之前的资源还是我占用着,别人别想动。除非我自己不想用了,释放掉。
4.循环等待条件。一定会有一个环互相等待。
死锁的避免与预防
死锁避免的基本思想:系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。
理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。只要打破四个必要条件之一就能有效预防死锁的发生:
● 打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
● 打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
● 打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
● 打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。