Java多线程(二)
线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
优先级高的不一定先执行,大多数情况是这样的。
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调度了。
优先级的设定建议在
start()
调度前,
setPriority
之后紧接
start()
守护线程_daemon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 例子:后台记录操作日志,监控内存,垃圾回收等待等
thread.setDaemon(true);//默认false表示是用户线程
该
thread
即使是永远运行,也会结束,因为是守护线程,JVM不会等待,用户线程结束之后即结束。
线程同步
多个线程操作同一个资源。每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入
锁机制synchronizad8ed
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
不安全案例:(
ArrayList
是线程不安全的)
public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}System.out.println(list.size());}
可能的原因:1.可能有些线程还在执行时,就输出了
size()
2.某个瞬间,往
list
同一位置添加了两次覆盖了
同步方法及同步块
同步方法:
synchronized
默认锁的是
this
这个对象本身(后续反射章节会有涉及)
public synchronized void method(int args){}
同步块:
synchronized(obj){}
举例一个不安全银行取钱的例子:
public class TestAccount {public static void main(String[] args) throws InterruptedException {Account account = new Account(\"sum\",100);Drawing you = new Drawing(account,50,\"you\");Drawing girlfriend = new Drawing(account,70,\"girl\");new Thread(you).start();new Thread(girlfriend).start();}}class Account{String name;int Money;public Account(String name,int money){this.Money=money;this.name=name;}}class Drawing implements Runnable{Account account;int drawingMoney;int nowMoney=0;String name;public Drawing(Account account,int drawingMoney, String name) {this.account = account;this.drawingMoney = drawingMoney;this.name = name;}@Overridepublic void run() {if(account.Money-drawingMoney < 0){System.out.println(\"there\'s no money\");}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.Mon343dey = account.Money - drawingMoney;nowMoney = nowMoney+drawingMoney;System.out.println(\"bank remains \"+account.Money);System.out.println(name+\" get \"+nowMoney);}}
结果如下:
银行没有判断出钱不够,这里的
sleep
放大了事故可能性。
增加同步块:
@Overridepublic void run() {synchronized (account){if(account.Money-drawingMoney < 0){System.out.println(\"there\'s no money\");return ;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.Money = account.Money - drawingMoney;nowMoney = nowMoney+drawingMoney;System.out.println(name+\" get \"+nowMoney);System.out.println(\"bank remains \"+account.Money);}}
共同操作的是
account
所以obj即为它,同步块里即为原来的方法块,可以锁任何对象(需要锁操作的共同资源对象),这是和
synchronized
方法不同之处。
用该方法改进上方的不安全
ArrayList
public static void main(String[] args) throws InterruptedException {List<String> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {new Thread(()->{synchronized (list){list.add(Thread.currentThread().getName());}}).start();}Thread.sleep(1000);System.out.println(list.size());}
输出即为1000。
提一下并发编程中的安全List(
CopyOnWriteArrayList
)
public static void main(String[] args) throws InterruptedException {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();for (int i = 0; i < 10000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}Thread.sleep(5000);System.out.println(list.size());}
这里的List是线程安全的。
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源释放才能运行。某个同步块同时拥有“两个以上对象的锁”时,就可能发生死锁。
补充:
synchronized(obj){}
其实可以理解为等待
obj
释放锁之后执行代码块
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(例:A等B拿的资源,B等A拿的资源)
Lock锁
JDK5.0开始,提供了更强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。
ReentrantLock
是
Lock
的常用实现类(可重入锁)。
class TestLock2 implements Runnable{int ticketNums = 10;private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true){try {lock.lock();//加锁if(ticketNums>0){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(ticketNums--);}else{break;}} finally { //解锁lock.unlock();}}}}
仍然是抢票举例,
.lock()
加锁,
.unlock()
解锁,以上结果没有冲突情况。
线程协作(生产者消费者问题)
在这个问题中,仅用
synchronized
是不够的
-
synchronized
可阻止并发更新同一个共享资源,实现了同步
-
synchronized
不能用来实现不同线程之间的通信
解决方式1:管程法
public class TestPC { //利用缓冲区解决public static void main(String[] args) {Buffer buffer = new Buffer();new Producer(buffer).start();new Consumer(buffer).start();}}class Producer extends Thread{Buffer buffer;public Producer(Buffer buffer){this.buffer = buffer;}@Overridepublic void run() {for (int i = 1; i < 100; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}buffer.push(new product(i));System.out.println(\"生产了第\"+i+\"个产品\");}}}class Consumer extends Thread{Buffer buffer;public Consumer(Buffer buffer){this.buffer = buffer;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(\"消费了第\"+buffer.pop().id+\"个产品\");}}}class product{int id;public product(int id) {this.id = id;}}class Buffer{product[] pro = new product[10]; //缓存区大小int count=0;public synchronized void push(product p){if(count == pro.length){//通知等待消费try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}pro[count] = p;count++;//通知可以消费了this.notifyAll();}public synchronized product pop() {if (count == 0) {//等待生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}count--;product p2 = pro[count];this.notifyAll();return p2;}}
此段代码只涉及了新的
wait()
和
notifyAll()
,其余过程和操作系统相关知识描述一致,可以理一下思路。
wait(), notify()
和
notifyAll()
三个方法必须使用在同步代码块或同步方法中。这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现
IllegalMonitorStateException
异常。
解决方式2:信号灯法(利用标志位)建立一个(true,false)标志位进行判断,操作系统相关知识已有,不再详细描述
线程池
背景:经常创建和销毁,使用量特别大
思路:提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
corePoolSize
:核心池大小
maximumPoolSize
:最大线程数
keepAliveTime
:线程没有任务时最多保持多长时间后会终止
简单举例:
public class TestPool {public static void main(String[] args) {//创建服务,创建线程池ExecutorService service = Executors.newFixedThreadPool(10);//执行service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());//关闭链接service.shutdown();}}class MyThread implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}
线程池相关知识将在并发编程部分详细介绍。