目录
6.Thread类中的方法:join()、sleep()、yield()之间的区别?
什么是进程和线程?
进程中占有的资源:地址空间,全局变量,打开的文件,子进程,信号量,账户信息
线程占有的资源有:栈,寄存器,状态,程序计数器
1.并行和并发有什么区别?
1.并行是指多个事件在同一时刻发生,并发是同一时间间隔发生,
2.并行是在不同实体上的多个事件,并发是同一实体上的;
2.线程和进程的区别?
进程是程序运行和资源分配的基本单元,线程是进程的一个实体,是cpu调度和分派的基本单位。同一进程中的多个线程之间可以并发执行。
3.创建线程的方式
1.继承Thread类创建线程。
public class Thread1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// myThread.start();
MyThread myThread2 = new MyThread();
// myThread2.start();
MyThread myThread1 = new MyThread("qwe");
// myThread1.start();
myThread.run();
myThread1.run();
}
}
class MyThread extends Thread{
public MyThread(){
super();
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
// super.run();
for (int i=0;i<10;i++){
System.out.println(i+"="+getName());
}
}
}
2通过Runnable接口创建线程类。
public class Thread2 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
Thread thread1 = new Thread(myRunnable,"sad");
thread1.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(i+"="+Thread.currentThread().getName());
}
}
}
3.通过Callable和Future创建线程
1.实现Callable的接口重写call方法
2.创建实现类的对象
3.将对象传递给FutureTask构造器,
4.最后将FutureTask对象传递给Thread构造器,启动线程
public class Thread3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableImpl callable = new CallableImpl();
FutureTask<String> task1 = new FutureTask<String>(callable);
FutureTask<String> task2 = new FutureTask<String>(callable);
FutureTask<String> task3 = new FutureTask<String>(callable);
new Thread(task1,"竞赛者1").start();
new Thread(task2,"竞赛者2").start();
new Thread(task3,"竞赛者3").start();
System.out.println(task1.get());
System.out.println(task2.get());
System.out.println(task3.get());
}
}
class CallableImpl implements Callable{
private boolean flag =false;
@Override
public Object call() throws Exception {
synchronized (this){
if (this.flag==false){
this.flag =true;
return Thread.currentThread().getName()+"抢答成功";
}else {
return Thread.currentThread().getName()+"抢答失败";
}
}
}
}
四条线程卖票系统
四条线程卖票
1.继承Thread类把售票业务重写在run方法中
2.定义静态变量票数
3.通过while循环卖票,循环条件为true
4.让程序休眠10ms避免一个线程全部执行完
5.执行票数自减
6.打印线程跟票
7.给方法体加锁解决重卖问题
8.创建4条线程,执行start方法
public class TestThreadV1 { public static void main(String[] args) {
//5.创建多个线程对象 Ctrl+D 复制当前行
TicketThreadV1 t1 = new TicketThreadV1();
TicketThreadV1 t2 = new TicketThreadV1();
TicketThreadV1 t3 = new TicketThreadV1();
TicketThreadV1 t4 = new TicketThreadV1();
//6.以多线程的方式启动
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义线程售票业务类
class TicketThreadV1 extends Thread{
//3.定义变量,用来保存票数
//int tickets = 100;//不可以,会卖400张票
//7.解决4个线程卖了400张票的BUG
static int tickets = 100;//静态资源属于类资源,被全局所有对象共享,只有一份
//2.把业务写在重写run()里
@Override
public void run() {
//4.通过循环结构来一直卖票
while (true) {
synchronized (TestThreadV1.class) {
if (tickets > 0) {
try {
//8.如果数据能够经受住sleep的考验,才能说明数据没有了安全隐患--人为制造问题
//问题1:产生了重卖:同一张票卖给了多个人
//问题2:产生了超卖:超出了规定票数,甚至卖出了0和-1这样的票数
Thread.sleep(10);//让程序休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "=" + tickets--);
}
//做判断,如果没有票了,就退出死循环
if (tickets <= 0) break;//注意,死循环一定要设置出口
}
}
}
}
4.说一下Runnable和Callable有什么区别?
1.Runnable接口中的run()方法没有返回值,
2.Callable接口中的call()方法是有方法返回值的,是一个泛型,和Future,FutureTask配合可以获取异步执行的结果
5.线程有哪些状态?
线程通常有五种状态,创建,就绪,运行,阻塞,死亡。
创建状态:生成线程对象;
就绪状态:当调用线程对象的start();
运行状态:cpu分配时间片,开始执行run方法中的代码;
阻塞状态:由于sleep(),wait(),join()等方法导致线程阻塞,暂停运行;
死亡状态:run()执行结束或调用stop();
6.Thread类中的方法:join()、sleep()、yield()之间的区别?
Thread的非静态方法join()让一个线程等待另外一个线程完成才继续执行。如果线程A执行体中调用B线程的join()方法,则A线程将会被阻塞,直到B线程执行完为止,A才能得以继续执行。继续执行就是,在B线程前执行的线程,允许和B线程一起执行
Sleep()让当前正在执行的线程先暂停一定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,处于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程的执行。当sleep()结束后,然后转入到 Runnable(就绪状态),这样才能够得到执行的机会。
一个线程执行了yield()方法后,就会进入Runnable(就绪状态),【不同于sleep()和join()方法,因为这两个方法是使线程进入阻塞状态】。除此之外,yield()方法还与线程优先级有关,当某个线程调用yield()方法时,就会从运行状态转换到就绪状态后,CPU从就绪状态线程队列中只会选择与该线程优先级相同或者更高优先级的线程去执行。
7.sleep()和wait()的区别?
sleep():是线程类(Thread)的静态方法,让调用线程进入睡眠状态,睡眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间,因为sleep()是static方法,他不能改变对象的锁,当在synchronized中调用sleep(),线程休眠了,但是对象没有被释放,其他线程依旧不能访问。
wait(),是Object类的方法,让线程进入到一个该对象相关的等待池,同时释放对象锁,让其他线程访问,需要通过notify()或者notifyAll()来唤醒。
8.notify()和notifyAll()有和区别?
notify()随机唤醒一个wait线程,notifyAll()唤醒全部wait线程;唤醒的线程由等待池进入锁池,全部唤醒到锁池中要等待锁竞争,优先级高的线程竞争到锁的概率大,没有竞争到锁的会继续留在对象池中,当线程再次调用wait(),它才会回到等待池中。
9.什么是线程池?
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率。在JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法。
10.如何创建线程池?
newFixedThreadPool(int nThreads):创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。
newCachedThreadPool():创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
newSingleThreadExecutor():这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
newScheduledThreadPool(int corePoolSize):创建一个可间隔时间执行的线程池,并且可以指定数量,而且以延迟或定时的方式来执行任务,类似于Timer。
public class FixedThreadPool {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
//创建一个线程池对象,控制要创建几个线程对象
ExecutorService service = Executors.newFixedThreadPool(3);
//可以执行Runnable对象或者Callable对象代表的线程
service.execute(runnable);
service.execute(runnable);
//结束线程池
service.shutdown();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(i+"="+Thread.currentThread().getName());
}
}
}
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
final int j=i;
service.execute(()->{
System.out.println(j+" "+ Thread.currentThread().getName());
// 即使出错之后也不会停止运行,会创建一个新的线程来继续执行
// int x = 1/0;
});
}
}
}
5000条线程同时执行30秒倒计时,每5秒打印一次字段
1.实现Runnable接口重写run方法
2.创建线程局部变量30秒
3.执行while循环循环条件为true
4.循环体打印线程名,倒计时时间,当前时间
5.如果倒计时时间为0,则跳出循环
6.线程休眠5秒,需要捕获异常,当前时间减去5秒
7.循环整个方法体5000次
public class ThreadSync {
public static void main(String[] args) {
// 开启5个线程,同步输出倒计时30s,
threadMethod(5000, 30);
}
// count 线程数量,seconds倒计时秒数
public static void threadMethod(int count, int seconds) {
// 格式化时间输出
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 遍历线程
for (int i = 0; i < count; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// 线程本地变量,线程安全
ThreadLocal<Integer> local = new ThreadLocal<>();
local.set(seconds);
while (true) {
System.out.println(String.format("线程:%s,倒计时:%d,当前时间:%s", Thread.currentThread().getId(),
local.get(), sf.format(new Date())));
if (local.get() == 0)
break;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
local.set(local.get() - 5);
}
}
}).start();
}
}
}
11.线程池有哪些状态?
5种状态,Running,ShutDown(关闭),Stop,Tidying(整理),Terminated(结束)
12.在Java程序中如何保证多线程的运行安全?
线程安全在三个方面体现:
1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized)
2.可见性:一个线程对主内存的修改可以及时的被其他线程看见,(synchronized,volatile)
3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)
锁
1.什么是死锁?
多个进程执行过程中,由于竞争资源或者由于彼此通信造成的一种阻塞现象,若无外力用,无法继续推进。
2.死锁的四个必要条件?
互斥条件:进程对所分配的资源不允许其他进程访问,加了锁
请求和保持条件:进程获取一定资源,向其他资源发送请求,但是该资源被其他进程占有,此时请求阻塞,但获取的资源还保持不放,
不可剥夺条件:获取资源不可被剥夺,只有自己释放
环路等待条件:进程发生死锁后,多个进程之间形成闭环。
3.死锁的解决办法
1.固定加锁的顺序(针对锁顺序死锁)
2.开放调用(针对对象间协作造成的死锁)
3.使用定时锁-->tryLock();如果等待锁超时就抛出异常而不是一直等待
总结
发生死锁的主要原因:
由于程序的交错执行
解决:固定加锁的顺序
执行某方法时需要持有锁且不释放
解决:缩减同步代码块范围,最好只在操作共享变量才加锁
一直等待
解决:超时抛出异常
4.synchronized和volatile的区别?
volatile仅能实现变量的修改可见性,不能保证原子性,volatile是变量修饰符,不会造成线程阻塞;synchronized可修饰类,方法,代码块。
5.synchronized和lock的区别?
lock只能给代码块加锁,lock需要自己加锁和释放锁,如果没有unLock()去释放锁就会造成死锁,lock可以知道有没有成功获取锁。
6.悲观锁和乐观锁?
悲观锁:每次对资源进行操作时,都会持有独占的锁(synchronized互斥锁,ReentrantLock排他锁)
乐观锁:不进行上锁,但会在更新的时候判断,数据有无被更改,适用于多读的应用类型,可提高吞吐量。(ReentrantReadWriteLock读写锁)
7.对象锁和类锁的区别
- 对象锁是只对当前对象加锁,如果是同一个对象那么才会涉及是否做同步
- 类锁故名思议就是对这个class加锁,即使是同类不同的对象也必须同步
8.多线程的通信
1.同步
2.while轮询机制
3.wait/notify
4.管道通信