多线程
一、进程与线程
进程
进程就是程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。比如:火车。 通常再一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位。比如:火车厢 。
线程
线程就是独立的执行路径 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程 main()称之为主线程,为系统的入口,用于执行整个程序 priority = 5; 在一个进程中,如果开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的 对同一份资源操作时mm会存在资源抢夺的问题,需要加入并发控制 线程会带来额外的开销,如CPU调度时间,并发控制开销 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建(重点)
三种创建方式
一、继承自Thread类,重写run方法,创建实例调用start方法
public class ThreadTest extends Thread {
二、实现Runnable接口,实现run方法,创建Thread时作为参数传入,调用start方法
public class TestRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现了run方法");
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
threadTest.start();
Thread thread = new Thread(new TestRunnable());
thread.start();
}
}
三、实现Callable接口,重写call方法,
可以定义返回值
可以抛出异常
public class TestCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("创建成功");
return true;
}
public static void main(String[] args) {
TestCallable callable = new TestCallable();
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);
//提交执行
Future<Boolean> result = service.submit(callable);
//获取返回值
boolean isTrue = result.get();
service.shutdownNow();
}
}
线程状态
创建状态:new 就绪状态:start 阻塞状态:sleep,wait 运行状态:run(获取cpu片段) 死亡状态:dead(一个线程只能start一次)
线程方法
一、停止线程
-
线程休眠 sleep() sleep(毫秒数)指定当前线程停止的实践,阻塞 sleep()存在异常InteruptedException sleep()时间到达后线程进入就绪状态 sleep()可以模拟网络延时,倒计时等 每一个对象都有一个锁,sleep不会释放锁
-
线程礼让 yield() 礼让线程,让当前正在执行的线程暂停,但不阻塞 将线程从运行状态转为就绪状态 让CPU重新调度,礼让不一定成功,看CPU心情
-
线程强制执行 join() Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞 可以想象成插队
-
线程优先级 java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程;线程调度器按照优先级决定该调度哪个线程来执行 线程的优先级用数字来表示,范围从1~10 Thread.MIN_PRIORITY = 1 Thread.MAX_PRIORITY= 10 Thread.NORM_PRIORITY = 5 使用getPriority()和setPriority()来获取或改变优先级
-
守护线程setDeamon() 线程分为用户线程和守护线程 虚拟机必须确保用户线程执行完毕 虚拟机不用等待守护线程执行完毕 如后台记录操作日志,监控内存,垃圾回收
线程同步(重点)
背景(问题)
票的剩余数会为负数,银行账户会为负数,list的长度会少?
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程在使用 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问的冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制syncronized,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一下问题
-
一个线程持有锁会导致其他所有需要此锁的进程挂起
-
在多线程竞争的情况下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
-
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
死锁 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有"两个以上的对象锁"时,就可能发生死锁现象 死锁产生的条件 互斥条件: 一个资源每次只能被一个进程使用 请求保持条件: 一个进程因请求资源而阻塞时.对以获得的资源保持不放 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
三种解决方法:
一、同步方法
同步方法中无需指定同步监视器,因为同步方法中的同步监视器就是this,就是这个对象本身,或者是class
同步方法在方法上添加synchronized关键子,锁的是对象本身
//不安全的买票
public class UnsafeButTicket {
public static void main(String[] args) {
BuyTicket bt=new BuyTicket();
new Thread(bt,"我").start();
new Thread(bt,"你").start();
new Thread(bt,"黄牛党").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums=10;
boolean flag=true;//外部停止方式
二、同步块: synchronized(obj){}
obj称之为同步监视器 obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步监视器的执行过程
第一个线程访问,锁定同步监视器,执行其中的代码 第二个线程访问,发现同步监视器被锁定,无法访问 第一个线程访问完毕,解锁同步监视器 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
//不安全取钱
//两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account=new Account(100,"结婚基金");
Drawing you=new Drawing(account,50,"你");
Drawing girlFriend=new Drawing(account,100,"女朋友");
you.start();
girlFriend.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
//取了多少钱;
int drawingMoney;
//现在手里又多少钱
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱